发布确认
amqp-client自带的发布确认机制,这里只讲异步确认,同步确认请转接rabbitMQ官网
https://www.rabbitmq.com/confirms.html
即channel.confirmSelect()方法,使用后会开启发布确认机制,增加确认监听器,内置成功和失败的回调方法,如下:
经过测试,得出以下几点总结:
1.当publish发布消息时,如果exchange交换机不存在,控制台会报错无法找到交换机,而不会调用监听器
2.当routingKey不存在时,不会导致监听器消息确认失败,由此可知,消息确认是在交换机
3.ConfirmCallback第二个参数表示是否为批量,回调函数会自动选择批处理或者单个处理,所以有时候需要判断一下
以上为普通确认发布的测试总结,因为普通确认发布有一些弊端,所以有了高级确认发布,
普通确认发布也可以对队列进行监控,使用returnedListener监听器
//异步确认
public static void syncConfirm() throws Exception {
//获取信道
Channel channel = RabbitMqUtil.getChannel();
//开启发布确认
channel.confirmSelect();
//声明队列
//durable = true 队列持久化
channel.queueDeclare(Confirm_Queue_Name,true,false,false,null);
/**
* 线程安全有序的一个哈希表 适用于高并发的情况下
*1.轻松的将序号和消息进行关联map
*2.轻松的批量删除条目 只要给到序号
* 3.支持高并发
* */
ConcurrentSkipListMap<Long,String> outStandingConfirms = new ConcurrentSkipListMap<>();
/**
* 准备消息的监听器,监听哪些消息成功了,哪些消息失败了
* */
//消息成功监听器
/**
* 参数
* 1.消息的标记
* 2.是否为批量
* 只有当交换机存在时才会调用
* 有可能批量有可能不批量
* */
ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
if (multiple){
//删除已经确认的消息,剩下的就是未确认的消息
ConcurrentNavigableMap<Long, String> confirmed = outStandingConfirms.headMap(deliveryTag);
confirmed.clear();
System.out.println("批量确认的消息:"+deliveryTag);
}else {
outStandingConfirms.remove(deliveryTag);
System.out.println("确认的消息:"+deliveryTag);
}
};
//消息失败回调函数,发送过去后没有接受到交换机的确认信息,在等待交换机确认信息的时候交换机宕机
ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
String message = outStandingConfirms.get(deliveryTag);
System.out.println("未确认的消息:"+message);
};
/**
* 1.监听哪些消息成功了
* 2.监听哪些消息失败了
* */
channel.addConfirmListener(ackCallback,nackCallback);
// channel.addConfirmListener(new ConfirmListener() {
// @Override
// public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// if (true) {
// System.out.println("message"+deliveryTag+"到达交换机");
// }
// }
//
// @Override
// public void handleNack(long deliveryTag, boolean multiple) throws IOException {
// if (true) {
// System.out.println("message"+deliveryTag+"没有到达交换机");
// }
// }
// });
//开始时间
long beginTime = System.currentTimeMillis();
//批量发送消息
for (int i = 0;i<10;i++){
String message = i+"";
channel.basicPublish("",Confirm_Queue_Name,null,message.getBytes(StandardCharsets.UTF_8));
//记录所有消息
outStandingConfirms.put(channel.getNextPublishSeqNo(),message);
}
//结束时间
long endTime = System.currentTimeMillis();
System.out.println("发布"+10+"个批量消息共用时:"+(endTime-beginTime));
}
高级发布确认
springboot整合的rabbitMQ实现了对消息到达交换机的监听,如果交换机不存在也会调用回调方法,需要在配置文件中开启确认发布,如果要监听消息路由到队列是否成功,需要在配置文件中配置消息回退机制
以上机制主要是用来防止消息的丢失,消息持久化不太好测试,猜测,回调函数会在消息持久化之后执行,如果消息回退,可以在消息回退方法中 对消息进行重试,redis记录重试次数,代码就不演示了,只演示配置文件和回调方法
#启用交换机确认回调接口
spring.rabbitmq.publisher-confirm-type=correlated
#开启发布回退
spring.rabbitmq.publisher-returns=true
package com.zwj.springbootrabbitmq.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.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author zwj
* @date 2022/1/10 - 20:32
*/
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
//注入 将实现类set到RabbitTemplate中的ConfirmCallback参数中
@PostConstruct //对象加载完依赖注入后执行
public void init(){
//注入
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
/**
* 交换机回调方法
* 1.发消息 交换机接收到了 回调
* 1.1 correlationData 保存回调信息的ID及相关信息
* 1.2 交换机收到信息 ack = true
* 1.3 cause null
* 2.发消息 交换机接收失败了 回调
* 2.1 correlationData 保存回调信息的ID及相关信息
* 2.2 交换机收到消息 ack = false
* 2.3 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);
}
}
//只有在当消息传递过程中不可达目的地是将消息返回给生产者
//只有不可达目的地的时候才进行回退
@Override
public void returnedMessage(ReturnedMessage returned) {
log.error("消息{},被交换机{}退回,退回原因:{},路由key:{}",
new String(returned.getMessage().getBody()),returned.getExchange(),returned.getReplyText(),
returned.getRoutingKey());
}
}