hello,各位小伙伴们,上午好~
昨晚生产系统机房切换,又度过了一个不眠之夜。趁着这段无聊时间,分享一下前一段时间 RocketMQ 踩坑经历
太惨了!!!早上刚躺下睡了两小时,就被一通电话侥幸起来查看问题。
前言
事情是这样的,前端时间我们有个新业务上线,这个业务需要监听支付成功的 mq 消息,然后向绑定的音箱推送消息。这样用户在支付完成之后,商家端就就可以收到收款播报。
起初我们在测试环境的测试的时候,一切流程非常顺利,没有任何问题。但是等到我们发布上线之后,却出现了问题。
一笔支付成功之后,音箱没有发出收款成功的播报。一切流程排查下来之后,这才发现原来 MQ 消费端没有正常在消费消息。
开始排查问题,第一想到的是消费端是不是发布失败了,但是查看相关日志,并没有任何异常。
登录 MQ 控制台,尝试手动重新发布消息,神奇的事来了,消费端成功收到消息。
总结现在的问题,下文开始排查。
- MQ 消费端应用没有异常,但是无法正常消费
- MQ 控制台发送消息,消费端可以成功消费消息
排查问题
刚开始排查的时候,由于没有任何异常业务日志可以定位问题,所以问题排查起来十分困难。
排查了两天了,想过各种问题。比如当前消费端使用 RocketMQ 客户端版本比较高,是不是版本兼容性导致的问题呢?
于是降低消费端的版本,重新发布之后,问题依然存在。
没办法,只好使用 Google 大法了。
通过搜索发现,原来默认情况下 rockmq 客户端的日志将会单独打印输出,日志文件位置如下:
${user.home}/logs/rocketmqlogs
下图为当时的日志截图:
可以看到消费端尝试连接一个 20878 的端口,但是由于网络问题,一直连接失败。
那这个 20878 是什么端口?
我们并没有主动配置这个端口,但是 rocketmq broker 配置的端口为 20880。
搜索发现,原来 rocketmq broker 默认将会启动三个通讯端口:
第一个是 rocketmq broker 配置文件上配置的端口,默认端口为 10911,这里我们修改成了 20880。
第二个是 rockemq broker vip 通道端口,这个端口将会在第一个端口基础上减 2,即 20878。
第三个是 rockemq broker 用户主从数据同步的端口,这个端口将会在第一个端口基础上加 1,即 20881。
大概知道问题,解决办法就很简单了,要么防火墙打开 29878 网络端口的限制,要么关闭使用 vip 端口。
RocketMQ 客户端提供两种方式关闭使用 vip 端口。
- 代码主动禁止使用 vip 端口,配置如下:
## 消费端
DefaultMQPushConsumer#setVipChannelEnabled(false)
## 生产端
DefaultMQProducer#setVipChannelEnabled(false);
- 设置 JVM 参数,禁用 vip 端口
-Dcom.rocketmq.sendMessageWithVIPChannel=false
源码分析
虽然问题解决了,但是上述问题本质原因还没有找到。所以这次我们就从源码出发,追本溯源。
为什么 vip 端口网络不通将会导致消费者不能正常消费?
从 rocketmq 错误日志,我们可以看到报错代码位于 RebalanceService
类中。
这里主要用来执行 topic Rebalance(重平衡)。
首先我们来了解一下,Rebalance
目的是为什么了。
假设当前 rocketmq broker 端存在一个 topic ,拥有四个队列,关系如下:
此时如果有一个消费者使用集群模式消费消息,那么它将需要负责消费所有队列中的消息。
当我们再增加一个消费者消费消息时,此时消费端将会自动进行重平衡,默认情况下将会使用平均分配原则。
可以看到 Rebalance
机制可以提升的消息的并行处理机制。
rocketmq 消费端启动时竟会触发 Rebalance
机制。接着,我们根据源码主要看下 Rebalance
主流程,代码位于RebalanceImpl#rebalanceByTopic
。
通常我们使用集群消费模式,所以这里主要看集群模式下 Rebalance
过程。
上述代码整体流程如下:
- 首先获取 Rebalance 过程所需元数据,包括 Topic 下的队列信息集合以及消费者组下的消费者实例 id 信息集合
- 两者都存在的情况下,将会按照一定策略将队列信息分配给每个消费者,默认按照
AllocateMessageQueueAveragely
,即平均分配原则 - 将预分配结果尝试更新
ProcessQueue Table
,如果有更新将会把新的队列在加入异步消费流程。
后续消息流程就不看源码,比较复杂,网上找了一张消息消费流程图:
可以看到,由于网络端口问题,无法正常获取所有消费者 ID 集合,这就导致无法正常分配队列信息。
List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
由于未被分配任一队列,消费端程序也就业务无法正常拉取消息。
为什么 mq 控制台重新发送的消息消费者可以收到?
rocketmq 控制台重新发送消息代码如下:
MessageService
将会把消息的元数据封装一个CONSUME_MESSAGE_DIRECTLY
类型的请求,接着调用 rocketmq 提供的 admin API,给 rocketmq broker 发送请求。
broker 端收到请求之后,将会查询消息,然后再向消费端发起 CONSUME_MESSAGE_DIRECTLY
请求。消费端接受到消息请求之后,将会直接消息这条消息。
为什么 broker 将会启动两个端口?
rocketmq broker 虽然启动了两个端口,但是从 rocketmq broker 的源码可以发现这两个端口启动之后起到作用是一样的。
那为什么开启两个监听端口那?我想很多同学应该也有这个疑惑,这里给出一个开发者解释答案。
https://github.com/apache/roc...
普通的端口将会承载所有消息网络请求,如果此时请求非常繁忙,broker 端的所有 I/O 线程可能都在执行请求,这就会导致后续网络请求进入队列,从而导致消息请求执行缓慢。
这对于生产者来说,可能是一个致命的问题,因为消息生产者通常消息发送延时要低。
这种情况下,我们就可以将消息发送到 VIP 端口,从而降低消息发送的延时。
默认情况下,rocketmq 客户端的 vipChannel
配置为 true
。
private boolean vipChannelEnabled = Boolean.parseBoolean(System.getProperty(SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "true"));
生产者的发送消息,消费者获取元数据信息等请求默认将会使用 vip
端口。
不过这里需要注意一点,消费者拉取消息,将不会使用vip
端口。
虽然这个设计很巧妙,但是说实话个人觉得这个配置权限应该交给开发者自己去配置,而不是默认开启。
因为不熟悉的情况下还是很容易踩坑的,默认情况下,大家应该只熟悉 9876 与 10911 这两个端口。
rocketmq 4.5.1 版本之后,vipChannel
配置被修改为 false
,这时是否使用 vip 端口真正交给开发者自己
如果此时想开启,需要主动 API 参数,或者 JVM 参数增加 -Dcom.rocketmq.sendMessageWithVIPChannel=true
总结
今天的问题主要由于 VIP 端口无法连接,从而导致消费端无法正常消费消息。虽然最后的解决办法非常简单,但是这个排查过程真的很难。
我们平常在使用 rocketmq 过程中,通常只要设置 nameserver 的配置即可, broker 等地址信息将会自动从 nameserver 获取。这就间接导致了,我们可能只了解 9876 这个端口。
生产环境由于网络安全问题,一般不会开放全部的端口。所以,我们在使用 rocketmq 的过程,需要了解以下四个端口,分别为(默认配置):
- 9876:nameserver 监听端口
- 10911: broker 监听端口
- 10909:broker vip 监听端口
- 10912:broker HA 端口,用于主从同步
生产使用 rocketmq 过程,如果碰到诡问题,不妨尝试 telnet 看下网关连通性。另外还可以通过查看 rocketmq 自身日志,确定问题,日志位置位于:
${user.home}/logs/rocketmqlogs
好了,今天文章就到这里。我是楼下小黑哥,你知道的越多,你不知道的就越多。
下周见~
欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客: studyidea.cn