RabbitMQ 消费者如何获取生产者设置的correlationId + 得到CorrelationId为空的解决方案 + 源码解析

本文解决:

  • RabbitMQ消息者如何获取生产者设置的correlationId
  • 获取到的CorrelationId为空

本文环境:

springboot 2.1.9.RELEASE + amqp-client-5.4.3.jar

本文分两部分,第一部分先直接给代码实现,第二部分进行原理解析。

实现代码

交换机、路由那些就自己改吧,附上全部代码很累赘,只说关键点

  1. 注册一个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;
    }
    
  2. 生产者发送消息时,使用该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);
        }
    
  3. 生产者接收消息(其实这步没有内容,只是配合展示一下代码)

    @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);
    }
    

发送消息,控制台输出:

RabbitMQ 消费者如何获取生产者设置的correlationId + 得到CorrelationId为空的解决方案 + 源码解析_第1张图片

原理解析

下述所讲代码全都是RabbitTemplate类里面的

核心自然是convertAndSend方法。本文不会附上全部代码,还是只解析关键代码。

先说句"废话", convertAndSend有多个重载方法,不设置对应的参数,均为null

  1. 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

    CorrelationDataCorrelation的实现类,不过该接口是个空接口,源码如下

    public interface Correlation {
    	// 没有省略代码,就是空的
    }
    
    public class CorrelationData implements Correlation {
        private volatile String id;
        //...省略
    }
    

    到此,@Bean的代码基本就出来了:

    • 向下强转,来获得CorrelationData,通过getId()方法,获得设置好的correlationId
    • 把该Id,放到Message的Properties中:messageProperties.setCorrelationId(correlationId);
  2. 继续,到send()方法,这里主要就是执行一个任务,笔者也不是很理解,就不赘述了。关键就是进入doSend方法

  3. 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;
             }
    });
    

    效果如下:(已经去掉了前面用的方法,单靠此处完成的)
    RabbitMQ 消费者如何获取生产者设置的correlationId + 得到CorrelationId为空的解决方案 + 源码解析_第2张图片

    但从条件可以看出,该方法是与生产者确认模式相关的,如果用要这种方式,就必须进行相关配置。
    而这样去完成本文的目的,对该方法"超负荷"了。不推荐采用

    if ((this.publisherConfirms || this.confirmCallback != null) 
     && channel instanceof PublisherCallbackChannel) 
    

解析完毕,其实各种实现归根到底,关键就是correlationId放到messageProperties里。

本文完,有误欢迎指出

你可能感兴趣的:(RabbitMQ,java,rabbitmq)