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

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章