发布确认
操作要求
- 设置要求队列必须持久化
- 设置要求队列中的消息必须持久化
- 发布确认(队列将消息保存到磁盘后,rabbitMQ向生产者告知已经持久化)
注:进入confirm模式,所有在该信道上面发布的消息将会被指派一个唯一的ID,一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID)
实现
队列、消息持久化,参考上面
发布确认
//开启发布确认
channel.confirmSelect();
单个发布确认
同步确认发布,发一条,确认一条,(缺点)发布速度慢
channel.confirmSelect();
for (...) {
...
channel.basicPublish(...);
//单个消息发布确认
boolean flag = channel.waitforConfirms();
if (flag) {
System.out.println("ok");
}
}
批量发布确认
发布一批消息,一次性确认,较快,(缺点)当出现问题时,不知道是哪个消息出现问题
channel.confirmSelect();
for(...) {
...
if (i == n/2) {
boolean flag = channel.waitforConfirms();
if (flag) {
System.out.println("ok");
}
}
}
异步确认发布
基本概念
async
效率高,消息丢失后可以知道是哪个消息丢失然后重新发布(通过回调函数)
实现
//监听器监听消息的时间发的比发布消息久,当消息全部发布后监听器仍在监听消息(两个线程)
channel.confirmSelect();
//消息监听器,确认哪些消息成功失败
channel.addConfirmListener(
//ackCallback, deliveryTag消息标识, multiple是否为批量确认
(long deliveryTag, boolean multiple)->{
...
},
//nackCallback
(deliveryTag, multiple)->{
...
})
for(...) {
...
}
处理异步未确认消息
利用跳表(一个有序的链表,有hash)
不管是否成功,回调函数都只能拿到发布消息时的一个编号,其是不知道当时发送了什么的;同时因为回调函数是批量确认的,所以使用基于链表的并发集合
将未确认的消息放到一个基于内存的能被发布线程访问的队列,比如说,这个队列在confirm、callbacks与发布线程之间进行消息的传递
//线程安全有序的底层一个跳表结构,适用于高并发
ConcurrentSkipListMap<K,V> concurrentSkipListMap = new ConcurrentSkipListMap<>();
channel.confirmSelect();
//消息监听器,确认哪些消息成功失败
channel.addConfirmListener(
//ackCallback, deliveryTag消息标识, multiple是否为批量确认
(long deliveryTag, boolean multiple)->{
...
if(multiple) {
//headMap()获取指针指向地址,浅拷贝,删试图就会删原地址,返回该映射中键小于(或等于,如果包含true)toKey的部分的试图
ConcurrentNavigableMap<K,V> confirmed = concurrentSkipListMap.headMap(deliveryTag);
confirmed.clear();
}
else {
concurrentSkipListMap.remove(deliveryTag);
}
},
//nackCallback
(deliveryTag, multiple)->{
...
V message = concurrentSkipListMap.get(deliveryTag);
})
for(...) {
...
concurrentSkipListMap.put(channel.getNextPublishSeqNo() - 1, message);
}