承接上文
Springboot 整合RabbitMQ_Maplelhw的博客-CSDN博客
开启消息手动确认
yaml文件:
spring:
rabbitmq:
host: 服务器地址
port: 5672
username: admin
password: 123
listener:
direct:
acknowledge-mode: manual # 开启手动确认
publisher-confirm-type: correlated # 开启异步确认机制
生产者代码:
// 开启消息确认
@GetMapping("/confirmMsg/{message}")
public void confirmMsg(@PathVariable String message){
// 配置confirm机制
RabbitTemplate.ConfirmCallback confirmCallback = (correlationData,b,error) -> {
if (b){
System.out.println("confirm 消息发送成功...消息ID为:" + correlationData.getId());
}else {
System.out.println("confirm 消息发送失败...消息ID为:" + correlationData.getId() + "原因:"+ error);
}
};
// 设置confirm
rabbitTemplate.setConfirmCallback(confirmCallback);
// 参数为 交换机 路由键 消息 唯一消息id
rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE_NAME,DirectConfig.DIRECT_B_ROUTING_KEY,
message,new CorrelationData(""+System.currentTimeMillis()));
}
消费者代码
// 消费消息
@RabbitListener(queues = DirectConfig.DIRECT_QUEUEQ_B_NAME)
public void receiveConfirmMessageB(Message message, Channel channel) throws IOException {
String content = new String(message.getBody());
try {
log.info("接收到的队列消息为:{}",content);
// 手动确认消息是否消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
log.info("接收到的队列消息为:{} 出现异常,返还队列",content);
// 第二个布尔值为true表示消息返还队列,false表示丢弃消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
}
结果:
模拟异常
结果:
开启高级发布确认
在生产环境中由于一些不明原因,导致
rabbitmq
重启,在
RabbitMQ
重启期间生产者消息投递失败, 导致消息丢失,需要手动处理和恢复.而普通确认如果交换机不存在,控制台会报错无法找到交换机,因为普通确认有弊端因而有了高级发布确认。
高级发布确认:即使交换机不存在也会执行回调方法。
新增回调方法
package com.example.myselfmq.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
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 javax.annotation.PostConstruct;
/**
* @author Maple
* @description: TODO
* @date 2022/8/13 01:25
* @email 576235959@qq.com
*/
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {
// 注入
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
/**
* 交换机确认回调方法
* 发消息 交换机收到了就回调
* 1.发消息 交换机收到了 回调
* @param correlationData 保存回调消息的ID以及相关信息
* @param ack true:交换机收到消息 false:交换机接收失败
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String id = correlationData != null ? correlationData.getId() : "";
if (ack){
log.info("交换机已经收到Id为:{}的消息",id);
}else {
log.info("交换机未收到Id为:{}的消息,原因是:{}",id,cause);
}
}
}
新增生产者接口
@GetMapping("/confirmMsgPlus/{message}")
public void confirmMsgPlus(@PathVariable String message){
String a = ""+System.currentTimeMillis();
String b = ""+System.currentTimeMillis()+1;
// 参数为 交换机 路由键 消息 唯一消息id
rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE_NAME,DirectConfig.DIRECT_B_ROUTING_KEY,
message,new CorrelationData(a));
log.info("发送消息内容:{}",a+" "+message);
rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE_NAME,"123"+DirectConfig.DIRECT_B_ROUTING_KEY,
message,new CorrelationData(b));
log.info("发送消息内容:{}",b+" "+message);
}
测试结果
消息都到达了交换机,但是由于第二条消息routingkey错误,导致第二条消息被丢弃。但是我们要自己处理这条消息,于是设置回退消息。
MyCallBack代码优化
package com.example.myselfmq.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
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 javax.annotation.PostConstruct;
/**
* @author Maple
* @description: TODO
* @date 2022/8/13 01:25
* @email 576235959@qq.com
*/
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback {
// 注入
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
/**
* 交换机确认回调方法
* 发消息 交换机收到了就回调
* 1.发消息 交换机收到了 回调
* @param correlationData 保存回调消息的ID以及相关信息
* @param ack true:交换机收到消息 false:交换机接收失败
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String id = correlationData != null ? correlationData.getId() : "";
if (ack){
log.info("交换机已经收到Id为:{}的消息",id);
}else {
log.info("交换机未收到Id为:{}的消息,原因是:{}",id,cause);
}
}
/**
* 返回到达不了目的地的消息
* @param returnedMessage
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
log.error("消息{},被交换机{}退回,退回原因:{},路由Key:{}",
new String(returnedMessage.getMessage().getBody()),
returnedMessage.getExchange(),returnedMessage.getReplyText(),returnedMessage.getRoutingKey());
}
}
yaml文件新增
spring:
rabbitmq:
host: 121.40.235.153
port: 5672
username: admin
password: 123
listener:
direct:
acknowledge-mode: manual # 开启手动确认
publisher-confirm-type: correlated # 开启异步确认机制
publisher-returns: true # 开启回退
结果:
设置备份交换机
如果既不想丢失消息,又不想增加生产者的复杂性,该怎么做呢?死信队列可以为队列设置死信交换机来存储那些处理失败的消息,可是这些不可路由消息根本没有机会进入到队列,因此无法使用死信队列来保存消息。在 RabbitMQ
中,有一种备份交换机的机制存在,可以很好的应对这个问题。
修改DirectConfig代码:
新增:
![](https://i-blog.csdnimg.cn/blog_migrate/939b042362325f9f71f1658260c81ce9.png)
声明交换机时绑定备份交换机
![](https://i-blog.csdnimg.cn/blog_migrate/cd3148f41cfa7ccfd093a84d9919e280.png)
声明备份交换机队列以及绑定
整体:
package com.example.myselfmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @author Maple
* @description: TODO
* @date 2022/8/12 20:31
* @email 576235959@qq.com
*/
@Configuration
public class DirectConfig {
// 交换机名称
public static final String DIRECT_EXCHANGE_NAME = "direct_exchange";
// 队列名称
public static final String DIRECT_QUEUEQ_NAME = "direct_queue";
// 队列b
public static final String DIRECT_QUEUEQ_B_NAME = "direct_b_queue";
// RoutingKey
public static final String DIRECT_ROUTING_KEY = "key1";
public static final String DIRECT_B_ROUTING_KEY = "key2";
// 死信交换机
public static final String DEAD_LETTER_EXCHANGE = "dead_exchange";
// 死信队列
public static final String DEAD_LETTER_QUEUE = "dead_queue";
// 死信RoutingKey
public static final String DEAD_LETTER_ROUTING_KEY = "dead_queue";
// 备份交换机
public static final String BACKUP_EXCHANGE_NAME = "backup_exchange";
// 备份队列
public static final String BACKUP_QUEUE_NAME = "backup_queue";
// 报警队列
public static final String WARNING_QUEUE_NAME = "warning_queue";
// 声明交换机
@Bean
public DirectExchange directExchange(){
return ExchangeBuilder.directExchange(DIRECT_EXCHANGE_NAME).durable(true).
// 设置该交换机的备份交换机
alternate(BACKUP_EXCHANGE_NAME).build();
}
// 声明队列
@Bean
public Queue directQueue(){
Map<String,Object> arguments = new HashMap<>();
// 设置死信交换机
arguments.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
// 设置死信RoutingKey
arguments.put("x-dead-letter-routing-key",DEAD_LETTER_ROUTING_KEY);
// 设置TTL
arguments.put("x-message-ttl",10000);
// 业务队列绑定死信交换机
return QueueBuilder.durable(DIRECT_QUEUEQ_NAME).withArguments(arguments).build();
}
@Bean
public Queue directBQueue(){
Map<String,Object> arguments = new HashMap<>();
// 设置死信交换机
arguments.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
// 设置死信RoutingKey
arguments.put("x-dead-letter-routing-key",DEAD_LETTER_ROUTING_KEY);
// 设置TTL
// arguments.put("x-message-ttl",40000);
// 业务队列绑定死信交换机
return QueueBuilder.durable(DIRECT_QUEUEQ_B_NAME).withArguments(arguments).build();
}
// 绑定交换机和队列
@Bean
public Binding queueBindingExchange(@Qualifier("directExchange")DirectExchange directExchange,
@Qualifier("directQueue")Queue directQueue){
return BindingBuilder.bind(directQueue).to(directExchange).with(DIRECT_ROUTING_KEY);
}
@Bean
public Binding queueBBindingExchange(@Qualifier("directExchange")DirectExchange directExchange,
@Qualifier("directBQueue")Queue directBQueue){
return BindingBuilder.bind(directBQueue).to(directExchange).with(DIRECT_B_ROUTING_KEY);
}
// 声明死信交换机
@Bean
public DirectExchange deadExchange(){
return new DirectExchange(DEAD_LETTER_EXCHANGE);
}
// 声明死信队列
@Bean
public Queue deadQueue(){
return new Queue(DEAD_LETTER_QUEUE);
}
// 绑定死信交换机和队列
@Bean
public Binding deadBindingExchange(@Qualifier("deadExchange")DirectExchange deadExchange,
@Qualifier("deadQueue")Queue deadQueue){
return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_LETTER_ROUTING_KEY);
}
// 备份交换机
@Bean("backupExchange")
public FanoutExchange backupExchange(){
return new FanoutExchange(BACKUP_EXCHANGE_NAME);
}
// 备份队列
@Bean
public Queue backupQueue(){
return new Queue(BACKUP_QUEUE_NAME);
}
// 报警队列
@Bean
public Queue warningQueue(){
return new Queue(WARNING_QUEUE_NAME);
}
// 绑定
@Bean
public Binding backupQueueBindingbackupExchange(@Qualifier("backupQueue") Queue backupQueue,
@Qualifier("backupExchange") FanoutExchange backupExchange){
return BindingBuilder.bind(backupQueue).to(backupExchange);
}
@Bean
public Binding backupQueueBindingwarningExchange(@Qualifier("warningQueue") Queue warningQueue,
@Qualifier("backupExchange") FanoutExchange backupExchange){
return BindingBuilder.bind(warningQueue).to(backupExchange);
}
}
新增报警消费
package com.example.myselfmq.consumer;
import com.example.myselfmq.config.DirectConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author Maple
* @description: TODO
* @date 2022/8/12 17:10
* @email 576235959@qq.com
* 报警消费者
*/
@Slf4j
@Component
public class WarningConsumer {
// 接收报警消息
@RabbitListener(queues = DirectConfig.WARNING_QUEUE_NAME)
public void receiveWarningMsg(Message message){
String msg = new String(message.getBody());
log.error("发现不可路由消息:{}",msg);
}
}
测试结果:
回退消息和备份交换机可以一起使用,但是同时使用的话备份交换机优先级更高。