RocketMQ学习六-消息发送错误与解决方案

本文主要提到下面两类错误及解决方案:

  • 消息发送超时
  • System busy、Broker busy
一,消息发送错误
【RocketMQ学习六-消息发送错误与解决方案】消息发送超时,通常客户端的日志如下:
RocketMQ学习六-消息发送错误与解决方案
文章图片

客户端报消息发送超时,通常第一怀疑的对象是 RocketMQ 服务器,是不是 Broker 性能出现了抖动,无法抗住当前的量。
那我们如何来排查 RocketMQ 当前是否有性能瓶颈呢?
首先我们执行如下命令查看 RocketMQ 消息写入的耗时分布情况:
cd /${USER.HOME}/logs/rocketmqlogs/ grep -n 'PAGECACHERT' store.log | more

输出结果如下所示:
RocketMQ学习六-消息发送错误与解决方案
文章图片

RocketMQ 会每一分钟打印前一分钟内消息发送的耗时情况分布,我们从这里就能窥探 RocketMQ 消息写入是否存在明细的性能瓶颈,其区间如下:
  • [<=0ms] 小于 0ms,即微妙级别的
  • [0~10ms] 小于 10ms 的个数
  • [10~50ms] 大于 10ms 小于 50ms 的个数
其他区间显示,绝大多数会落在微妙级别完成,按照笔者的经验如果 100~200ms 及以上的区间超过 20 个后,说明 Broker 确实存在一定的瓶颈,如果只是少数几个,说明这个是内存或 PageCache 的抖动,问题不大。
在 RocketMQ broker 中还存在快速失败机制,即当 Broker 收到客户端的请求后会将消息先放入队列,然后顺序执行,如果一条消息队列中等待超过 200ms 就会启动快速失败,向客户端返回 [TIMEOUT_CLEAN_QUEUE]broker busy的错误。另外,Producer客户端如果那个时候正好出现了垃圾回收也是有可能造成消息发送超时的问题。
针对消息发送超时我们应该如何应对呢?
一般来说我们可以减少消息发送的超时时间,增加重试次数,并增加快速失败的最大等待时长。具体通过maxWaitTimeMillsInQueue进行配置,一般来说超时时间调为500ms-1000ms。需要提一下的是超时时间在不同版本间含义是不同的,在4.3.0(不含4.3.0)以下的版本,超时时间指的是单次发送的时间;而在4.3.0及以上版本中超时时间指的是所有重试的总的超时时间。
二,System busy、Broker busy
在使用 RocketMQ 中,如果 RocketMQ 集群达到 1W/tps 的压力负载水平,System busy、Broker busy 就会是大家经常会遇到的问题。例如如下图所示的异常栈。
RocketMQ学习六-消息发送错误与解决方案
文章图片

RocketMQ学习六-消息发送错误与解决方案
文章图片

纵观 RocketMQ 与 System busy、Broker busy 相关的错误关键字,总共包含如下 5 个:
[REJECTREQUEST]system busy
too many requests and system thread pool busy
[PC_SYNCHRONIZED]broker busy
[PCBUSY_CLEAN_QUEUE]broker busy
[TIMEOUT_CLEAN_QUEUE]broker busy
一般来说原因可以归纳为下面3种:
  1. PageCache 压力较大
其中如下三类错误属于此种情况:
[REJECTREQUEST]system busy
[PC_SYNCHRONIZED]broker busy
[PCBUSY_CLEAN_QUEUE]broker busy
判断 PageCache 是否忙的依据就是,在写入消息、向内存追加消息时加锁的时间,默认的判断标准是加锁时间超过 1s,就认为是 PageCache 压力大,向客户端抛出相关的错误日志。
  1. 发送线程池积压的拒绝策略
在 RocketMQ 中处理消息发送的,是一个只有一个线程的线程池,内部会维护一个有界队列,默认长度为 1W。如果当前队列中积压的数量超过 1w,执行线程池的拒绝策略,从而抛出 [too many requests and system thread pool busy] 错误。
  1. Broker 端快速失败
默认情况下 Broker 端开启了快速失败机制,就是在 Broker 端还未发生 PageCache 繁忙(加锁超过 1s)的情况,但存在一些请求在消息发送队列中等待 200ms 的情况,RocketMQ 会不再继续排队,直接向客户端返回 System busy,但由于 RocketMQ 客户端目前对该错误没有进行重试处理,所以在解决这类问题的时候需要额外处理。
PageCache 繁忙解决方案
一旦消息服务器出现大量 PageCache 繁忙(在向内存追加数据加锁超过 1s)的情况,这个是比较严重的问题,需要人为进行干预解决,解决的问题思路如下。
  1. transientStorePoolEnable
开启 transientStorePoolEnable 机制,即在 Broker 中配置文件中增加如下配置:
transientStorePoolEnable=true
transientStorePoolEnable 的原理如下图所示:
RocketMQ学习六-消息发送错误与解决方案
文章图片

引入 transientStorePoolEnable 能缓解 PageCache 的压力背后关键如下:
  • 消息先写入到堆外内存中,该内存由于启用了内存锁定机制,故消息的写入是接近直接操作内存,性能可以得到保证。
  • 消息进入到堆外内存后,后台会启动一个线程,一批一批将消息提交到 PageCache,即写消息时对 PageCache 的写操作由单条写入变成了批量写入,降低了对 PageCache 的压力。
引入 transientStorePoolEnable 会增加数据丢失的可能性,如果 Broker JVM 进程异常退出,提交到 PageCache 中的消息是不会丢失的,但存在堆外内存(DirectByteBuffer)中但还未提交到 PageCache 中的这部分消息,将会丢失。但通常情况下,RocketMQ 进程退出的可能性不大,另外,如果启用了 transientStorePoolEnable,消息发送端需要有重新推送机制(补偿思想)。
  1. 扩容
如果在开启了 transientStorePoolEnable 后,还会出现 PageCache 级别的繁忙,那需要集群进行扩容,或者对集群中的 Topic 进行拆分,即将一部分 Topic 迁移到其他集群中,降低集群的负载。

    推荐阅读