目录
背景
问题排查
问题发现
问题分析
问题解决
解决方案
建议
总结
业务团队使用的RocketMQ集群是我们中间件组搭建的,使用的客户端是微服务组基于原生的RocketMQ客户端封装的xcloud-rocketmq
业务团队在项目上线之后,反馈线上程序一直报错:
1、看到业务团队这个表述,让我理解为线上集群存在问题,业务方topic在console创建成功了,但是发送消息时失败了,为了确保线上集群没问题,我先创建了一个测试的topic,写一个Java客户端进行发送消息,发现一切正常;
2、怀疑是客户端的问题,因为我使用的是原生的生产者,业务方使用的是微服务组封装的xcloud客户端,于是我使用了和业务方相同的客户端,测试结果仍然为正常,也没有报任何警告日志。
所以,用排除归纳法,能确定RocketMQ服务端和客户端都是正常的,不一样的是和业务方的使用方式、配置等。
经过交流,发现业务方配置了RocketMQ客户端的日志,将RocketMQ客户端的日志也输出到了业务日志中,导致ELK采集到的业务日志一直打印RocketMQ客户端的异常信息,也就是说这个一直重复打印的日志,是一个警告日志,是RocketMQ客户端原生的日志。
查看了源码,发现RocketMQ客户端确实会默默将日志打印到一个目录:
我查看了一下本机的~/logs/rocketmqlogs目录,确实有日志文件
看到了完整的异常日志信息之后,发现一个重要的信息:No topic route info in name server for the topic:RMQ_SYS_TRACE_TOPIC
RMQ_SYS_TRACE_TOPIC:这个topic是broker开启消息跟踪之后,系统自动创建的,但是我没有开启呀,按理说是不应该有这个topic的异常信息的;
TBW102:这个topic是Broker启动时,当autoCreateTopicEnable的配置为true时,会自动创建该默认topic,当生产者发送消息的topic没有创建时,会先路由到这个默认的topic,然后再继承该topic的配置,创建新的topic,但是我也没有开启自动创建topic;
所以,按照日志的分析来看,问题总结如下:
客户端会定时更新topic的路由信息,以保证客户端生产消费的正确性,此时服务端发现客户端请求了一个未知的topic:RMQ_SYS_TRACE_TOPIC,这个topic通过console上查找确认了,查无此topic,按照RocketMQ路由不到topic的逻辑,此时TBW102就出现了,由于没有开启自动创建topic,TBW102也不存在,这就导致了这个死循环,那么问题来了:为什么服务端没有RMQ_SYS_TRACE_TOPIC这个topic,但是客户端会报这个异常?
带着疑问,来看追踪报错信息的源头,发现客户端更新topic列表的时候,不是直接从服务端获取topic列表信息,而是先查询所有的Consumer和Producer集合,再通过Consumer和Producer来获取所关联的topic,代码如下:
为什么会这么做,不像Kafka一样直接获取topic列表呢?猜想是Kafka用zk来存储元数据信息,能保证数据的强一致性,而RocketMQ无法保证,只能以客户端自己缓存的Consumer和Producer作为依据,来查所路由的topic信息。
为什么服务端没有RMQ_SYS_TRACE_TOPIC这个topic,但是客户端会报这个异常?
猜测:
1、有系统或用户的Consumer或Producer,关联到了RMQ_SYS_TRACE_TOPIC这个topic;
2、用户开启了消息追踪,设置了消息追踪的参数为true,从而导致客户端获取到的topic列表包含了这个topic,而服务端没有,就一直循环报错。
首先看官方的issues,也有人提出这个问题:
https://github.com/apache/rocketmq/issues/2938
官方的解释是:RocketMQ在4.4.0之后,支持消息追踪,如果客户端设置了消息追踪为true,需要在broker的配置文件中设置tracetopicEnable=true
这也符合我的猜想,应该是有客户端在构造Producer时,将tracetopicEnable参数传了true,所以需要传false或不传,或者将服务端配置修改为支持消息追踪。
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("zhurunhua", false);
producer.setNamesrvAddr("172.20.10.42:9976;172.20.10.43:9976");
producer.start();
long l = System.currentTimeMillis();
try {
Message msg = new Message("test_topic",
"TagA",
"OrderID188",
"Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.send(msg);
System.out.printf("cost:%s-->%s%n", (System.currentTimeMillis() - l), sendResult);
} catch (Exception e) {
log.error("", e);
}
producer.shutdown();
}
}
但是查看的业务方的代码,并没有将消息追踪设置为true
@Component
@Slf4j
public class MqComp {
@Resource
private RocketMQTemplate rocketMqTemplate;
public SendResult syncSendOrderly(String destination, Message> message, String hashKey) {
return rocketMqTemplate.syncSendOrderly(destination, message, hashKey);
}
public void send(String destination, Message> message) {
rocketMqTemplate.send(destination, message);
}
public void asyncSend(String destination, Message> message, SendCallback sendCallback, long timeout) {
rocketMqTemplate.asyncSend(destination, message, sendCallback, timeout);
}
}
于是我猜测,微服务组封装的客户端,默认将消息追踪打开了,果不其然:
所以需要联系微服务组将该参数改为可配置的,或者修改broker配置,将traceTopicEnable设置为true。
由于RocketMQ客户端本身的日志也很庞大,每隔几秒就会刷新路由信息,可以将RocketMQ客户端的日志级别调低,来看源码是如何制定的:
所有可以在配置文件中指定日志级别和路径:
rocketmq:
client:
logRoot: ./logs/rmq
logLevel: ERROR
logFileName: rocketmq-client.log
出现该问题的根本原因在于:服务端不支持消息追踪的情况下,客户端设置enableMsgTrace=true
由于服务端和客户端分属两个团队负责,并且业务方阐述问题并不是很明确,所以问题排查过程有些困难,需要熟悉客户端的配置及使用,便于以后更快定位问题