记一次RocketMQ消息堆积
昨天晚上系统从阿里云迁移到腾讯云,今天就出现MQ消息堆积。在已往的认知里,消息堆积基本上是消费者不给力,但在监控上看发现生产者消息很少,每秒也就几条,没理由消费者忙不过来。
但基于认知,还是第一时间去增加了消费者(把pod从3个节点加到了5个,队列始终是3个),过了几个小时又出现了消息堆积,这时候我们犯了第一个错误。当某个消费组的消费者数量大于队列数的时候,增加消费者其实是没用的。 可参看 RocketMQ 消费关系图
问题排查
通过一系列的排查,得到下面几个结论
- 生产者每秒就产生几条消息
- 消费者处理消息的平均速度在 800ms 每条
- 在某个时间段 消息错误率很高
1,2 连起来看是不科学的,既然「2」处理的那么快,而且我们还是多个节点,每个节点里面还是多线程,理论上每秒可以处理几十上百条,那问题就出现了在 「3」
消费者要处理的是方法A,在方法A第一行打印的日志是logA,方法A是被完整的try起来的,在catch里面打印的是logB。通过日志发现 logA挺多的,但是 logB很少(寥寥几条)
public ConsumeResult consume(MessageView message) {try {logger.info("logA")// ......} catch (Exception e) {logger.error("logB")}
}
这也不科学,怎么会没有异常的日志呢?又去控制台查询「重试消息」,发现没有重试的消息,更说不过去了。综合来看消息阻塞并不是消费者消费的太慢,倒像是消费者不消费了
这期间还发现了一个问题,在中午12点多的时候消息堆积到了300+,但是没一会(半分钟左右)消息就都被消费完了,这说明我们的系统还是支持很大的消费,只是不知道为什么不消费了
问题结论
被阻塞的是顺序消息,顺序消息在消费的时候会锁住当前队列。参看 局部有序
很不巧,在众多的消息中就有一条消息消费失败了。为什么在阿里云就没问题呢?腾讯云就不行呢?尽管在阿里云和腾讯云配置的都是16次重试,但阿里云重试间隔是 1s(在极快的时间就把消息丢到了死信队列里面去了),而腾讯云是阶梯式的 10s 30s … 2h,所以在腾讯云中的消息会一直被阻塞
问题已经找到了,临时的解决方案:把重试次数改为2,这样在一分钟之内就会把消息丢到死信队列里面从而跳过这个阻塞,然后重启消费者(因为这时候消费者已经阻塞了队列要等很久才会释放)
再来探讨一下这个过程中的一些问题
1、为什么顺序消息要锁队列
所谓的顺序消息,要严格保证顺序的话,生产者就要发送到同一个队列,消费组里面只能有一个消费者去消费,消费者必须单线程去消费,且未消费成功不允许跳过,也就是阻塞队列了
2、如果一定要锁,那岂不是不能重试了?
顺序消费的重试和并发消费的重试是完全不一样的。而腾讯云的顺序消费,竟然也是阶梯的… (ps: 腾讯云说后续会修复这个bug)
RocketMQ 官方文档说明 1
RocketMQ 官方文档说明 2
3、为什么在控制台没有看到重试消息
顺序消息的重试,是由客户端发起的,不是服务端,所以在控制台是看不到「重试消息」,但可以看到 logA 打印了很多次
腾讯云文档
RocketMQ文档
4、锁队列,是分消费组的
- groupA、groupB
- queueA
- messageA
如果groupA 正在顺序消费queueA中的messageA,但不幸这个消费失败了,queueA阻塞了。请注意这时候只是阻塞groupA的消费,groupB不管是顺序还是非顺序消费,都可以继续消费queueA不会被阻塞,因为每一个消费组都会维护自己的消费位点
顺序消费模式下的阻塞只影响:
- 同一消费组内的该队列消费
- 同一消费者实例对该队列的处理
不会影响:
- 其他消费组对同一队列的消费
- 同一消费组对其他队列的消费