RabbitMq学习(五)消息确认之发送确认

消息发送确认

生产者发送消息,是先发送消息到Exchange,然后Exchange再路由到Queue。这中间就需要确认两个事情,第一,消息是否成功发送到Exchange;第二,消息是否正确的通过Exchange路由到Queue。
spring提供了两个回调函数来处理这两种消息发送确认。

ConfirmCallback和ReturnCallback

  • 实现ConfirmCallback并重写confirm(CorrelationData correlationData, boolean ack, String cause)回调方法,可以确认消息是否发送到Exchange。

  • 实现ReturnCallback并重写returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey)回调方法,可以确认消息从EXchange路由到Queue失败。注意:这里的回调是一个失败回调,只有消息从Exchange路由到Queue失败才会回调这个方法。

  • 注意,若需要以上两个回调函数生效,需要添加配置
    配置文件:

    spring:
      rabbitmq:
        publisher-confirms: true
        publisher-returns: true
    

    配置类:

    /**
     * 配置
     * 
     * @return
     */
    @Bean
    public ConnectionFactory connectionFactory() {
    	CachingConnectionFactory connectionFactory = new CachingConnectionFactory("127.0.0.1", 5672);
    	connectionFactory.setUsername("admin");
    	connectionFactory.setPassword("123456");
    	connectionFactory.setPublisherConfirms(true);
    	connectionFactory.setPublisherReturns(true);
    	return connectionFactory;
    }
    

回调配置类

直接上代码,如下,直接实现ConfirmCallback和ReturnCallback

package com.xquant.rabbitmq.send.mq;

import javax.annotation.PostConstruct;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author chunhui.tan
 * @version 创建时间:2018年11月19日 下午2:32:37
 *
 */
@Component
public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback, ReturnCallback {

	@Autowired
	private RabbitTemplate rabbitTemplate;

	@PostConstruct
	public void init() {
		rabbitTemplate.setConfirmCallback(this);// 指定 ConfirmCallback
		rabbitTemplate.setReturnCallback(this);// 指定 ReturnCallback
	}

	/**
	 * 确认消息是否发送到Exchange
	 */
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		if (ack) {
			System.out.println("消息发送成功:" + correlationData);
		} else {
			System.out.println("消息发送失败:" + cause);
		}

	}

	/**
	 * 消息发送到Queue失败后的回调
	 */
	@Override
	public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
		System.out.println(message.getMessageProperties().getCorrelationIdString() + " 发送失败");
		System.out.println("消息主体 message : " + message);
		System.out.println("描述:" + replyText);
		System.out.println("消息使用的交换器 exchange : " + exchange);
		System.out.println("消息使用的路由键 routing : " + routingKey);
	}

}

测试1,消息发送到Exchange的回调

配置类,还是和之前一样,两个Queue,分别通过不同的bindingKey绑定同一个Exchange。

package com.xquant.rabbitmq.send.mq;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author chunhui.tan
 * @version 创建时间:2018年11月14日 下午1:29:21
 *
 */
@Configuration
public class DirectMqConfig {

	/**
	 * 交换机名称
	 */
	public static final String DIRECT_EXCHANGE_NAME = "direct_exchange";

	/**
	 * 绑定key,交换机绑定队列时需要指定
	 */
	public static final String BINGDING_KEY_TEST1 = "direct_key1";
	public static final String BINGDING_KEY_TEST2 = "direct_key2";

	/**
	 * 队列名称
	 */
	public static final String QUEUE_TEST1 = "test1";
	public static final String QUEUE_TEST2 = "test2";

	/**
	 * 构建DirectExchange交换机
	 * 
	 * @return
	 */
	@Bean
	public DirectExchange directExchange() {
		// 支持持久化,长期不用补删除
		return new DirectExchange(DIRECT_EXCHANGE_NAME, true, false);
	}

	/**
	 * 构建序列
	 * 
	 * @return
	 */
	@Bean
	public Queue test1Queue() {
		// 支持持久化
		return new Queue(QUEUE_TEST1, true);
	}

	@Bean
	public Queue test2Queue() {
		// 支持持久化
		return new Queue(QUEUE_TEST2, true);
	}

	/**
	 * 绑定交交换机和
	 * 
	 * @return
	 */
	@Bean
	public Binding test1Binding() {
		return BindingBuilder.bind(test1Queue()).to(directExchange()).with(BINGDING_KEY_TEST1);
	}

	@Bean
	public Binding test2Binding() {
		return BindingBuilder.bind(test2Queue()).to(directExchange()).with(BINGDING_KEY_TEST2);
	}

	/**
	 * 配置
	 * 
	 * @return
	 */
	@Bean
	public ConnectionFactory connectionFactory() {
		CachingConnectionFactory connectionFactory = new CachingConnectionFactory("127.0.0.1", 5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("123456");
		connectionFactory.setPublisherConfirms(true);
		connectionFactory.setPublisherReturns(true);
		return connectionFactory;
	}

	/**
	 * 实例化操作模板
	 * 
	 * @param connectionFactory
	 * @return
	 */
	@Bean
	public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
		return new RabbitTemplate(connectionFactory);
	}

}

生产者,方便起见,还是用接口

@RestController
public class ProducerController {

	@Autowired
	private RabbitTemplate rabbitTemplate;

	/**
	 * 给test1队列发消息
	 * 
	 * @return
	 */
	@GetMapping("/sendMessage1")
	public Object sendMessage1() {
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.setContentEncoding("UTF-8");
		Message message = new Message("你好,这是发给test1队列的消息".getBytes(), messageProperties);
		rabbitTemplate.send("testhuidiao", DirectMqConfig.BINGDING_KEY_TEST1, message);
		return "ok";
	}

	/**
	 * 给test2队列发消息
	 * 
	 * @return
	 */
	@GetMapping("/sendMessage2")
	public Object sendMessage2() {
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.setContentEncoding("UTF-8");
		Message message = new Message("你好,这是发给test2队列的消息".getBytes(), messageProperties);
		rabbitTemplate.send(DirectMqConfig.DIRECT_EXCHANGE_NAME, DirectMqConfig.BINGDING_KEY_TEST2, message);
		return "ok";
	}

消费者代码就不贴出了,就是分别监听两个消息队列的监听函数。
接下来测试,首先启动项目,观察rabbitweb页面如下:
在这里插入图片描述在这里插入图片描述
没有问题,然后我们调用sendMessage1接口,控制台打印如下:

2018-11-19 15:41:42.286  INFO 9292 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-19 15:41:42.286  INFO 9292 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-19 15:41:42.301  INFO 9292 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms
消息发送失败:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'testhuidiao' in vhost '/', class-id=60, method-id=40)
2018-11-19 15:41:42.440 ERROR 9292 --- [110.34.160:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'testhuidiao' in vhost '/', class-id=60, method-id=40)

可以看到触发了回调方法confirm(CorrelationData correlationData, boolean ack, String cause),而且发送消息到Exchange失败,ask==false。
调用sendMessage2接口,控制台打印如下:

2018-11-19 15:45:16.158  INFO 10804 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-19 15:45:16.159  INFO 10804 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-19 15:45:16.173  INFO 10804 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 14 ms
消息发送成功:null
这是监听test2得到的消息:======你好,这是发给test2队列的消息


可以看到触发了回调方法confirm(CorrelationData correlationData, boolean ack, String cause),而且发送消息到Exchange成功,ask==true。
我们在rabbit的Web页面删掉Exchange,如下图:
在这里插入图片描述
然后调用sendMessage2方法,控制台打印如下,触发失败回调

消息发送失败:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'direct_exchange' in vhost '/', class-id=60, method-id=40)
2018-11-19 15:49:06.089 ERROR 10804 --- [110.34.160:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'direct_exchange' in vhost '/', class-id=60, method-id=40)

测试2,消息从Exchange路由到Queue失败的回调

配置类和消费者代码都不变,然后我们修改生产者的代码如下,其实只是修改了sendMessage1接口中routingKey的名称为testadmin,而实际上是没有一个queue通过这个routingKey绑定到我们定义的Exchange上的

@RestController
public class ProducerController {

	@Autowired
	private RabbitTemplate rabbitTemplate;

	/**
	 * 给test1队列发消息
	 * 
	 * @return
	 */
	@GetMapping("/sendMessage1")
	public Object sendMessage1() {
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.setContentEncoding("UTF-8");
		Message message = new Message("你好,这是发给test1队列的消息".getBytes(), messageProperties);
		rabbitTemplate.send(DirectMqConfig.DIRECT_EXCHANGE_NAME, "testadmin", message);
		return "ok";
	}

	/**
	 * 给test2队列发消息
	 * 
	 * @return
	 */
	@GetMapping("/sendMessage2")
	public Object sendMessage2() {
		MessageProperties messageProperties = new MessageProperties();
		messageProperties.setContentEncoding("UTF-8");
		Message message = new Message("你好,这是发给test2队列的消息".getBytes(), messageProperties);
		rabbitTemplate.send(DirectMqConfig.DIRECT_EXCHANGE_NAME, DirectMqConfig.BINGDING_KEY_TEST2, message);
		return "ok";
	}

启动工程测试,调用sendMessage1接口,

2018-11-19 16:33:52.061  INFO 12300 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-19 16:33:52.062  INFO 12300 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-19 16:33:52.075  INFO 12300 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 13 ms
消息发送成功:null

很奇怪,明明没有路由到queue,但是从Exchange到Queue的失败回调还是没有触发。
通过百度找到原因:没有设置mandatory=true
关于mandatory的作用:
当mandatory标志位设置为true时,如果exchange根据自身类型和消息routingKey无法找到一个合适的queue存储消息,那么broker会调用basic.return方法将消息返还给生产者;当mandatory设置为false时,出现上述情况broker会直接将消息丢弃;通俗的讲,mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者

总之,如果要触发returnedMessage回调函数,我们必须设置mandatory=true,否则Exchange没有找到Queue就会丢弃掉消息,而不会触发回调。
具体设置方法:

spring:
  rabbitmq:
    template:
      mandatory: true
        


或者:

/**
	 * 实例化操作模板
	 * 
	 * @param connectionFactory
	 * @return
	 */
	@Bean
	public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
		RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
		rabbitTemplate.setMandatory(true);
		return rabbitTemplate;

	}

改为以上配置后我们测试sendMessage1接口,控制台打印如下:

2018-11-19 16:46:45.832  INFO 9376 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-19 16:46:45.832  INFO 9376 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-19 16:46:45.846  INFO 9376 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 14 ms
null 发送失败
消息主体 message : (Body:'[B@38005986(byte[39])' MessageProperties [headers={}, contentType=application/octet-stream, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])
描述:NO_ROUTE
消息使用的交换器 exchange : direct_exchange
消息使用的路由键 routing : adadasaa

可以看到描述信息是NO_ROUTE,没有路由。

总结

关于RabbitMQ消息发送确认,主要是从两个回调函数来确认。
首先配置如下信息:
yml文件:

spring: 
  rabbitmq:
    addresses: 127.0.0.1
    port: 5672
    username: admin
    password: 123456
    publisher-confirms: true #开启confirms回调函数
    publisher-returns: true #开启returnedMessage回调函数
    template:
      mandatory: true #必须为true,否则无法触发returnedMessage回调,消息丢失

配置类:

/**
	 * 配置
	 * 
	 * @return
	 */
	@Bean
	public ConnectionFactory connectionFactory() {
		CachingConnectionFactory connectionFactory = new CachingConnectionFactory("127.0.0.1", 5672);
		connectionFactory.setUsername("admin");
		connectionFactory.setPassword("123456");
		//开启confirms回调函数
		connectionFactory.setPublisherConfirms(true);
		//开启returnedMessage回调函数
		connectionFactory.setPublisherReturns(true);
		return connectionFactory;
	}

	/**
	 * 实例化操作模板
	 * 
	 * @param connectionFactory
	 * @return
	 */
	@Bean
	public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
		RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
		//必须为true,否则无法触发returnedMessage回调,消息丢失
		rabbitTemplate.setMandatory(true);
		return rabbitTemplate;

	}

然后编写一个类,实现ConfirmCallback, ReturnCallback这两个接口,在confirm(CorrelationData correlationData, boolean ack, String cause)和returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) 写对应的逻辑

具体如下:

package com.xquant.rabbitmq.send.mq;

import javax.annotation.PostConstruct;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author chunhui.tan
 * @version 创建时间:2018年11月19日 下午2:32:37
 *
 */
@Component
public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback, ReturnCallback {

	@Autowired
	private RabbitTemplate rabbitTemplate;

	@PostConstruct
	public void init() {
		rabbitTemplate.setConfirmCallback(this);// 指定 ConfirmCallback
		rabbitTemplate.setReturnCallback(this);// 指定 ReturnCallback
	}

	/**
	 * 确认消息是否发送到Exchange
	 */
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		if (ack) {
			System.out.println("消息发送成功:" + correlationData);
		} else {
			System.out.println("消息发送失败:" + cause);
		}

	}

	/**
	 * 消息发送到Queue失败后的回调
	 */
	@Override
	public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
		System.out.println(message.getMessageProperties().getCorrelationIdString() + " 发送失败");
		System.out.println("消息主体 message : " + message);
		System.out.println("描述:" + replyText);
		System.out.println("消息使用的交换器 exchange : " + exchange);
		System.out.println("消息使用的路由键 routing : " + routingKey);
	}

}

猜你喜欢

转载自blog.csdn.net/qq_38846242/article/details/84256383