由No route info for this topic引发的关于RocketMQ的问题

背景

昨天一位业务的同学在我审批MQ通过后,在业务代码里加了个 producer,结果启动项目时集成的调度中心的二方包里的 producer 在通过 MQ 注册 JOB 时报 No route info for this topic。

思路

这个异常也算比较常见了,一般是没有创建 topic,或者连接错了 namesrv 导致。但是查看了他的代码后发现配置没有问题。然后他反馈他 DEBUG 出来 producer 里的 namesrv 是正确的,然后他自己新加的。这就很奇怪了,于是我让他提交了代码然后申请了代码权限看下原因。

解决

这里先说一句,我对 BUG 这种东西,一直都相信一句话,99%你碰到觉得无比高深的 BUG,基本都是非常简单的点弄错了导致的。这次果然还是如此。

一开始我都没去看配置的问题,因为从业务方之前截图给我的配置里是没问题的。我们对配置的引用有两种方式,一种是 @key@,另外一种是 ${key}。截图给我的时候用的是 @key@,我想当然觉得这应该也是能正常拿到 value 的。

于是开始DEBUG,发现他配置的 @key@ 居然没引用到 value,直接把 @key @作为 namesrv 传进去了,如下图所示。

image-20200617094655941

但是思考一下,又有问题了。按正常的思维,namesrv配置难道不是应该跟着 producer 吗。难道 rocketMQ 在同一个 JVM 中只允许连一个 namesrv?如果是这样,那是配置的覆盖还是单例来实现呢?

于是继续看源码。

异常是在这行抛出

image-20200617101631909

抛出的原因是下面这行判断不通过。

1
topicPublishInfo != null && topicPublishInfo.ok()

继续追踪下去,可以看到是这个 Map 中 topic 对应的 topic 路由信息不存在。

image-20200617102300534

那么这个Map又是什么时候写入信息的呢。其实是在下图这个地方。

image-20200617102825025

上面那个写入路由信息的方法又是在这个地方调用的。注意,这个方法是 MQClientInstance 里的,不知道大家看到 Instance 这个类名结尾有没有啥感觉。我是看到这个基本就往单例或者跟某个 key 绑定这方面去想。那么继续看 MQClientInstance 是怎么维护的。

image-20200617103020755

MQClientInstance 存储和生成的地方如下面两张图所示。

image-20200617103404096

image-20200617103518874

可以看到是在这个地方做了个绑定,那么继续往下看。生成了 MQClientInstance 并以下图里的代码的结果为 key 放入

image-20200617103630933

那真相就出现了,同一个 JVM 中如果配置的时候不配 unitName,那么不管配多少个 producer 都只会有一个 MQClientInstance,相当于 config 也就生效一份 😁。基本就看哪个 producer先初始化了。后面来的只能拿之前的那个 MQClientInstance 来获取路由信息。

再回到之前最开始的问题,业务方后面配置的 producer 是用 xml 配置的 bean,而调度中心的 producer 他们使用时在重写了 afterPropertiesSet 方法里初始化。理论上肯定是 spring 自身维护的 bean 初始化在 afterPropertiesSet 之前。然而xml里配置的value恰好又是错误的,就导致了 No route info for this topic 出现了。将 namesrv 的引用改为正确的,问题解决。

后续在 vdianmq 里可以以 warn log 的形式提醒下业务方这方面使用时需要注意一下。