本文解决:
- RabbitMQ消息者如何获取生产者设置的correlationId
- 获取到的CorrelationId为空
本文环境:
springboot 2.1.9.RELEASE + amqp-client-5.4.3.jar
本文分两部分,第一部分先直接给代码实现,第二部分进行原理解析。
实现代码
交换机、路由那些就自己改吧,附上全部代码很累赘,只说关键点
-
注册一个bean
@Bean public MessagePostProcessor correlationIdProcessor() { MessagePostProcessor messagePostProcessor = new MessagePostProcessor() { @Override public Message postProcessMessage(Message message, Correlation correlation) { MessageProperties messageProperties = message.getMessageProperties(); if (correlation instanceof CorrelationData) { String correlationId = ((CorrelationData) correlation).getId(); messageProperties.setCorrelationId(correlationId); } // 可以设置持久化,但与本文无关,因此没有附上 return message; } @Override public Message postProcessMessage(Message message) throws AmqpException { return message; } }; return messagePostProcessor; } -
生产者发送消息时,使用该bean作为参数
(除了作为参数传入,还可以全局设置,原理解析会讲)
// @Bean不指定name时,默认为方法名,此处就是注入上述bean @Autowired MessagePostProcessor correlationIdProcessor; public void sendMiaoshaMessage(String msg) { CorrelationData correlationData = gencorrelationData(); System.out.println("设置ID: " + correlationData.getId()); System.out.println("发送消息"); rabbitTemplate.convertAndSend(MQConfig.MIAOSHA_EXCHANGE, MQConfig.MIAOSHA_ROUTING_KEY,msg, // 作为参数 correlationIdProcessor, correlationData); } -
生产者接收消息(其实这步没有内容,只是配合展示一下代码)
@RabbitListener(queues = MQConfig.MIAOSHA_QUEUE) @RabbitHandler public void receiveMessage(String msg, Channel channel, Message messages) { System.out.println("收到消息"); MessageProperties messageProperties = messages.getMessageProperties(); String id = messageProperties.getCorrelationId(); System.out.println("收到Id: " + id); }
发送消息,控制台输出:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4DX8yrDe-1597109080396)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20200810231722863.png)]](/https://i-blog.csdnimg.cn/blog_migrate/ee6b093f946f99306a08be22adbfe450.png)
原理解析
下述所讲代码全都是
RabbitTemplate类里面的
核心自然是convertAndSend方法。本文不会附上全部代码,还是只解析关键代码。
先说句"废话", convertAndSend有多个重载方法,不设置对应的参数,均为null
-
convertAndSend方法源码:@Override public void convertAndSend(String exchange, String routingKey, final Object message, final MessagePostProcessor messagePostProcessor, @Nullable CorrelationData correlationData) throws AmqpException { Message messageToSend = convertMessageIfNecessary(message); messageToSend = messagePostProcessor.postProcessMessage(messageToSend, correlationData); send(exchange, routingKey, messageToSend, correlationData); }这里的核心就是:
messageToSend = messagePostProcessor.postProcessMessage(messageToSend, correlationData);所以如果没有传入
messagePostProcessor参数,自然这步就没有操作了所以这里需要先介绍
MessagePostProcessor接口,有两个方法@FunctionalInterface public interface MessagePostProcessor { Message postProcessMessage(Message message) throws AmqpException; default Message postProcessMessage(Message message, Correlation correlation) { return postProcessMessage(message); } }这里需要注意的是,
default方法中,参数是Correlation,而不是CorrelationData。CorrelationData是Correlation的实现类,不过该接口是个空接口,源码如下public interface Correlation { // 没有省略代码,就是空的 } public class CorrelationData implements Correlation { private volatile String id; //...省略 }到此,@Bean的代码基本就出来了:
- 向下强转,来获得
CorrelationData,通过getId()方法,获得设置好的correlationId - 把该Id,放到Message的Properties中:
messageProperties.setCorrelationId(correlationId);
- 向下强转,来获得
-
继续,到
send()方法,这里主要就是执行一个任务,笔者也不是很理解,就不赘述了。关键就是进入doSend方法 -
dosend方法:protected void doSend(Channel channel, String exchange, String routingKey, Message message, boolean mandatory, CorrelationData correlationData) throws Exception { Message messageToUse = message; MessageProperties messageProperties = messageToUse.getMessageProperties(); ... } // 这里就是rabbitTemplate全局设置的消息前置处理器,逻辑完全同作为参数传入的messagePostProcessor,逐个调用其postProcessMessage方法 // 因此除了传入参数外,也可以全局设置 if (this.beforePublishPostProcessors != null) { for (MessagePostProcessor processor : this.beforePublishPostProcessors) { messageToUse = processor.postProcessMessage(messageToUse, correlationData); } } // 与confirmCallBack相关。 // 这里也可以完成本文的目的,下文也会附上相关代码,但这样的处理使该方法超出了其作用范围,因此没有采用 setupConfirm(channel, messageToUse, correlationData); ... }先说明注释中提到的做法:
- 不通过传入参数,直接全局设置的做法:
// 是rabbitTemplate的成员变量 private volatile Collection<MessagePostProcessor> beforePublishPostProcessors; //所以全局设置代码如下,在设置rabbitTemplate时,直接设置 // (此处因为是在同一个configuration里面,所以直接调用了方法。 // 如果不在同一个类,一样的,自动注入再设置即可) rabbitTemplate.setBeforePublishPostProcessors(correlationIdProcessor());附:通过
setupConfirm方法内实现本文目的:先附上关键源码
private void setupConfirm(Channel channel, Message message, @Nullable CorrelationData correlationDataArg) { if ((this.publisherConfirms || this.confirmCallback != null) && channel instanceof PublisherCallbackChannel) { CorrelationData correlationData = this.correlationDataPostProcessor != null ? this.correlationDataPostProcessor.postProcess(message, correlationDataArg) : correlationDataArg; ... } //其中的this.correlationDataPostProcessor,也是一个成员变量 private volatile CorrelationDataPostProcessor correlationDataPostProcessor;所以道理也差不多,全局设置就完事了。
这种方法唯一的好处,就是它的参数直接就是
CorrelationData,不用转换rabbitTemplate.setCorrelationDataPostProcessor(new CorrelationDataPostProcessor() { @Override public CorrelationData postProcess(Message message, CorrelationData correlationData) { MessageProperties messageProperties = message.getMessageProperties(); messageProperties.setCorrelationId(correlationData.getId()); //这里可以处理correlationData,该data会在confirm时接收 correlationData.setId("经过处理的Id"); return correlationData; } });效果如下:(已经去掉了前面用的方法,单靠此处完成的)

但从条件可以看出,该方法是与生产者确认模式相关的,如果用要这种方式,就必须进行相关配置。
而这样去完成本文的目的,对该方法"超负荷"了。不推荐采用if ((this.publisherConfirms || this.confirmCallback != null) && channel instanceof PublisherCallbackChannel)
解析完毕,其实各种实现归根到底,关键就是把correlationId放到messageProperties里。
本文完,有误欢迎指出
本文介绍了如何在RabbitMQ中让消费者获取生产者设置的correlationId,针对获取到的CorrelationId为空的问题提供了解决方案。在SpringBoot 2.1.9.RELEASE和amqp-client-5.4.3.jar环境下,通过关键代码解析,展示了如何设置和获取correlationId,包括直接传参和全局设置两种方法,并分析了相关源码。同时,文中提醒了一种不推荐的过度使用生产者确认模式的方法。

1839

被折叠的 条评论
为什么被折叠?



