rabbitmq之备份交换机
前言
// 处理成功----false 表示不批量处理,批量处理容易丢失信息,消息会被rabbitmq broker 删除。
channel.basicAck(envelope.getDeliveryTag(), false);
// 处理失败 重新入队
channel.basicNack(envelope.getDeliveryTag(), false, true);
// 放弃消息,不重新入队,此时相当于消息被拒,配置了死信交换机就会存放到死信中
channel.basicNack(tag, false, false);
// 重新入队
channel.basicNack(tag, false, true);
有了 mandatory 参数和回退消息,我们获得了对无法投递消息的感知能力,有机会在生产者的消息 无法被投递时发现并处理。但有时候,我们并不知道该如何处理这些无法路由的消息,最多打个日志,然 后触发报警,再来手动处理。而通过日志来处理这些无法路由的消息是很不优雅的做法,特别是当生产者 所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错。而且设置 mandatory 参数会增 加生产者的复杂性,需要添加处理这些被退回的消息的逻辑。如果既不想丢失消息,又不想增加生产者的 复杂性,该怎么做呢?前面在设置死信队列的文章中,我们提到,可以为队列设置死信交换机来存储那些 处理失败的消息,可是这些不可路由消息根本没有机会进入到队列,因此无法使用死信队列来保存消息。 在 RabbitMQ 中,有一种备份交换机的机制存在,可以很好的应对这个问题。什么是备份交换机呢?备份交换机可以理解为 RabbitMQ 中交换机的“备胎”,当我们为某一个交换机声明一个对应的备份交换机时, 就是为它创建一个备胎,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由 备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout ,这样就能把所有消息都投递到与其绑 定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都 进入这个队列了。当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警。
代码架构图
若正常交换机及队列出现问题,消息将被备用交换机接收并转发,转发到备份队列进行备份,以及转发到报警队列进行报警提醒
配置类
针对与普通交换转发给备份交换机需要携带此参数
arguments.put("alternate-exchange",BACKUP_EXCHANGE);
/**
* 备份交换机
*/
@Configuration
public class BackupsConfig {
// 声明普通交换机
private static final String BOOT_CFM_EXC = "boot.cfm.exc";
// 声明普通队列
private static final String BOOT_QUEUE = "boot.queue";
// 声明 routing-key
private static final String ROUTING_KEY = "key1";
// 声明备份交换机
public static final String BACKUP_EXCHANGE = "backup.exchange";
// 声明备份队列
public static final String BACKUP_QUEUE = "backup.queue";
// 声明警告队列
public static final String WARNING_QUEUE = "warning.queue";
// 声明普通交换机--->若交换机无法正常将消息传递给消费者,那么将消息转存到备份交换机中
@Bean
public DirectExchange crmExchange(){
Map<String,Object> arguments = new HashMap<>();
// 绑定关系,如果交换机无法发送给队列,那么转发到备份交换机中,与之前的 死信队列 类似
arguments.put("alternate-exchange",BACKUP_EXCHANGE);
return new DirectExchange(BOOT_CFM_EXC,true,false,arguments);
}
// 声明普通队列
@Bean
public Queue crmQueue(){
return QueueBuilder.durable(BOOT_QUEUE).build();
}
// 绑定关系--->将普通交换机和 普通队列进行关联
@Bean
public Binding bindCrmExchangeAndCrmQueue(@Qualifier("crmExchange") DirectExchange crmExchange,
@Qualifier("crmQueue") Queue crmQueue){
return BindingBuilder.bind(crmQueue).to(crmExchange).with(ROUTING_KEY);
}
// 声明备份交换机
@Bean
public FanoutExchange backExchange(){
return new FanoutExchange(BACKUP_EXCHANGE);
}
// 声明备份队列
@Bean
public Queue backQueue(){
return QueueBuilder.durable(BOOT_QUEUE).build();
}
// 声明警告队列
@Bean
public Queue warningQueue(){
return QueueBuilder.durable(WARNING_QUEUE).build();
}
//绑定关系。备份交换机和备份队列
@Bean
public Binding bindingBackExchangeAndBackQueue(@Qualifier("backExchange")FanoutExchange backExchange,
@Qualifier("backQueue") Queue backQueue){
return BindingBuilder.bind(backQueue).to(backExchange);
}
//绑定关系。备份交换机和警告队列
@Bean
public Binding bindingBackExchangeAndWarningQueue(@Qualifier("backExchange")FanoutExchange backExchange,
@Qualifier("warningQueue") Queue warningQueue){
return BindingBuilder.bind(warningQueue).to(backExchange);
}
}
发布确认回调接口
自定义一个类分别实现:RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback
这两个接口,其中ConfirmCallback
接口是交换机的确认回调,ReturnCallback
是消息回调,当消息传送到队列过程中不可抵达的时候 将消息返回给生产者
在实现两个接口后,我们需要将它注入到我们的rabbitTemplate中:
/**
* 向rabbitTemplate 注入回调失败的类
* 后置处理器:其他注解都执行结束才执行。
*/
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
完整代码:
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 向rabbitTemplate 注入回调失败的类
* 后置处理器:其他注解都执行结束才执行。
*/
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
/**
* 交换机确认回调方法
* 发消息 交换机接收到了 回调
* @param correlationData :保存回调消息的 ID 及相关信息,交换机收到消息 ack=true代表成功;ack=false 代表失败
* @param ack :true 代表交换机收到了
* @param cause : 原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
log.info("交换机已经收到 ID 为:{} 的消息",correlationData.getId());
}else{
// rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,
// ConfirmConfig.CONFIRM_ROUTINGKEY,"message",correlationData);
log.info("交换机未收到 ID为 {} 的消息,原因是 {}",correlationData.getId(),cause);
}
}
/**
* 当消息传送到队列过程中不可抵达的时候 将消息返回给生产者
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("消息 {} ,被交换机 {} 退回原因 {}",message,exchange,replyText);
}
}
消费者
@Component
@Slf4j
public class WarningConsumer {
// 以及创建好的 报警队列
public static final String WARNING_QUEUE_NAME = "warning.queue";
//接收报警消息
@RabbitListener(queues = WARNING_QUEUE_NAME) // 读取队列中的消息
public void receiveWarningMsg(Message message){
String msg = new String(message.getBody());
log.info("报警发现不可路由消息:{}",msg);
}
}
生产者
// 测试备份交换机
@GetMapping("/sendMessageBackups/{message}")
public void sendMessageToBackupsExchange(@PathVariable String message){
// 需要发送一个correlationData,此时交换机确认回调中携带了这个参数,相当于消息的标识
CorrelationData correlationData = new CorrelationData("1");
// 发送消息,生产者发送到交换机,交换机通过routing_key 发送到消费者 ,模拟错误,不能发送到指定队列
rabbitTemplate.convertAndSend(BackupsConfig.BOOT_CFM_EXC,BackupsConfig.ROUTING_KEY+2,message,correlationData);
}
测试结果
http://localhost:8080/ttl/sendMessageBackups/你好
可以看到,当消息不可抵达的时候,采用备份交换机,可以使得消息进行存储,通过备份队列进行消费。
总结
当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理。
注意事项:当退回模式和备份交换机一起使用的时候,备份交换机的优先级比较高,不会执行回退消息的回调。
(1)mandatory 参数告诉服务器至少将该消息路由到一个队列中,否则将消息返回给生产者。
(2)immediate 参数告诉服务器,如果该消息关联的队列上有消费者,则立刻投递;如果所有匹配的队列上都没有消费者,则直接将消息返还给生产者,不用将消息存入队列而等待消费者了。
RabbitMQ 3.0 版本开始去掉了对immediate参数的支持,对此RabbitMQ官方解释是:immediate参数会影响镜像队列的性能,增加了代码复杂性,建议采用TTL和DLX的方法替代。
mandatory:参数在application.yml 文件中进行配置:
spring:
rabbitmq:
port: 5672
host: xxxxx
username: xxx
password: xxx
publisher-confirm-type: correlated #开启交换机确认回调
publisher-returns: true # 开启队列回退消息
listener:
simple:
acknowledge-mode: manual #开启手动确认
template:
mandatory: false # 表示参数告诉服务器至少将该消息路由到一个队列中,否则将消息返回给生产者。
在 rabbitmqTemplate中调用convertAndSend
实际在底层调用的是channel.basicPublish
方法。
上图是一个工作流程,实现
备份交换机 和 消息回退的区别:
消息回退(Message Requeue)和备份交换机(Alternate Exchange)都是RabbitMQ提供的保证消息不丢失的两种机制,主要区别如下:
- 触发时机不同
- 消息回退是在消费者端调用basicNack或basicReject触发
- 备份交换机是在Exchange路由不到队列时触发
- 处理方式不同
- 消息回退是将消息重新入队,重新派发给消费者
- 备份交换机是将消息路由到备份交换机绑定的队列
- 使用场景不同
- 消息回退适用于消费失败需要重试
- 备份交换机适用于路由失败需要备份
- 实现方式不同
- 消息回退需要消费端实现消息ACK
- 备份交换机需要额外声明备份交换机绑定队列
- 性能不同
- 消息回退可能导致消息重复消费
- 备份交换机不会导致重复
综上,消息回退和备份交换机各有不同的使用场景,可以根据需求选择适合的机制,也可以同时使用两者。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)