贞炸了!上线之后,消息收不到了!

hello,各位小伙伴们,上午好~

昨晚生产系统机房切换,又度过了一个不眠之夜。趁着这段无聊时间,分享一下前一段时间 RocketMQ 踩坑经历

太惨了!!!早上刚躺下睡了两小时,就被一通电话侥幸起来查看问题。

前言

事情是这样的,前端时间我们有个新业务上线,这个业务需要监听支付成功的 mq 消息,然后向绑定的音箱推送消息。这样用户在支付完成之后,商家端就就可以收到收款播报。

起初我们在测试环境的测试的时候,一切流程非常顺利,没有任何问题。但是等到我们发布上线之后,却出现了问题。

一笔支付成功之后,音箱没有发出收款成功的播报。一切流程排查下来之后,这才发现原来 MQ 消费端没有正常在消费消息。

开始排查问题,第一想到的是消费端是不是发布失败了,但是查看相关日志,并没有任何异常。

登录 MQ 控制台,尝试手动重新发布消息,神奇的事来了,消费端成功收到消息

总结现在的问题,下文开始排查。

  1. MQ 消费端应用没有异常,但是无法正常消费
  2. 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 端口。

  1. 代码主动禁止使用 vip 端口,配置如下:
## 消费端
DefaultMQPushConsumer#setVipChannelEnabled(false)
## 生产端
DefaultMQProducer#setVipChannelEnabled(false);
  1. 设置 JVM 参数,禁用 vip 端口
-Dcom.rocketmq.sendMessageWithVIPChannel=false

源码分析

虽然问题解决了,但是上述问题本质原因还没有找到。所以这次我们就从源码出发,追本溯源。

为什么 vip 端口网络不通将会导致消费者不能正常消费?

从 rocketmq 错误日志,我们可以看到报错代码位于 RebalanceService 类中。

这里主要用来执行 topic Rebalance(重平衡)。

首先我们来了解一下,Rebalance 目的是为什么了。

假设当前 rocketmq broker 端存在一个 topic ,拥有四个队列,关系如下:

此时如果有一个消费者使用集群模式消费消息,那么它将需要负责消费所有队列中的消息。

rocketmq 消费者-第 1 页 的副本

当我们再增加一个消费者消费消息时,此时消费端将会自动进行重平衡,默认情况下将会使用平均分配原则。

可以看到 Rebalance 机制可以提升的消息的并行处理机制。

rocketmq 消费端启动时竟会触发 Rebalance 机制。接着,我们根据源码主要看下 Rebalance 主流程,代码位于RebalanceImpl#rebalanceByTopic

通常我们使用集群消费模式,所以这里主要看集群模式下 Rebalance 过程。

上述代码整体流程如下:

  1. 首先获取 Rebalance 过程所需元数据,包括 Topic 下的队列信息集合以及消费者组下的消费者实例 id 信息集合
  2. 两者都存在的情况下,将会按照一定策略将队列信息分配给每个消费者,默认按照 AllocateMessageQueueAveragely,即平均分配原则
  3. 将预分配结果尝试更新 ProcessQueue Table,如果有更新将会把新的队列在加入异步消费流程。

后续消息流程就不看源码,比较复杂,网上找了一张消息消费流程图:

来自:https://blog.csdn.net/binzhaomobile/article/details/75004190

可以看到,由于网络端口问题,无法正常获取所有消费者 ID 集合,这就导致无法正常分配队列信息。

 List<String> 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/rocketmq/issues/1510

普通的端口将会承载所有消息网络请求,如果此时请求非常繁忙,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

posted @ 2020-11-24 08:39  楼下小黑哥  阅读(2351)  评论(6编辑  收藏  举报