RabbitMQ:发布确认

201 阅读2分钟

发布确认

操作要求

  1. 设置要求队列必须持久化
  2. 设置要求队列中的消息必须持久化
  3. 发布确认(队列将消息保存到磁盘后,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

效率高,消息丢失后可以知道是哪个消息丢失然后重新发布(通过回调函数)

2021101018215325.png

实现

//监听器监听消息的时间发的比发布消息久,当消息全部发布后监听器仍在监听消息(两个线程)

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);
}