前言
面对在实际的生产环境中RabbitMQ服务宕机或者重启导致消息在投递阶段丢失的问题,我们需要采用消息的发布确认和回退消息两种机制来保证消息的不丢失。在这篇文章中,荔枝同样以demo实例的方式来梳理相关的知识,希望能够帮助到有需要的小伙伴~~~
文章目录
一、发布确认——回调接口和回退消息
在生产环境中由于一些不明原因,导致rabbitmq重启,在RabbitMQ重启期间生产者消息投递失败,导致消息丢失,需要手动处理和恢复。于是,我们开始思考,如何才能进行RabbitMQ的消息可靠投递呢?特别是在这样比较极端的情况,RabbitMQ集群不可用的时候,无法投递的消息该如何处理呢:
1.1 回调接口
前面我们讲到为了防止极端情况下的消息丢失,需要在原有发布确认模式中进行增强。这种丢消息的情况由两种:一是交换机接收不到消息;二是队列接收不到消息。我们先讲第一种情况:交换机接收不到消息的处理机制:回调接口。
配置类配置
要记得开启配置类,有三个参数:None/Correlated/Simple,其中simple会有信道关闭的风险。
spring.rabbitmq.publisher-confirm-type=correlated
消息发布者
消息发布者需要注意在这里需要传入一个correlationData对象,该对象只是一个对消息的唯一标识而已,如果在消息发布者处没有传入,那么回调接口类在重写ConfirmCallback接口的实现方法的时候就不能获取到消息的id值。
package com.crj.rabbitmqtestspringboot.controller;
import com.crj.rabbitmqtestspringboot.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//发布确认的生产者
@Slf4j
@RestController
@RequestMapping("/confirm")
public class ProducerController {
private RabbitTemplate rabbitTemplate;
//发消息
@GetMapping("/sendMessage/{message}")
public void sendMessage(@PathVariable String message){
CorrelationData correlationData = new CorrelationData("1");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,ConfirmConfig.CONFIRM_ROUTING_KEY,message,correlationData);
}
}
可以看到convertAndSend有多种重载的方法,这里我们按需求选择。
消息接收者
package com.crj.rabbitmqtestspringboot.controller.consumer;
import com.crj.rabbitmqtestspringboot.config.ConfirmConfig;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
//接收消息
@Component
public class Consumer {
@RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME)
public void receiveConfirmMessage(Message message){
System.out.println(new String(message.getBody()));
}
}
回调接口的实现类
回调接口实现类需要实现RabbitTemplate类中的ConfirmCallback接口,并通过重写接口原来的confirm方法来获取交换机是否获取消息的状态信息。
回调方法被调用的时机:
- 发消息 交换机接收到了会开始回调
- 发消息 交换机接收失败 开始回调
重写的confirm方法中的参数:
- correlationData 保存回调信息的ID机器相关信息,String类型
- ack 交换机是否接收到消息,boolean类型
- reason 失败的原因,String类型
package com.crj.rabbitmqtestspringboot.controller.callback;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback {
/**
* 因为该接口是内部接口,因此当RabbitTemplate调用ConfirmCallback接口并不能调用到我们定义的实现类
* 所以我们需要做一下注入的操作
*/
@Autowired
private RabbitTemplate rabbitTemplate;
//注入
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
}
/**
*交换机确认回调方法
* 回调方法被调用的时机:
* 1.发消息 交换机接收到了会开始回调
* 2.发消息 交换机接收失败 开始回调
*
*
* @param correlationData 保存回调信息的ID机器相关信息
* @param ack 交换机是否接收到消息boolean
* @param reason 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String reason) {
String id = correlationData != null ? correlationData.getId() : "";
if(ack){
log.info("交换机已经收到Id为{}的消息",id);
}else {
log.info("交换机为收到Id为{}的消息,原因:{}",id,reason);
}
}
}
这里还需要注意的是:由于ConfirmCallback接口是内部接口,因此当RabbitTemplate实例化后调用ConfirmCallback接口实现类的时候并不能调用到我们定义的实现类,因此我们需要做一下注入的操作。
@PostConstruct注解:当依赖注入完成后用于执行初始化的方法,并且只会被执行一次,该注解的执行顺序必须按照上述demo注解的顺序!!!
1.2 回退消息
在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃的。因此为了让无法被路由的消息被处理,我们可以通过设置mandatory参数可以在当消息传递过程中不可达时将消息返回给生产者。 简单来说就是:队列如果接收不到消息就会使用回退消息,而消息只有在消息不可达的时候才会采用回退消息给生产者。
配置类中开启回退消息
spring.rabbitmq.publisher-returns=true
接口实现类
这里需要注意的是returnedMessage接口方法的重写,荔枝学习的教程重写该方法时候有五个参数,但现在好像已经改了一下,重写该方法的时候仅有一个参数作为方法的形参!
package com.crj.rabbitmqtestspringboot.controller.callback;
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;
@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnsCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
//注入
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String reason) {
String id = correlationData != null ? correlationData.getId() : "";
if(ack){
log.info("交换机已经收到Id为{}的消息",id);
}else {
log.info("交换机为收到Id为{}的消息,原因:{}",id,reason);
}
}
//重写ReturnsCallback接口的方法,只有在消息不可达的时候才会调用
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.err.println("ReturnedMessage: " + returnedMessage);
}
}
二、备份交换机
除了上面的回退消息的解决方法,我们还可以通过加一个备份交换机来备份消息。备份交换机顾名思义就是在原来接收消息的交换机没有确定接收到消息的时候(宕机或者服务中断),将消息发送到备份交换机备份。
关联直接交换机和备份交换机,方便消息转发。这里荔枝仅给出关键代码:withArgument这里会传递一个map参数,设置备份交换机。
//声明交换机
@Bean ("confirmExchange")
public DirectExchange confirmExchange(){
return ExchangeBuilder.directExchange(CONFIRM EXCHANGE NAME).durable(true)
.withArgument("alternate-exchange",BACKUP_EXCHANGE_NAME).build();
}
需要注意的是:当消息回退和备份交换机机制同时开启的时候,备份交换机的优先级更高。
总结
发布确认机制和回退消息机制其实就是为了保证消息队列中的消息的安全性,也是为了防止在发送后消息的不丢失以及回退重发来保证业务功能的正常运行。从这两个角度去理解整个机制会有更好的体会哈哈哈哈~
今朝已然成为过去,明日依然向往未来!我是荔枝,在技术成长之路上与您相伴~~~
如果博文对您有帮助的话,可以给荔枝一键三连嘿,您的支持和鼓励是荔枝最大的动力!
如果博文内容有误,也欢迎各位大佬在下方评论区批评指正!!!