发布确认

Posted lwx_R

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了发布确认相关的知识,希望对你有一定的参考价值。

1.原理

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始)
一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),
这就使得生产者知道消息已经正确到达到的队列了
如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号
此外broker也可以设置basic. ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式最大的好处在于他是异步的,一旦发布一条消息, 生产者应用程序就可以在等信道发回确认的同时继续发送下一条消息
当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失
就会发送一条nack消息,生产者应用程序同样可以在问调方法中处理该nack消息。

2.方法

发布确认默认是没有开启的,如果要开启需要调用方法confirmSelect,每当你要想使用发布确认,都需要在channel上调用该方法

2.1 单个确认

这是一种简单的确认方式,它是一种同步确认发布的方式,也就是发布一个消息之后只有它
被确认发布,后续的消息才能继续发布,waitForConfirmsOrDie(long)这个方法只有在消息被确认
的时候才返回,如果在指定时间范围内这个消息没有被确认那么它将抛出异常。
这种确认方式有一个最大的缺点就是:发布速度特别的慢,因为如果没有确认发布的消息就会
阻塞所有后续消息的发布,这种方式最多提供每秒不超过数百条发布消息的吞吐量。当然对于某
些应用程序来说这可能已经足够了。

//单个消息发送确认请求
boolean f = channel.waitForConfirms();
if(f)
    System.out.println("Success");

2.2 批量确认

上面那种方式非常慢,与单个等待确认消息相比,先发布一批消息然后-起确认可以极大地
提高吞吐量,当然这种方式的缺点就是:当发生故障导致发布出现问题时,不知道是哪个消息出现
问题了,我们必须将整个批处理保存在内存中,以记录重要的信息而后重新发布消息。当然这种
方案仍然是同步的,也一样阻塞 消息的发布。

//            批量
if(i % 100 == 0)
    boolean f = channel.waitForConfirms();
    if(f)
        System.out.println("Success");
    

2.3 异步确认

异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说,
他是利用回调函数来达到消息可靠性传递的,这个中问件也是通过函数回调来保证是否投递成功,
下面就让我们来详细讲解异步确认是怎么实现的。

处理异步未确认消息:最好的解决的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的
比如说用 ConcurrentSkipListMap这个队列在confirm callbacks与发布线程之问进行消递。

package Confirm;

import com.rabbitmq.client.*;

import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;


public class Producer 
    /**
     * 发布确认
     *  1.单个确认
     *  2.批量确认
     *  3.异步确认
     */
    public static final String QUEUE_NAME = "con";

    public static void main(String[] args) throws Exception 
        ConnectionFactory factory = new ConnectionFactory();
        //连接RabbitMQ队列
        factory.setHost("127.0.0.1");
        //设置用户名 密码
        factory.setUsername("guest");
        factory.setPassword("guest");
        //创建连接
        Connection connection = factory.newConnection();
        //获取信道
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, true, false, false,null);

        //开启确认
        channel.confirmSelect();
        //开始时间
        long begin = System.currentTimeMillis();

        /**
         * 线程安全有序的哈希表 适用于高并发
         * 1.将序号和消息关联
         * 2.批量删除条目 只要给序号
         * 3.支持多线程
         *
         */
        ConcurrentSkipListMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();


        //成功回调函数
        /**
         * 1.消息标记
         * 2.是否为批量确认
         */
        ConfirmCallback ackCallback = (deliveryTag, multiple) -> 
            //2.删除已确认消息
            if(multiple)
                ConcurrentNavigableMap<Long, String> concurrentNavigableMap = outstandingConfirms.headMap(deliveryTag);
                concurrentNavigableMap.clear();
             else 
                outstandingConfirms.remove(deliveryTag);
            
            System.out.println("ack" + deliveryTag);
        ;

        //失败回调函数
        ConfirmCallback nackCallback = (deliveryTag, multiple) -> 
            //3.处理未确认消息
            String message = outstandingConfirms.get(deliveryTag);

            System.out.println("nack" + message);
        ;
        //消息监听器(另一个线程) 成功或失败
        channel.addConfirmListener(ackCallback, nackCallback);

        //发消息
        for( int i = 0; i < 1000; i++)
            String message = i + "";
            channel.basicPublish("", QUEUE_NAME,null, message.getBytes(StandardCharsets.UTF_8));
            //单个消息发送确认请求
            boolean f = channel.waitForConfirms();
            if(f)
                System.out.println("Success");
            
//            批量
            if(i % 100 == 0)
                boolean f = channel.waitForConfirms();
                if(f)
                    System.out.println("Success");
                
            
            //异步

            //1.记录所有要发送消息
            outstandingConfirms.put(channel.getNextPublishSeqNo(), message);


        
        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("Time " + (end - begin));
    


RabbitMQ 学习----发布确认

文章目录

RabbitMQ 学习(七)----发布确认


发布确认是一个保证RabbitMQ 可靠性的一个机制


  保证生产者将信息成功的发送到 RabbitMQ的 server端了,那么broker就会回一个确认,如果没有收到或者收到拒绝信息,那么说明可能网络不好没有发送成功,server端宕机了,broker拒绝接收等情况,如果不进行后续处理,那么信息就会丢失,生产者收到失败的消息使用回调函数在进行处理。


  生产者将信道设置成 confirm 模式,所有在该信道上发布的消息都会指定一个唯一的ID,一旦消息投递到队列中,就是发送成功了,broker会立刻发送一个确认ack 给生产者,这个时候,生产者就知道消息已经发送成功了。

  如果队列和信息是持久化的,那么确认消息会在将消息写入磁盘之后再发出,broker返回的确认包含 确认消息的序列号,还可以设置 multiple,表示此序号前的所有消息都得到了处理。


  一旦发布消息,生产者等待确认的同时继续发送下一条消息,如果rabbitMq自身内部错误导致消息为发送成功,生产者就可以再回调方法中继续处理。


为了保证RabbitMQ的可靠性,生产者怎么做?

1、设置要求队列持久化

2、设置队列中的消息持久化

3、发布确认,保证写入磁盘,broker成功收到


(1)开启发布确认的方法


  channel 的cofirm模式默认是没有开启的,如果需要开启需要调用 confirmSelect(),当我们使用发布确认的时候。需要使用channel调用该方法。

Channel channel =connection.createChannel();
channel.confirmSelect();

(2)单个发布确认


  这是一种同步确认发布的方式,就是发布一个消息之后等待确认后,后续的消息才能继续发布。waitForConfiemOrDie(long) 这个方法只有当消息被确认才会返回,如果在指定的时间内未返回就会抛出异常。


  这种确认方式最大的缺点:速度特别慢。如果消息没有确认,就会阻塞后续消息的发送,造成发送消息的速度很慢。


public class SingleConfirm 
    /**
     * 发布确认模式
     * 1、单个确认
     * @param args
     */
    public static void main(String[] args) 
        Connection connection = RabbitMQUtils.getConnect();
        Channel channel = null;

        try 

            channel = connection.createChannel();

            // 开启确认模式
            channel.confirmSelect();

            // 声明队列
            channel.queueDeclare("confirm", true, false, false, null);

            long begin = System.currentTimeMillis();

            // 批量发送消息,每次发送进行确认
            for (int i = 0; i <1000 ; i++) 

                String message = i+"";

                // 发布单条消息
                channel.basicPublish("", "confirm", null, message.getBytes());

                // 单个消息发送之后,马上发布确认,使用 waitForConfirms
                if(channel.waitForConfirms())
                    //System.out.println("消息发送成功:"+i);
                

            

            long end = System.currentTimeMillis();

            System.out.println("发送1000条数据,使用单个发布确认的时间为:"+(end-begin));

         catch (Exception e)
            e.printStackTrace();
        finally 
            RabbitMQUtils.close(channel, connection);
        

    



非常浪费时间


(3)批量确认发布


  每发送一部分消息,批量同步确认一次,若有消息无法发出,该模式无法确认是哪个消息无法发送;


发布1000条消息,每发送100条确认一次

public class MultipleConfirm 
    public static void main(String[] args) 
        Connection connection = RabbitMQUtils.getConnect();
        Channel channel = null;

        try 

            channel = connection.createChannel();

            // 开启确认模式
            channel.confirmSelect();

            // 声明队列
            channel.queueDeclare("confirm", true, false, false, null);

            long begin = System.currentTimeMillis();

            // 批量确认消息的数量,没发送100个返回一个确认ack
            int notAck = 100;

            // 批量发送消息,每次发送进行确认
            for (int i = 0; i <1000 ; i++) 

                String message = i+"";

                // 发布单条消息
                channel.basicPublish("", "confirm", null, message.getBytes());

                if((i+1)%notAck==0)
                    // 每发送100条确认一次,查看是否这一批是否有发送失败的情况
                    channel.waitForConfirmsOrDie();
                


            

            long end = System.currentTimeMillis();

            System.out.println("发送1000条数据,使用批量发布确认的时间为:"+(end-begin));

         catch (Exception e)
            e.printStackTrace();
        finally 
            RabbitMQUtils.close(channel, connection);
        

    


时间为 396毫秒


(4)异步发布确认


  生产者发送消息与 接收确认这两个步骤不是同步的,是异步的,生产者只管发送,同时使用监听(addConfirmListener)返回的确认,对成功确认、失败确认两种情况分别进行处理。非常高效且安全

  • 开启确认模式
  • 声明确认成功的callback
  • 声明确认失败的callback
  • 开启确认监听 addConfirmListener() ,设置callback
  • 信道发送消息,不需要额外设置接收waitForConfirm什么的
 public static void main(String[] args) 
        Connection connection = RabbitMQUtils.getConnect();
        Channel channel = null;

        try 

            channel = connection.createChannel();

            // 开启确认模式
            channel.confirmSelect();

            // 声明队列
            channel.queueDeclare("confirm", true, false, false, null);

            // 作为接收成功的函数式接口 参数
            ConfirmCallback ackCallback =(deliveryTag, multiple)-> System.out.println("确认的消息: "+deliveryTag);
                // 表示接收成功的回调函数


            // 作为接收失败的函数式接口 参数
            ConfirmCallback nackCallback = (deliveryTag,multiple)-> System.out.println("接收失败!");
                // 表示接收失败的回调函数


            // 这是一个异步的监听 消息返回确认信息的 反应
            channel.addConfirmListener(ackCallback,nackCallback);

            long begin = System.currentTimeMillis();


            // 批量发送消息,每次发送进行确认
            for (int i = 0; i <1000 ; i++) 
                String message = i+"";
                // 发布单条消息
                channel.basicPublish("", "confirm", null, message.getBytes());

            

            long end = System.currentTimeMillis();

            System.out.println("发送1000条数据,使用异步发布确认的时间为:"+(end-begin)+"ms");

         catch (Exception e)
            e.printStackTrace();
        
        
        // 执行完不能关闭连接,还要继续监听确认的信息
        /**
         finally 
            RabbitMQUtils.close(channel, connection);
        
         */

    

时间非常高效

以上是关于发布确认的主要内容,如果未能解决你的问题,请参考以下文章

RabbitMQ 学习----发布确认

RabbitMQ - 发布确认

RabbitMQ:发布确认模式

RabbitMQ 消息确认机制 - 发布者确认

rabbitmq的发布确认和事务

GCP 云函数何时确认发布/订阅消息?