一.概念
1.1消息的确认(Confirm):
是指生产者投递消息后,如果 Broker 收到消息,则会给我们生产者一个应答。生 产者进行接收应答,用来确定这条消息是否正常的发送到 Broker ,这种方式也是消息的可靠性投递的核心保障!
Confirm 确认机制流程图:
1.2 Return 消息机制
用于处理一些不可路 由的消息!
消息生产者,通过指定一个 Exchange 和 Routingkey,把消息送达到某一个队列中去,然后我们的消费者监听队列,进行消费处理操作!但是在某些情况下,如果我们在发送消息的时候,当前的 exchange 不存在或者指定的路由 key 路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用 Return !
Return 消息机制流程图:
二.rabbitmq-produce的改动
项目使用上一篇中的项目 rabbitmq-produce、rabbitmq-consumer
2.1 修改配置文件application.yml
代码如下:
server:
port: 8783
spring:
#配置程序名为rabbitmq-produce-learn
application:
name: rabbitmq-produce-learn
#配置rabbitMq 服务器
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
#虚拟host 可以不设置,使用server默认host
#virtual-host: JCcccHost
#支持发布确认
publisher-confirms: true
#支持发布返回
publisher-returns: true
template:
mandatory: true
listener:
direct:
prefetch: 0
#采用手动应答
acknowledge-mode: manual
simple:
prefetch: 0 #RabbitMQ 将消息顺序发送给多个消费者有两种模式(公平分发、轮询模式),
#区别在于公平分发的prefetch默认是1,如果设置为0就是轮询模式。
#确认模式
#采用手动应答
#none:不确认,不会发送任何ack
#manual:手动确认,发送端和客户端都需要手动确认
#auto:自动确认,就是自动发ack,除非抛异常。
acknowledge-mode: manual
2.2 在rabbitmq-produce中,新增一个RabbitComfirmAndReturnConfig配置类
RabbitComfirmAndReturnConfig的代码如下:
package com.example.rabbitmqproduce.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* rabbitmq
* comfirm 消息确认机制 和 Return 消息机制 配置类
* 消息的确认,是指生产者投递消息后,如果 Broker 收到消息,则会给我们生产者一个应答。生
* 产者进行接收应答,用来确定这条消息是否正常的发送到 Broker ,这种方式也是消息的可靠性投递的核心保障!
*
*
* Return 消息机制 用于处理一些不可路 由的消息!
*消息生产者,通过指定一个 Exchange 和 Routingkey,把消息送达到某一个队列中去,然后我们的消费者监听队列,进行消费处理操作!
*但是在某些情况下,如果我们在发送消息的时候,当前的 exchange 不存在或者指定的路由 key 路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用 Return !
*在基础API中有一个关键的配置项:Mandatory:如果为 true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为 false,那么 broker 端自动删除该消息!
*/
@Configuration
public class RabbitComfirmAndReturnConfig {
private Logger logger = LoggerFactory.getLogger(RabbitComfirmAndReturnConfig.class);
@Autowired
private CachingConnectionFactory connectionFactory;
public final static String ackQueueName = "ackQueue";
public static final String ackExchangeName = "ackExchange";
private static final String ackBindingKey = "ackRouting";
@Bean
public Queue ackQueue() {
return new Queue(ackQueueName);
}
@Bean
public DirectExchange ackExchange() {
return new DirectExchange(ackExchangeName);
}
@Bean
public Binding bindingExchangeAckMessage() {
return BindingBuilder.bind(ackQueue()).to(ackExchange()).with(ackBindingKey);
}
/**
* 定制化 RabbitTemplate 模版
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate() {
//若使用confirm-callback或return-callback,必须在配置文件yml 配置publisherConfirms或publisherReturns为true
connectionFactory.setPublisherConfirms(true);
connectionFactory.setPublisherReturns(true);
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
//必须设置为 true,不然当 发送到交换器成功,但是没有匹配的队列,不会触发 ReturnCallback 回调
//而且 ReturnCallback 比 ConfirmCallback 先回调,意思就是 ReturnCallback 执行完了才会执行 ConfirmCallback
rabbitTemplate.setMandatory(true);
//设置 ConfirmCallback 回调 配置文件yml需要配置 publisher-confirms: true
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
// 如果发送到交换器都没有成功(比如说删除了交换器),ack 返回值为 false
// 如果发送到交换器成功,但是没有匹配的队列(比如说取消了绑定),ack 返回值为还是 true (这是一个坑,需要注意)
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String messageId = correlationData.getId();
if (ack) {
logger.info("confirm:"+messageId);
logger.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause);
}else {
logger.info("confirm:"+messageId);
logger.info("消息发送失败:correlationData({}),ack({}),cause({})", correlationData, ack, cause);
}
}
});
//设置 ReturnCallback 回调 配置文件yml需要配置 publisher-returns: true
//如果发送到交换器成功,但是没有匹配的队列,就会触发这个回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
logger.info("message:"+message);
logger.warn("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message);
}
});
return rabbitTemplate;
}
}
2.3 新建消息生产者DirectExchangeProduce
DirectExchangeProduce的代码如下:
package com.example.rabbitmqproduce.produce;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 发送信息确认ack
* @Component 注入到Spring容器中
*/
@Component
public class RabbitAckProduce {
//注入一个 RabbitTemplate 来发布消息
@Autowired
private RabbitTemplate rabbitTemplate;
private Logger logger = LoggerFactory.getLogger(RabbitAckProduce.class);
private static final String ackRouteKey = "ackRouting";
private static final String ackExchangeName = "ackExchange";
/**
* 发送消息
*/
public void sendMessage() {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "hello!亚索 面对疾风吧";
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
Map<String,Object> map=new HashMap<>();
map.put("messageId",messageId);
map.put("messageData",messageData);
map.put("createTime",createTime);
//CorrelationData用于confirm机制里的回调确认
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
//将消息携带绑定键值:directRouting 发送到交换机directExchange
rabbitTemplate.convertAndSend(ackExchangeName,ackRouteKey, map,correlationData);
logger.info("mq消息发送结束==》{}", map.toString());
}
}
2.3 写个测试的TestController类
代码如下:
package com.example.rabbitmqproduce.controller;
import com.example.rabbitmqproduce.produce.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("TestController")
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
private RabbitMqProduce rabbitMqProduce;
@Autowired
private DirectExchangeProduce directExchangeProduce;
@Autowired
private FanoutExchangeProduce fanoutExchangeProduce;
@Autowired
private TopicExchangeProduce topicExchangeProduce;
@Autowired
private RabbitAckProduce rabbitAckProduce;
/**
* 测试基本消息模型(简单队列)
*/
@RequestMapping(value = "/testSimpleQueue", method = RequestMethod.POST)
public void testSimpleQueue() {
logger.info("测试基本消息模型(简单队列)SimpleQueue---开始");
for (int i = 0; i < 10; i++) {
rabbitMqProduce.sendMessage();
}
logger.info("测试基本消息模型(简单队列)SimpleQueue---结束");
}
/**
* 测试 Direct-exchange模式
*/
@RequestMapping(value = "/directExchangeTest", method = RequestMethod.POST)
public void directExchangeTest() {
logger.info("测试 Direct-exchange模式 队列名为directQueue---开始");
for (int i = 0; i < 10; i++) {
directExchangeProduce.sendMessage();
}
logger.info("测试 Direct-exchange模式 队列名为directQueue---结束");
}
/**
* 测试 Fanout-exchange模式
*/
@RequestMapping(value = "/fanoutExchangeTest", method = RequestMethod.POST)
public void fanoutExchangeTest() {
logger.info("测试 fanout-exchange模式 队列名为fanoutQueue---开始");
fanoutExchangeProduce.sendMessage();
logger.info("测试 fanout-exchange模式 队列名为fanoutQueue---结束");
}
/**
* 测试 Topic-exchange模式 topicA 和 topicB
*/
@RequestMapping(value = "/topictExchangeTest", method = RequestMethod.POST)
public void topictExchangeTest() {
logger.info("测试 topict-exchange模式 队列名为topictQueueNameA---开始");
topicExchangeProduce.sendMessageTopicA();
logger.info("测试 topict-exchange模式 队列名为topictQueueNameA---结束");
logger.info("测试 topict-exchange模式 队列名为topictQueueNameB---开始");
topicExchangeProduce.sendMessageTopicB();
logger.info("测试 topict-exchange模式 队列名为topictQueueNameB---结束");
}
/**
* 测试 ack模式
*/
@RequestMapping(value = "/ackTest", method = RequestMethod.POST)
public void ackTest() {
logger.info("测试 ack 队列名为ackQueue---开始");
rabbitAckProduce.sendMessage();
logger.info("测试 ack 队列名为ackQueue---结束");
}
}
三.rabbitmq-consumer的改动
3.1 新建消息消费者RabbitAckConsumer类
RabbitAckConsumer代码如下:
package com.example.rabbitmqconsumer.consumer;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
/**
* 收到信息确认ack
* @RabbitListener(queues = "ackQueue") 监听名为ackQueue的队列
*/
@Component
@RabbitListener(queues = "ackQueue")
public class RabbitAckConsumer {
@Autowired
private RabbitTemplate rabbitTemplate;
private Logger logger = LoggerFactory.getLogger(RabbitAckConsumer.class);
/**
* 消费消息
* @RabbitHandler 代表此方法为接受到消息后的处理方法
*/
@RabbitHandler
public void receiveMessage(Map msg , Message message , Channel channel) throws IOException {
// 采用手动应答模式, 手动确认应答更为安全稳定
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
logger.info("接收到的消息:---->" + msg.toString());
}
}
四.测试
首先启动生产者rabbitmq-produce项目。在postman或浏览器上访问:
http://localhost:8783/TestController/ackTest POST请求
这时可以在rabbitmq-produce的控制台可以看到
然后再启动消费者rabbitmq-consumer工程,在rabbitmq-consumer可以看到: