发布确认模式
生产者发消息给mq,mq将消息持久化到磁盘上了,mq再告诉生产者我已经把消息持久化到磁盘上了,这时才能保证消息是没有丢失,稳稳地保存在了磁盘上。
mq告诉生产者,我已经保存到磁盘了,这一步就叫发布确认
发布确认原理
生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息都将会被指派一个唯一的 ID (从 1 开始),一旦消息被投递到所有匹配的队列之后,broker 就会发送一个确认给生产者 (包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置 basic.ack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm 模式最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息, 生产者应用程序同样可以在回调方法中处理该 nack 消息。
发布确认的策略
开启发布确认的方法:发布确认默认是没有开启的,如果要开启,需要调用方法 confirmSelect,每当你要想使用发布确认,都需要在 channel 上调用该方法。
发布确认的三个方式:
- 单个
- 批量
- 异步
和springboot整合
直接上代码:
- 添加配置文件:
在springboot的rabbitmq的配置文件中添加
spring.rabbitmq.publisher-confirm-type=correlated
1、NONE 值:是禁用发布确认模式,是默认值。
2、CORRELATED 值:是发布消息成功到交换器后会触发回调方法。
3、SIMPLE 值:经测试有两种效果
其一效果和 CORRELATED 值一样会触发回调方法
其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是 waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker。
4、配置类:
@Configuration
public class ConfirmConfig {
public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
//声明业务 Exchange
@Bean("confirmExchange")
public DirectExchange confirmExchange() {
return new DirectExchange(CONFIRM_EXCHANGE_NAME);
}
// 声明确认队列
@Bean("confirmQueue")
public Queue confirmQueue() {
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
// 声明确认队列绑定关系
@Bean
public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
@Qualifier("confirmExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("key1");
}
}
回调函数:
RabbitTemplate.ConfirmCallback:RabbitTemplate中的一个内部接口
@FunctionalInterface
public interface ConfirmCallback {
void confirm(@Nullable CorrelationData var1, boolean var2, @Nullable String var3);
}
让生产者感知消息是否发送成功,成功发送和失败发送都做一些事情
@Component
@Slf4j
public class MyCallback implements RabbitTemplate.ConfirmCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
//依赖注入 rabbitTemplate 之后再设置它的回调对象
// 此注解会在其他注解执行完成后再执行,所以rabbitTemplate先注入,再执行此初始化方法
@PostConstruct
public void init() {
// 设置rabbitTemplate的ConfirmCallBack为我们重写后的类
rabbitTemplate.setConfirmCallback(this);
}
/**
* 交换机不管是否收到消息的一个回调方法
*
* @param correlationData 消息相关数据
* @param ack 交换机是否收到消息
* @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);
}
}
}
编写消息消费者:
@RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME)
public void receiveMsg(Message message) {
String msg = new String(message.getBody());
log.info("接受到队列 confirm.queue 消息:{}", msg);
}
编写生产者:
@GetMapping("sendConfirmMsg/{message}")
public void sendConfirmMsg(@PathVariable String message) {
CorrelationData correlationData = new CorrelationData("1");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME + "123", "key1", message, correlationData);
log.info("当前时间:{},发送消息给确认队列C:{}", new Date(), message);
}