其他问题

更新时间:
复制 MD 格式

AliyunAMQPReadOnlyAccess 为什么不支持查询队列消息?

AliyunAMQPReadOnlyAccess 策略只有"amqp:Get*"和"amqp:List*"的权限,可以添加 amqp:BasicGet 自定义权限,用于访问队列中的消息。请参见云消息队列 RabbitMQ 版自定义权限策略参考

{
    "Version": "1",
    "Statement": [
        {
            "Action": [
                "amqp:BasicGet"
            ],
            "Resource": [
                "acs:amqp:*:*:/instances/$instanceId/vhosts/$vhostName/queues/$queueName/messages/*"
            ],
            "Effect": "Allow"
        }
    ]
}

清空队列后控制台为什么仍显示有堆积消息?

清空队列操作默认不会清除延时消息。

QoS 生效机制?

如果存在超时阻塞的情况,建议将 QoS 或 Prefetch Count 设置为 1,避免客户端缓存过多消息同时过期。

什么时候消息会进死信?

如果一条消息没有设置 TTL,该消息进入死信队列仅有两种情况:1. 被 NACK,2. 消费重试次数达到上限。

如何设置消息ID?

请参见如何设置Message ID

死信队列里的消息量为什么会减少?

云消息队列 RabbitMQ 版消息最长保留 3 天。如果消息超过 3 天未被消费,死信队列中的消息量也会随之减少。请参见使用限制

消息保留时长?

云消息队列 RabbitMQ 版成功消费与未成功消费的所有消息的最大保留时长为 3 天。请参见使用限制

RabbitMQ 的网段?

云消息队列 RabbitMQ 版的网段不固定,无法提前获取固定网段信息。

自动删除队列为什么没有生效?

如果消费者未成功订阅队列,调用 channel.close 不会触发队列的自动删除。需要通过 channel.basicConsume 成功订阅消费后,自动删除队列的逻辑才会生效。

存储费用是什么?

云消息队列 RabbitMQ 版的消息保留时长为 3 天,存储费用基于消息体的存储量计算。该存储空间无法手动清理的,只能等待消息过期后自动释放。

清空队列的实际操作是重置消费位点。清空后仍可查看历史消息,因此清空队列后存储空间不会减少。

清空队列是什么?

清空队列的实际操作是重置消费位点(跳过未消费的消息),而不是删除消息。清空后仍可查看历史消息。

Queue 每秒流出消息数取决于什么?

Queue 每秒流出消息数取决于消息量、消费者数量及消费者设置的 QoS。

使用排他队列时需要注意什么?

Spring 框架使用CONNECTION模式时,需要提前在控制台创建 Exchange、Queue 及绑定关系。CONNECTION 模式下不会自动声明和创建这些资源。

如果消息进入队列, 然后重试16次, 那么收费是一次的费用还是16次的费用呢?

仅收取一次费用。后续重新入队再消费不再额外计费,仅对初始推送计费一次。

重入队是指消费失败后,消息重新进入服务端的队列等待消费。服务端有专门的重试队列,会按照一定的时间间隔重新向消费者推送消息。

TPS 上限对应哪个监控?

对应云监控中的实例 API 请求速率峰值。

image

Connection 连接数可以超过限制吗?

对于 Connection 连接数限制,虽然官方文档中标注为实例级别的限制,但实际是按后台节点维度进行限制,实例级别连接总数可能少量超过限制值。

对于预付费实例,各项配置均不可动态调整,建议在创建实例时合理规划资源,避免因资源超限导致业务异常。

生产和消费速率一致,为什么监控还是显示有堆积?

实际发送和消费速率基本匹配,但部分消息的 ACK 耗时较长,导致监控在某个采样时间点统计到一定的堆积量。在下一个采样周期,堆积通常会恢复正常。

某些 Pod 的消费量为什么突然下降了?

建议排查 Pod 的性能情况。可能因 CPU 使用率过高,导致客户端未能及时提交 ACK。服务端在未收到 ACK 的情况下无法继续推送消息,需要等待超时后重新推送,从而导致消费量下降。

例如 QoS 设置为1,那么只允许该消费者在同一时间有1 条消息未 ACK;服务端需要等待消费者处理完消息并提交 ACK 后,才能推送下一条消息。如果未收到 ACK,则只能等待超时后才能推送下一条。

RabbitMQ 可以使用默认密钥的主密钥吗?

可以。

报错 The channelMax limit is reached?

服务端只会对单个 Connection 上的 Channel 数量有限制,不会对总的 Channel 数量有限制;

从 SDK 代码来看,只要createChannel返回null,都会打印“The channelMax limit is reached”;

public Channel createChannel(boolean transactional) {
    try {
        Channel channel = this.delegate.createChannel();
        if (channel == null) {
            throw new AmqpResourceNotAvailableException("The channelMax limit is reached. Try later.");
        }
        if (transactional) {
            // Just created so we want to start the transaction
            channel.txSelect();
        }

升配后需要重启客户端,否则客户端在创建连接时仍使用升配前的 Channel 数量限制,会继续报错。

比如:实例购买的时候Channel Max 为 100,客户端在创建连接时会与服务端进行参数协商,最终采用服务端提供的 100 的限制;由于这些连接在升配之前就已建立,其协商的 Channel Max 值仍为 100,而非升配后的 2000 或 2500,因此会继续触发报错。

AMQConnection 类的 start() 方法即为客户端与服务端进行连接参数协商的入口,其源码如下:

public class AMQConnection extends ShutdownNotifierComponent implements Connection, NetworkConnection {

     * Connection.Start/.StartOk, Connection.Tune/.TuneOk, and then
     * calls Connection.Open and waits for the OpenOk. Sets heart-beat
     * and frame max values after tuning has taken place.
     * @throws IOException if an error is encountered
     * either before, or during, protocol negotiation;
     * sub-classes {@link ProtocolVersionMismatchException} and
     * {@link PossibleAuthenticationFailureException} will be thrown in the
     * corresponding circumstances. {@link AuthenticationFailureException}
     * will be thrown if the broker closes the connection with ACCESS_REFUSED.
     * If an exception is thrown, connection resources allocated can all be
     * garbage collected when the connection object is no longer referenced.
     */
    public void start()
            throws IOException, TimeoutException {
        initializeConsumerWorkService();
        initializeHeartbeatSender();
        this._running = true;
        // Make sure that the first thing we do is to send the header,
        // which should cause any socket errors to show up for us, rather
        // than risking them pop out in the MainLoop
        AMQChannel.SimpleBlockingRpcContinuation connStartBlocker =
            new AMQChannel.SimpleBlockingRpcContinuation();
        // We enqueue an RPC continuation here without sending an RPC
        // request, since the protocol specifies that after sending
        // the version negotiation header, the client (connection
        // initiator) is to wait for a connection.start method to
        // arrive.
        _channel0.enqueueRpc(connStartBlocker);
        try {
            // The following two lines are akin to AMQChannel's
            // transmit() method for this pseudo-RPC.
            _frameHandler.setTimeout(handshakeTimeout);
            _frameHandler.sendHeader();
        } catch (IOException ioe) {
            _frameHandler.close();
            throw ioe;
        }

        this._frameHandler.initialize( connection: this);

Channel 参数协商过程示意

public class AMQConnection extends ShutdownNotifierComponent implements Connection, NetworkConnection {
    public void start()
    } catch (TimeoutException te) {
        _frameHandler.close();
        throw te;
    } catch (ShutdownSignalException sse) {
        _frameHandler.close();
        throw AMQChannel.wrap(sse);
    } catch(IOException ioe) {
        _frameHandler.close();
        throw ioe;
    }

    try {
        // 从服务端获取channelMax
        int negotiatedChannelMax =
                negotiateChannelMax(this.requestedChannelMax,
                                connTune.getChannelMax());

        int channelMax = ConnectionFactory.ensureUnsignedShort(negotiatedChannelMax);

        if (channelMax != negotiatedChannelMax) {
            LOGGER.warn("Channel max must be between 0 and {}, value has been set to {} instead of {}",
                    MAX_UNSIGNED_SHORT, channelMax, negotiatedChannelMax);
        }

        // 初始化channel管理器
        _channelManager = instantiateChannelManager(channelMax, threadFactory);

        int frameMax =
                negotiatedMaxValue(this.requestedFrameMax,
                                connTune.getFrameMax());
        this._frameMax = frameMax;

        int negotiatedHeartbeat =
                negotiatedMaxValue(this.requestedHeartbeat,
                                connTune.getHeartbeat());

        int heartbeat = ConnectionFactory.ensureUnsignedShort(negotiatedHeartbeat);

        if (heartbeat != negotiatedHeartbeat) {
            LOGGER.warn("Heartbeat must be between 0 and {}, value has been set to {} instead of {}",
                    MAX_UNSIGNED_SHORT, heartbeat, negotiatedHeartbeat);

提升 TPS 会影响现有业务吗?

不会。通过控制台升配提升 TPS 时,不会导致连接闪断或已有连接断开。

创建 Connection 时,会有可能导致其他 Channel Shutdown 吗?

通常不会。但如果客户端资源(网络、内存、JVM)不足,可能导致其他连接或通道中断。建议重点排查客户端的资源使用情况,确认是否存在资源抢占或资源不足导致连接或通道中断的问题。

Queue 的消费者突然消失了?

建议优先检查该时间段内客户端的业务和资源情况,确认是否存在进程挂起或资源不足导致业务中断的问题。如果客户端运行正常,需要进一步确认服务端的消息推送是否正常。

队列清空消息未生效的原因?

Unacked 消息无法通过清空队列操作清除。Unacked 消息是指已推送到客户端但尚未提交 ACK 的消息,其数量会在消息过期后自动更新

客户端报错 IOException 或 connection error 的原因?

使用 RabbitMQ 客户端连接实例时,可能会遇到 IOExceptionconnection error,以下是常见原因及排查建议:

网络问题

网络不稳定或连接中断可能导致客户端连接断开。请检查网络环境、DNS 解析和防火墙规则是否正常。

ALL_IDEL 超时关闭

ALL_IDEL被服务端关闭而客户端使用到了已经关闭的连接,可能导致客户端报错。有多种原因:

  • 客户端使用的 SDK 不维持连接,不发送心跳包到服务端;

  • 客户端消费者线程被阻塞,导致 TCP buffer 被服务端消息占满,客户端心跳包无法发出;

  • 单 Connection 上多个 Channel 同时发送消息,单 TCP 流量突增,且服务端偶发乱序包使得满足 Linux Netfilter 的 Conntrack 对 TCP 包的 INVALID 判定,客户端主动 RST 连接。

建议排查以下方面:

  • 确认客户端 SDK 是否启用心跳机制;

  • 确认消费者线程是否阻塞(如消费逻辑耗时过长、同步操作阻塞BasicAck);

  • 通过tcpdump在客户端本地抓包,查看客户端与服务端网络交互情况,确定是否有发送心跳、或主动RST连接。

客户端日志如何计算 TPS?

对应 SQL 语句为:

 * and amqp-cn-xxx and Action : SendMessage | select InstanceId as instance_id, VHost as virtual_host, Queue as queue, microtime / 1000 / 1000 as time_second, count(*) as send_qps group by instance_id, virtual_host, queue, time_second order by time_second, send_qps limit 10000000

统计结果示例:

表格中重点关注 queuesend_qps 两列:队列 test-queue-by-code7 的 send_qps 分别为 6 和 4,队列 print.active.delayExchange3 的 send_qps 均为 1,virtual_host 均为 vhost-test

RabbitMQ 修改日志 Logstore 之后为什么查看日志为空?

在新的 Logstore 中点击【开启索引】,等待约一分钟后刷新页面即可查看日志。

操作步骤:

1)点击开启索引

2)直接【确定】即可

RabbitMQ 断连的可能原因?

如果客户端提交的有效 ACK 数量较少,服务端因未收到有效应答,会重复投递消息,导致消息堆积持续不下降。

而且这种情况下,表明消息在客户端堆积,可能导致连接无法发送心跳包,最终触发 Connection ALL_IDLE,连接断开。请优先排查客户端未能提交有效 ACK 的原因。

TTL 相关设置?

TTL 最大支持设置为 3 天,超过 3 天的设置将不生效。TTL 不生效时,消息到期后不会进入死信队列,并可能出现各种不符合预期的行为。

1)如果未设置 TTL,队列中均为普通消息,监控中的 readyMessage 指标即为普通消息的数量,也就是实际堆积量。

2)如果设置了 TTL,监控中的 readyMessage 指标表示存活消息的数量:

a) 如果 TTL 正常生效,队列中的存活消息数量根据 TTL 动态计算,监控中的 readyMessage 即为实际存活消息数量,属于正常现象。

b)如果 TTL 已设置但未生效,存活消息数量始终为 0,监控中的 readyMessage 也始终显示为 0,无法反映实际堆积情况。

报错 java.lang.IllegalArgumentException: Content headers exceeded max frame size: 40209 > 32768?

消息头部的帧大小超过限制。消息头部(Header)的默认大小限制为 32 KB,该限制在服务端无法调整。

建议将较长的数据从 Header 移至 Body 中,避免超出 Header 帧大小限制。

为什么清空队列还是可以查到消息?

清空队列后查询消息功能检索的是日志数据,日志的保存时间较长,因此仍可查询到历史消息。

解绑订阅关系报错?

原因:

排他队列仅对首次声明它的连接可见,只能由创建该排他队列的连接进行删除操作,无法通过控制台操作。

详情请参见排他队列

解决方案:

1)如果不再需要该排他队列,断开对应连接后,排他队列将被自动删除,其绑定关系也会一并移除。

2)如果无法找到创建该排他队列的 Connection ID,可以通过重启客户端来删除排他队列。

报错:VPC flow is not allowed to login in?

报错原因:

服务端检测到使用的用户名密码不是通过控制台生成的静态用户名密码,将请求流量误判为开源鉴权流量,因此拒绝连接。

解决方案:

确认 VPC 接入点连通性后,请确认使用的用户名和密码是在控制台上生成的静态用户名密码。如有疑问,建议重新生成用户名密码,确保配置正确。

创建实例:实例创建时间已经超过30min,但是实例仍然未创建好

排查步骤:

  • 检查创建实例指定的 VPC 是否存在,并属于指定 UID,且状态为 Available;

  • 检查创建实例指定的 vSwitch 是否满足要求,详细要求可见 OpenAPI CreateInstance 中的 VswitchIds 参数。

原因说明:

一般是上述两个条件在创建实例过程中发生了变化(例如 VPC 被删除)。在发起请求时校验通过,但是异步创建实例时相关请求报错。

解决方案:

如果创建实例的参数在创建过程中发生变化(例如 VPC 或者 vSwitch 被删除),需要释放 RabbitMQ 实例,重新创建。

如何实现跨地域内网访问?

跨地域内网访问的标准方案是使用 PrivateLink + CEN 方案;

请注意:使用该方案会产生 PrivateLink 的费用。详情请参见私网连接接入点