RocketMq流量控制

229 阅读3分钟

在使用 RocketMQ 的过程中,有时候我们会看到下面的日志:

[TIMEOUT_CLEAN_QUEUE]broker busy, start flow control for a while, period in queue: 206ms, size of queue: 5

这是因为 RocketMQ 触发了流量控制。生产者把消息写入 Broker,Consumer 从 Broker 拉取消息。Broker 是 RocketMQ 的核心 ,触发流量控制主要就是为了防止 Broker 压力过大而宕机

Broker流控

RockerMQ 默认采用异步刷盘策略,Producer 把消息发送到 Broker 后,Broker 会先把消息写入 Page Cache,刷盘线程定时地把数据从 Page Cache 刷到磁盘上。

那Broker Busy是如何产生的呢?

Broker 默认是开启快速失败的,处理逻辑类是 BrokerFastFailure,这个类中有一个定时任务用来清理过期的请求,每 10 ms 执行一次。

  1. Page Cache 繁忙 清理过期请求之前首先会判断 Page Cache 是否繁忙,如果繁忙,就会给 Producer 返回一个系统繁忙的状态码, 也就是开头的日志。那怎么判断 Page Cache 繁忙呢?Broker 收到一条消息后会追加到 Page Cache 或者内存映射文件,这个过程首先获取一个 CommitLog 写入锁,如果持有锁的时间大于 osPageCacheBusyTimeOutMills(默认 1s,可以配置),就认为 Page Cache 繁忙。具体代码见 DefaultMessageStore 类 isOSPageCacheBusy 方法。
  2. 清理过期请求 清理过期请求时,如果请求线程的创建时间到当前系统时间间隔大于 waitTimeMillsInSendQueue(默认 200ms,可以配置)就会清理这个请求,然后给 Producer 返回一个系统繁忙的状态码。

System Busy

在开启 transientStorePoolEnable 的情况下,写入消息时会先写入堆外内存(DirectByteBuffer),然后刷入 Page Cache,最后刷入磁盘。而读取消息是从 Page Cache,这样可以实现读写分离,避免读写都在 Page Cache 带来的问题

  1. 当发生拒绝请求时,就会返回系统繁忙的响应。判断依据是在开启 transientStorePoolEnable 配置的情况下,是否还有可用的 ByteBuffe。

  2. 线程池拒绝

Broker 收到请求后,会把处理逻辑封装成到 Runnable 中,由线程池来提交执行,如果线程池满了就会拒绝请求(这里线程池中队列的大小默认是 10000,可以通过参数 sendThreadPoolQueueCapacity 进行配置),线程池拒绝后会抛出异常 RejectedExecutionException,程序捕获到异常后,会判断是不是单向请求(OnewayRPC),如果不是,就会给 Producer 返回一个系统繁忙的状态码。

消息重试

Broker 发生流量控制的情况下,返回给 Producer 系统繁忙的状态码(code=2),Producer 收到这个状态码是不会进行重试的。

消费流控

DefaultMQPushConsumerImpl 类中有 Consumer 流控的逻辑 。

缓存消息数量超过阈值

ProcessQueue 保存的消息数量超过阈值(默认 1000,可以配置)

缓存消息大小超过阈值

ProcessQueue 保存的消息大小超过阈值(默认 100M,可以配置)

 缓存消息跨度超过阈值

对于非顺序消费的场景,ProcessQueue 中保存的最后一条和第一条消息偏移量之差超过阈值(默认 2000,可以配置)

获取锁失败

对于顺序消费的情况,ProcessQueue 加锁失败,也会延迟拉取,这个延迟时间默认是 3s,可以配置。