生产者发送消息,是先发送消息到Exchange,然后Exchange再路由到Queue。这中间就需要确认两个事情,第一,消息是否成功发送到Exchange;第二,消息是否正确的通过Exchange路由到Queue。
spring提供了两个回调函数来处理这两种消息发送确认。
实现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);
}
}
配置类,还是和之前一样,两个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(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(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(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(reply-code=404, reply-text=NOT_FOUND - no exchange 'direct_exchange' in vhost '/', class-id=60, method-id=40)
配置类和消费者代码都不变,然后我们修改生产者的代码如下,其实只是修改了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);
}
}