在使用RocketMQ的过程中,消费堆积问题是不可避免的问题。这次借机分享下如何系统思考和解决问题,各位方家指正。
我本地已经复现了该问题,这里以一个复现的app做分享和解释。主要包含
- 复现问题
- 问题描述
- 问题分析
- 翻看源码确认问题
- 解决问题
- 临时解决 --- 变通解
- 长期解决 --- 提PR彻底解
- 周边问题确认
本文涉及rocketmq dashboard代码、rocketmq admin tool代码、broker代码、client代码、client&broker心跳源码等,建议坐地铁时翻看,有问题随时留言。
1. 复现问题:这一步靠经验吧。
https://github.com/rocketmq101/spring-issue-405
2. 问题描述
用户在使用RocketMQ Spring 2.2.1的时候发现消费堆积,异常截图如下:
消费详情一直不变,并且始终消费不到queue 0, queue 1:
消费者实例:启动了一个push消费者,消费者client却有两个。
用户使用的代码:
用户配置:
3. 初步分析
某种原因导致启动了两个消费者实例,并且会导致订阅关系不一致。
可能性1:
根据以往的经验,订阅关系不一致,会导致全部的消费者在分配queue-消费者实例的时候出现混乱,导致订阅错乱,所以有的queue没有消费者消费。
此时重温下什么是订阅关系:在一个消费者组中,消费者组-topic-tag在每个消费者实例启动时必须保持一致。
经过排查,这个消费者订阅关系一致,排除掉。
可能性2:
Push消费者和Pull消费者同时存在。导致用户使用的Push消费者只消费到了一部分queue,另一部分由Pull消费者分配了,但是没有展示。
为什么这么猜测?
首先,源自于flink消费kafka的逻辑,在flink消费kafka时,kafka-manager上是看不到消费者的。flink消费kafka实际是pull的,猜测RocketMQ和kafka这里逻辑一样。
其次,RocketMQ队列分配逻辑:每个消费者将自己注册到broker中,启动reblance时,全部的queue按照一定的原则分配给全部的消费者实例。Pull和Push消费者是否一起当作消费者分配了不同的队列呢?
这个猜测最后通过排查rocketmq-dashboad、rocketmq broker、rocketmq client的代码后确认了此逻辑。
4. 源码翻看。
4.1 RocketMQ Spring源码确认问题
翻看github issue,可以找到:https://github.com/apache/rocketmq-spring/issues/450有相似问题。这个问题目前笔者已经提PR修复了,欢迎大家使用新版本的RocketMQ Spring。
翻看源码后,确认了一个恶心的逻辑。
恶心逻辑:如果rocketmq-spring配置项中包含了下面三个配置项时,会默认启动一个DefaultLitePullConsumer实例。
代码语言:javascript复制rocketmq.name-server=127.0.0.1:9876
rocketmq.consumer.group=test-group
rocketmq.consumer.topic=test-topic
总结:所以可以解释通现象中明明只配置了一个PushConsumer,但是启动了2个Client实例。
4.2 Broker4.9.3 源码,确认日志。
其实消费者在启动注册到Broker的时候, 会打印日志:
如果启动很久了,日志可能被删除了,重启下消费者就会看到。
5. 问题处理
如果处理呢?在配置RocketMQ Spring 时,只要以下三个配置项不同时存在就行了(这里吐槽Spring的条件注解,非常好用,也非常容易滋生暗病)
代码语言:javascript复制rocketmq.name-server=127.0.0.1:9876
rocketmq.consumer.group=test-group
rocketmq.consumer.topic=test-topic
一般处理到这里就完了。下面是我提的两个问题:
第一个问题:如何彻底解这个问题呢?
当然是提交代码,合并到社区,新版本会彻底修掉这个bug,大家也不会再遇到这个问题。
目前代码已经合并到社区,欢迎大家阅读指正:
https://github.com/apache/rocketmq-spring/pull/459。
这个修改的指导思想是:配置项功能需要简单、明确,一个配置项就干一个事情。
第二个问题:在消费详情中,为什么Pull消费者在Dashboard中不显示消费者client和queue的关系信息呢?
实际下图的空白中,是Pull消费者消费的,却没有consumerClient。
第三个问题:在消费者实例列表中,明明Pull和Push消费者的ConsumerType不一样,为什么这里只显示了CONSUME_ACTIVELY(Pull)
CONSUME_ACTIVELY为什么表示Pull,请看代码:
第二,三个问题将在下一篇继续介绍。