问题回顾
这两天在RocketMQ的群里看到群友提出了一个这样的问题
刚开始因为工作有点忙,没有在意,后来工作空闲之余看了看这个问题,顿时有了兴趣,然后想了想,又对着源码梳理了一番,我怀疑他那边是一个producer发送了900个Topic,然后这900多个Topic共有同一个MQClientInstance,就导致每30s执行一次的定时任务持续的获取到lockNamesrv的锁,导致在有新增的Topic就会在获取路由信息的时候阻塞在lockNamesrv的锁上面,迟迟获取不到锁,出现这no route info这个异常。这个跟公平锁不公平锁关系不大,公平锁也是有几率获取不到锁的,只不过几率小一些。
在跟这个群友交流之后,发现他那边是有将近900个producer,然后这900多个producer发送不同的主题,但关键是共用一个MQClientInstance,所以问题就出现在了共有同一个MQClientInstance上面。下面我们就来解析一下为什么会导致出现这个问题吧。
问题梳理
MQClientInstance是什么
首先谈谈MQClientInstance是什么吧,他是RocketMQ的客户端的一个核心的组件,主要负责网络通信、消息的拉取以及消费者的重平衡,还有就是任务调度。
网络通信指的是创建MQClientInstance对象时,就会初始化Netty的客户端实例,并向其注册 Processor用于处理与服务端的交互。
消息的拉取以及消费者的重平衡是靠内部的pullMessageService和rebalanceService来实现的 任务。
任务调度指的是基于定时调度线程池去执行一些定时调度任务 比如发送心跳、持久化消费进度,还有就是更新路由信息,本篇重点分析这个调度任务。
源码解析MQClientInstance更新Topic路由信息的调度任务
客户端维护者需要发送的Topic路由信息,并且定时去更新路由信息,保持与Nameserver一致,定时任务是延迟10毫秒触发,然后每30s执行一次
updateTopicRouteInfoFromNameServer就是获取MQClientInstance关联生产者所有的Topic,然后for循环去更新路由信息,这个topicList就是900多个Topic
updateTopicRouteInfoFromNameServer重载的方法中有一个获取锁的动作,获得锁的线程才能去调用NameServer的服务更新路由信息,若此时Topic个数过多,导致锁挣用较为严重,确实在非公平锁的前提下,一个新的Topic到来,需要生产者主动去查询的时候,是无法快速获取到锁的,导致出现no route info这个异常。为什么说公平锁也不行呢,因为公平锁排队也可能导致新的Topic由生产者主动查询时排队超时。所以问题的关键不在这里,而是如何将这数量过多的Topic拆分到多个MQClientInstance中去。
其实按照现在较新版本的MQ客户端来说,MQClientInstance已经很难重复了,一般来说就是创建一个生产者,就会伴随着一个MQClientInstance,所以较新版本的客户端很难遇到这个问题了。
问题解答
关于MQClientInstance调度Topic问题,最后跟那个群友说了一下将MQClientInstance拆分为多个就应该可以规避这个问题了,而且不用担心过多的MQClientInstance与NameServer通信会导致NameServer负载过重,因为生产一般不会单节点,多节点会分摊压力而且NameServer本身工程不复杂,逻辑上大多数是纯内存操作,一般4C8G的机器配置就可以了。