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。

相关推荐
ly76894 小时前
Redis 分布式锁的边界条件:Redlock 争议、锁续期与客户端崩溃后的互斥失效
数据库·redis·分布式·分布式锁·watchdog·redlock
灯澜忆梦5 小时前
【minio】#5 | MinIO 分布式部署 + HTTPS 部署
分布式·网络协议·https·对象存储·minio
谢亮_vipxieliang15 小时前
Spring Cloud 服务治理入门:注册发现、配置中心、网关限流、熔断降级与分布式一致性方案
分布式·spring·spring cloud
樱花落木兰1 天前
分布式登录实战:Session 会话共享改造,Redis 存储用户登录状态
java·javascript·数据库·redis·分布式·缓存
ShineWinsu1 天前
对于Redis:Steam、Geospatial、Hyperloglog、Bitmap、Bitfield类型的解析
数据库·c++·redis·分布式·缓存·面试·zset
Flynt2 天前
Redis Cluster主节点挂了,为什么"高可用"还全员掉线?我把三次kill的记录翻出来了
数据库·redis·分布式
JosieBook2 天前
【数据库】MySQL 实战精通系列 · 第10篇:分库分表与分布式事务实战
数据库·分布式·mysql
梦帮科技2 天前
vLLM / TensorRT-LLM 极限推理:PagedAttention 细粒度物理页表管理与连续批处理(Continuous Batching)实战
数据结构·人工智能·分布式·python·深度学习·算法·vllm
Frank_refuel2 天前
分布式RPC框架实战(二):服务端模块划分与底层设计
分布式·网络协议·rpc
mftang3 天前
EtherCAT协议:从“飞读飞写”机制到分布式时钟同步的实时以太网架构深度解析
分布式·架构·ethercat·分布式时钟·从站控制器