RabbitMQ消息的链路跟踪

背景

TraceId能标记一次请求的调用链路,在我们排查问题的时候十分重要。系统引入MQ后,MQ消息默认不带TraceId,所以消息发送和处理的链路就断了。下面分享如何对业务逻辑无感的方式,将TraceId带到消费端。

难点

RabbitMQ的Message对象可以在属性上设置头信息,所以携带TraceId的位置有了,问题是怎么无感的方式设置和获取TraceId?

Spring RabbitMQ拦截器

在Spring里使用RabbitMQ本身没有拦截器,但是有一个消息处理器,可以在发送和接收消息之前对消息进行处理。里面有3个重载的方法,对原始消息进行转换。我们可以借助这个处理器,在Message对象里加上TraceId。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public interface MessagePostProcessor { /** * Change (or replace) the message. * @param message the message. * @return the message. * @throws AmqpException an exception. */ Message postProcessMessage(Message message) throws AmqpException; /** * Change (or replace) the message and/or change its correlation data. Only applies to * outbound messages. * @param message the message. * @param correlation the correlation data. * @return the message. * @since 1.6.7 */ default Message postProcessMessage(Message message, Correlation correlation) { return postProcessMessage(message); } /** * Change (or replace) the message and/or change its correlation data. Only applies to * outbound messages. * @param message the message. * @param correlation the correlation data. * @param exchange the exchange to which the message is to be sent. * @param routingKey the routing key. * @return the message. * @since 2.3.4 */ default Message postProcessMessage(Message message, Correlation correlation, String exchange, String routingKey) { return postProcessMessage(message, correlation); } } |

发送消息时,携带TraceId

Spring默认使用RabbitTemplate来发送消息,RabbitTemplate的send方法在发送消息之前,会调用beforePublishPostProcessors来处理Message对象。beforePublishPostProcessors集合里存的就是MessagePostProcessor对象,它会在发消息之前执行:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public void doSend(Channel channel, String exchangeArg, String routingKeyArg, Message message, boolean mandatory, @Nullable CorrelationData correlationData) throws IOException { // ... // 核心代码 if (this.beforePublishPostProcessors != null) { for (MessagePostProcessor processor : this.beforePublishPostProcessors) { messageToUse = processor.postProcessMessage(messageToUse, correlationData, exch, rKey); } } // ... sendToRabbit(channel, exch, rKey, mandatory, messageToUse); // ... } |

我们要做的是在注册RabbitTemplate的时候加上处理TraceId的逻辑

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Bean public RabbitTemplate rabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(); configurer.configure(template, connectionFactory); template.setBeforePublishPostProcessors(new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { // 这个方法没调用,调用的是下面那个方法 return message; } @Override public Message postProcessMessage(Message message, Correlation correlation, String exchange, String routingKey) { String requestId = MDC.get(MdcConstants.REQUESTID); if (StringUtils.isEmpty(requestId)) { requestId = StringUtils.random(); } message.getMessageProperties().setHeader(RabbitMQConstants.HEADER_REQUESTID, requestId); return message; } }); return template; } |

消费消息时,获取TraceId

当我们通过@RabbitListener注册consumer时,Spring会通过org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.AsyncMessageProcessingConsumer#mainLoop方法,不断从consumer队列里拿到消息。在把消息交给@RabbitListener标注的对象前,也会对Message对象进行处理。这里的处理器是存在org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#afterReceivePostProcessors属性上。

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| private void doExecuteListener(Channel channel, Object data) { if (data instanceof Message) { Message message = (Message) data; if (this.afterReceivePostProcessors != null) { for (MessagePostProcessor processor : this.afterReceivePostProcessors) { message = processor.postProcessMessage(message); if (message == null) { throw new ImmediateAcknowledgeAmqpException( "Message Post Processor returned 'null', discarding message"); } } } // ... invokeListener(channel, message); } else { invokeListener(channel, data); } } |

怎么设置SimpleMessageListenerContainer#afterReceivePostProcessors的值?

SimpleMessageListenerContainer对象是由SimpleRabbitListenerContainerFactory工厂对象创建,在创建SimpleMessageListenerContainer对象时,会把工厂里的属性拷贝过来,afterReceivePostProcessors就是通过工厂拷过来。所以我们直接设置SimpleRabbitListenerContainerFactory的afterReceivePostProcessors值就可以。

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Bean(name = {"rabbitListenerContainerFactory"}) @ConditionalOnProperty( prefix = "spring.rabbitmq.listener", name = {"type"}, havingValue = "simple", matchIfMissing = true ) public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory, ObjectProvider<ContainerCustomizer<SimpleMessageListenerContainer>> simpleContainerCustomizer) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); configurer.configure(factory, connectionFactory); simpleContainerCustomizer.ifUnique(factory::setContainerCustomizer); factory.setAfterReceivePostProcessors(message -> { Object requestId = message.getMessageProperties().getHeader(RabbitMQConstants.HEADER_REQUESTID); if (StringUtils.isEmpty(requestId)) { requestId = StringUtils.random(); } MDC.put(MdcConstants.REQUESTID, String.valueOf(requestId)); return message; }); return factory; } |

这样,我们就能在@RabbitListener的消费逻辑里拿到TraceId。

相关推荐
敏叔V5878 小时前
联邦学习与大模型:隐私保护下的分布式模型训练与微调方案
分布式
短剑重铸之日9 小时前
《7天学会Redis》特别篇: Redis分布式锁
java·redis·分布式·后端·缓存·redission·看门狗机制
重生之绝世牛码11 小时前
Linux软件安装 —— kafka集群安装(SASL密码验证)
大数据·linux·运维·服务器·分布式·kafka·软件安装
填满你的记忆12 小时前
【从零开始——Redis 进化日志|Day5】分布式锁演进史:从 SETNX 到 Redisson 的完美蜕变
java·数据库·redis·分布式·缓存
無森~12 小时前
ZooKeeper
分布式·zookeeper·云原生
小北方城市网13 小时前
SpringBoot 集成 MinIO 实战(对象存储):实现高效文件管理
java·spring boot·redis·分布式·后端·python·缓存
Blossom.11813 小时前
大模型分布式训练通信优化:从Ring All-Reduce到分层压缩的实战演进
人工智能·分布式·python·深度学习·神经网络·机器学习·迁移学习
码农水水13 小时前
得物Java面试被问:大规模数据的分布式排序和聚合
java·开发语言·spring boot·分布式·面试·php·wpf
爱吃提升14 小时前
分布式爬虫的核心原理详细介绍
分布式·爬虫
無森~14 小时前
Hadoop序列化与反序列化
大数据·hadoop·分布式