RabbitMq高级特性

消息的可靠性

发送方确认机制

confirm

发送方确认(publisher confirm)机制。生产者将信道设置成confirm(确认)模式,一旦信道进入confirm 模式,所有在该信道上⾯面发布的消息都会被指派一个唯一的ID(从1 开始),一旦消息被投递到所有匹配的队列之后(如果消息和队列是持久化的,那么确认消息会在消息持久化后发出),RabbitMQ 就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID),这样生产者就知道消息已经正确送达了。

RabbitMQ 回传给生产者的确认消息中的deliveryTag 字段包含了确认消息的序号,另外,通过设置channel.basicAck方法中的multiple参数,表示到这个序号之前的所有消息是否都已经得到了处理了。生产者投递消息后并不需要一直阻塞着,可以继续投递下一条消息并通过回调方式处理理ACK响应。如果 RabbitMQ 因为自身内部错误导致消息丢失等异常情况发生,就会响应一条nack(Basic.Nack)命令,生产者应用程序同样可以在回调方法中处理理该 nack 命令。

public static void main(String[] args) {
    try {
        Connection connection = MqConnectionFactory.getConnection();
        Channel channel = connection.createChannel();
        //向RabbitMq服务器发送AMQP指令,将当前通道标记为发送方确认通道
        channel.confirmSelect();

        channel.queueDeclare("rabbitMq_direct", true, false, false, null);
        channel.exchangeDeclare("rabbitMq_direct_pc", "direct", true, false, null);
        channel.queueBind("rabbitMq_direct", "rabbitMq_direct_pc", "key.pc");
        System.out.println("开始发送消息");
        for (int i = 1; i < 10 ; i++) {
            String msg = "rabbitMq_direct---:" + i;
            System.out.println("生产者投递消息:" + msg);
            channel.basicPublish("rabbitMq_direct_pc", "key.pc", null, msg.getBytes());

            //等待RabbitMq的确认消息
            try {
                /**
                 * waitForConfirmsOrDie有几个重载的方法,可以自定timeout超时时间,超时后会抛TimeoutException。
                 * 类似的有几个waitForConfirmsOrDie方法,Broker端在返回nack(Basic.Nack)之后该方法会抛出java.io.IOException。
                 * 需要根据异常类型来做区别处理理, TimeoutException超时是属于第三状态(无法确定成功还是失败),
                 * 而返回Basic.Nack抛出IOException这种是明确的失败。
                 * 但是这种方式实际上还是同步阻塞模式的,性能并不不 是太好。
                 */
                channel.waitForConfirmsOrDie(5_000);
                System.out.println("消息被确认:message = " + msg);
            } catch (IOException e) {
                e.printStackTrace();
                System.err.println("消息被拒绝! message = " + msg);
            } catch (IllegalStateException e) {
                e.printStackTrace();
                System.err.println("当前通道不是发送方确认通道");
            } catch (TimeoutException e) {
                e.printStackTrace();
                System.err.println("等待消息确认超时! message = " + msg);
            }
        }
        //MqConnectionFactory.close(connection, channel);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

每次发一条就需要等待确认,效率很差,可以使用回调的方法异步来处理Broker的响应addConfirmListener 方法可以添加ConfirmListener 这个回调接口,这个 ConfirmListener 接口包含两个方法:handleAck 和handleNack,分别用来处理 RabbitMQ 回传的 Basic.Ack 和 Basic.Nack。

    public static void main(String[] args) {
        try {
            Connection connection = MqConnectionFactory.getConnection();
            Channel channel = connection.createChannel();
            //向RabbitMq服务器发送AMQP指令,将当前通道标记为发送方确认通道
            AMQP.Confirm.SelectOk selectOk = channel.confirmSelect();

            channel.queueDeclare("rabbitMq_direct", true, false, false, null);
            channel.exchangeDeclare("rabbitMq_direct_pc", "direct", true, false, null);
            channel.queueBind("rabbitMq_direct", "rabbitMq_direct_pc", "key.pc");

            ConcurrentNavigableMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
            /**
             *监听成功 ack
             * @param deliveryTag 计数的,每次都是从1开始
             * @param multiple   true的时候表示小于等于deliveryTag的消息都得到了确认
             * @throws IOException
             */
            ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
                if (multiple) {
                    //返回给定小于给定键的映射视图
                    final ConcurrentNavigableMap<Long, String> headMap = outstandingConfirms.headMap(deliveryTag, true);
                    //清空outstandingConfirms这个集合中的headMap集合,也就是清空被确认的消息
                    headMap.clear();
                    System.out.println("小于等于 " + deliveryTag + " 的消息都被 确认了");
                } else {
                    //移除已经被确认的消息
                    outstandingConfirms.remove(deliveryTag);
                    System.out.println("编号为:" + deliveryTag + " 的消息被确认");
                }
            };
            /**
             * 监听不成功的消息 nack
             */
            ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
                if (multiple) {
                    System.out.println("小于等于 " + deliveryTag + " 的消息不确认");
                } else {
                    System.out.println("编号为:" + deliveryTag + " 的消息不确认");
                }
            };

            //监听未确认的消息
            channel.addConfirmListener(ackCallback,nackCallback);

//            channel.addConfirmListener(new ConfirmListener() {
//                //消息正确到达broker
//                @Override
//                public void handleAck(long deliveryTag, boolean multiple) throws IOException {
//                    System.out.println("已收到消息");
//                    //做一些其他处理
//                }
//                //RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息
//                @Override
//                public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//                    System.out.println("未确认消息,标识:" + deliveryTag);
//                    //做一些其他处理,比如消息重发等
//                }
//            });
            for (int i = 0; i < 1000; i++) {
                String msg = "rabbitMq_direct---:" + i;
                //获取下一条即将发送的消息ID
                long nextNo = channel.getNextPublishSeqNo();
                channel.basicPublish("rabbitMq_direct_pc", "key.pc", null, msg.getBytes());
                System.out.println("序列号为:" + nextNo + "的消息已经发 送了:" + msg);
                outstandingConfirms.put(nextNo, msg);
            }
            //等待消息被确认
            //MqConnectionFactory.close(connection, channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

return

return代表消息被broker正常接受(ack)后,但是broker没有对应的队列进行投递时产生的状态,消息会被回退给生产者。

            channel.addReturnListener(new ReturnCallback() {
                @Override
                public void handle(Return returnMessage) {
                    System.err.println("消息被退回:"+ JSON.toJSONString(returnMessage));
                }
            });

消费端确认机制

前面虽然有了生产者确认机制和消息的持久化机制。但是依然无法保证整个过程的可靠性。如果消息被消费过程中业务处理失败但是业务已经被标记为消费,那么这个就可丢失消息一样了。RabbitMq在消费端存在ACK机制,即消费端消费消息之后发送Ack报文给Broker端,告知自己已经消费完成,否词可能会一直重发消息直至消息过期(AUTO模式)。

一般处理消费端消息可靠性有三种手段:

  1. 采用NONE模式,消费的过程中自行捕获异常,引发异常后直接记录日志并落到异常恢复表,再通过后台定时任务扫描异常恢复表尝试做重试动作。如果业务不自行处理则有丢失数据的风险。
  2. 采用AUTO(自动Ack)模式,不主动捕获异常,当消费过程中出现异常时会将消息放回Queue中,然后消息会被重新分配到其他消费者节点(如果没有则还是选择当前节点)重新被消费,默认会一直重发消息并直到消费完成返回Ack或者一直到过期
  3. 采用MANUAL(手动Ack)模式,消费者自行控制流程并手动调用channel相关的方法返回Ack
//                // 手动应答 模式 告诉给队列服务器 已经处理成功
                channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
//                //消息标签,不确认是多个消息还是一个消息,是否重新入列。用于拒绝多条消息
                channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false,true);
//                //用于拒绝一条消息,对于不确认的消息是否入列然后重发
                channel.basicReject(delivery.getEnvelope().getDeliveryTag(),true);

//SpringBoot  ackMode = "MANUAL" ackMode = "NONE"
    @RabbitListener(queues = "", ackMode = "AUTO")
    public void getMsg(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, @Payload String msg) {
        System.out.println("msg: " + msg);
        try {// 手动ack,deliveryTag表示消息的唯一标志,multiple表示是否是批量确认
            channel.basicAck(deliveryTag, false);
            // 手动nack,告诉broker消费者处理失败,最后一个参数表示是否需要将消息重新入列
            channel.basicNack(deliveryTag, false, true);
            // 手动拒绝消息。第二个参数表示是否重新入列
            channel.basicReject(deliveryTag, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//yml配置
    spring:
  rabbitmq:
    listener:
      simple:
        retry:
          #最大重试次数 spring.rabbitm
          max-attempts: 5
          #是否开启消费者重试(为false时关闭消费者重试,意思不是“不重试”,而是一直收到消息直到jack 确认或者一直到超时)
          enabled: true
          #重试间隔时间(单位毫秒)
          initial-interval: 5000
          # 重试超过最大次数后是否拒绝
        default-requeue-rejected: false
        #ack模式
        acknowledge-mode: manual

消息的传输保障一般分为三个层级:

  1. At most once:最多一次。消息可能会丢失,但绝不会重复传输
  2. At least once:最少一次。消息绝不会丢失,但可能会重复传输
  3. Exactly once:恰好一次。每条消息肯定会被传输一次且仅传输一次

RabbitMQ 支持其中的“最多一次”和“最少一次”。

最少一次的投递实现需要考虑几个内容:

  1. 消息生产者需要开启事务机制或者publisher confirm 机制,以确保消息可以可靠地传输到RabbitMQ 中。
  2. 消息生产者需要配合使用 mandatory 参数或者备份交换器来确保消息能够从交换器路由到队列中,进而能够保存下来而不会被丢弃。
  3. 消息和队列都需要进行持久化处理,以确保RabbitMQ 服务器在遇到异常情况时不会造成消息丢失。
  4. 消费者在消费消息的同时需要将autoAck 设置为false,然后通过手动确认的方式去确认已经正确消费的消息,以避免在消费端引起不必要的消息丢失。

最多一次

最多一次的方式就无需考虑上面的问题,生产者随意发送,消费者随意消费。但是这样不能保证消息不丢失。

恰好一次

恰好一次是RabbitMq目前无法保证的。举两个示例:

  1. 消费者在消费完一条消息之后向RabbitMQ 发送确认Basic.Ack 命令,此时由于网络断开或者其他原因造成RabbitMQ 并没有收到这个确认命令,那RabbitMQ 不会将此条消息标记删除。在重新建立连接之后,消费者还是会消费到这一条消息,这就造成了重复消费。
  2. 生产者在使用publisher confirm机制的时候,发送完一条消息等待RabbitMQ返回确认通知,此时网络断开,生产者捕获到异常情况,为了确保消息可靠性选择重新发送,这样RabbitMQ 中就有两条同样的消息,在消费的时候消费者就会重复消费。

持久化存储机制

持久化是提高RabbitMQ可靠性的基础,否则当RabbitMQ遇到异常时(如:重启、断电、停机等)数据将会丢失。主要从以下几个方面来保障消息的持久性:

  1. Exchange的持久化。通过定义时设置durable 参数为ture来保证Exchange相关的元数据不不丢失。
  2. Queue的持久化。也是通过定义时设置durable 参数为ture来保证Queue相关的元数据不不丢失。
  3. 消息的持久化。通过将消息的投递模式 (BasicProperties 中的 deliveryMode 属性)设置为 2即可实现消息的持久化,保证消息自身不丢失。

在这里插入图片描述

RabbitMQ中的持久化消息都需要写入磁盘(当系统内存不不足时,非持久化的消息也会被刷盘处理),这些处理理动作都是在“持久层”中完成的。持久层是一个逻辑上的概念,实际包含两个部分:

  1. 队列索引(rabbit_queue_index),rabbit_queue_index 负责维护Queue中消息的信息,包括消息的存储位置、是否已交给消费者、是否已被消费及Ack确认等,每个Queue都有与之对应的rabbit_queue_index。
  2. 消息存储(rabbit_msg_store),rabbit_msg_store 以键值对的形式存储消息,它被所有队列列共享,在每个节点中有且只有一个。

RabbitMQ消息有两种类型:

  1. 持久化消息和非持久化消息。
  2. 这两种消息都会被写入磁盘。

持久化消息在到达队列时写入磁盘,同时会内存中保存一份备份,当内存吃紧时,消息从内存中清除。这会提高一定的性能。

非持久化消息一般只存于内存中,当内存压力大时数据刷盘处理,以节省内存空间。

RabbitMQ存储层包含两个部分:队列索引和消息存储。

在这里插入图片描述

​ 当一个非持久化消息过来,首先保存到indexFile和storeFile里面,如果当消费者消费慢或者不在线的时候,且内存压力大的时候会进行刷盘处理,保存到msg_srore_transient里面

​ 当一个持久化消息过来,首先保存到indexFile和storeFile里面,也会保存到msg_srore_persistent里面

​ 消息存储会将消息头和消息体全部保存起来。

/var/lib/rabbitmq/mnesia/HOSTNAME/msg_stores/vhosts/$VHostId这个路路径下包含 queues、msg_store_persistent、msg_store_transient 这 3 个目录,这是实际存储消息的位置。其中queues目录中保存着rabbit_queue_index相关的数据,而msg_store_persistent保存着持久化消息数据,msg_store_transient保存着⾮非持久化相关的数据。

另外,RabbitMQ通过配置queue_index_embed_msgs_below可以根据消息大小决定存储位置,默认queue_index_embed_msgs_below是4096字节(包含消息体、属性及headers),小于该值的消息存在rabbit_queue_index中。

队列索引:rabbit_queue_index

索引维护队列的落盘消息的信息,如存储地点,是否已经被消费者接收,是否已被消费者ack等
在这里插入图片描述

​ 索引使用顺序的段文件来存储,后缀为.idx,文件名从0开始累加,每个段文件中固定包segment_entry_count 条记录,默认值是16384。也会存储消息,当消息小于4K的时候会存储到索引中去,加快查询,不需要在进行数据文件的查询。当加载索引文件的时候也会将消息放入到内存中去,可以通过参数queue_index_embed_msgs_below 来控制小于多少将消息保存到索引文件中。设置的时候也需要注意,一点点增大也可能会引起内存爆炸式增长。
在这里插入图片描述

消息存储:rabbit_msg_store

​ 消息以键值对的形式存储到文件中,一个虚拟主机上的所有队列使用同一块存储,每个节点只有一个。存储分为持久化存储(msg_store_persistent)和短暂存储(msg_store_transient)。持久化存储的内容在broker重启后不会丢失,短暂存储的内容在broker重启后丢失。

​ store使用文件来存储,后缀为.rdq,经过store处理的所有消息都会以追加的方式写入到该文件中,当该文件的大小超过指定的限制(file_size_limit)后,将会关闭该文件并创建一个新的文件以供新的消息写入。文件名从0开始进行累加。在进行消息的存储时,RabbitMQ会在ETS(Erlang TermStorage)表中记录消息在文件中的位置映射和文件的相关信息。
在这里插入图片描述

​ 消息(包括消息头、消息体、属性)可以直接存储在index中,也可以存储在store中。最佳的方式是较小的消息存在index中,而较大的消息存在store中。这个消息大小的界定可以通过queue_index_embed_msgs_below 来配置,默认值为4096B。当一个消息小于设定的大小阈值时,就可以存储在index中,这样性能上可以得到优化。一个完整的消息大小小于这个值,就放到索引中,否则放到持久化消息文件中(存储于msg_store_persistent目录中的.rdq文件中)。

读取消息时,先根据消息的ID(msg_id)找到对应存储的文件,如果文件存在并且未被锁住,则直接打开文件,从指定位置读取消息内容。如果文件不存在或者被锁住了,则发送请求由store进行处理。

​ 删除消息时,只是从ETS表删除指定消息的相关信息,同时更新消息对应的存储文件和相关信息。在执行消息删除操作时,并不立即对文件中的消息进行删除,也就是说消息依然在文件中,仅仅是标记为垃圾数据而已。当一个文件中都是垃圾数据时可以将这个文件删除。当检测到前后两个文件中的有效数据可以合并成一个文件,并且所有的垃圾数据的大小和所有文件(至少有3个文件存在的情况下)的数据大小的比值超过设置的阈值garbage_fraction(默认值0.5)时,才会触发垃圾回收,将这两个文件合并,执行合并的两个文件一定是逻辑上相邻的两个文件。合并逻辑:

  1. 锁定这两个文件
  2. 先整理前面的文件的有效数据,再整理后面的文件的有效数据
  3. 将后面文件的有效数据写入到前面的文件中
  4. 更新消息在ETS表中的记录
  5. 删除后面文件
    在这里插入图片描述

队列结构

​ 通常队列由rabbit_amqqueue_process和backing_queue这两部分组成,rabbit_amqqueue_process负责协议相关的消息处理,即接收生产者发布的消息、向消费者交付消息、处理消息的确认(包括生产端的confirm和消费端的ack)等。backing_queue是消息存储的具体形式和引擎,并向rabbit_amqqueue_process提供相关的接口以供调用。
在这里插入图片描述

​ 如果消息投递的目的队列是空的,并且有消费者订阅了这个队列,那么该消息会直接发送给消费者,不会经过队列这一步。当消息无法直接投递给消费者时,需要暂时将消息存入队列,以便重新投递。

rabbit_variable_queue.erl 源码中定义了RabbitMQ队列的4种状态:

  1. alpha:消息索引和消息内容都存内存,最耗内存,很少消耗CPU
  2. beta:消息索引存内存,消息内存存磁盘
  3. gama:消息索引内存和磁盘都有,消息内容存磁盘
  4. delta:消息索引和内容都存磁盘,基本不消耗内存,消耗更多CPU和I/O操作

消息存入队列后,不是固定不变的,它会随着系统的负载在队列中不断流动,消息的状态会不断发

送变化。

​ 持久化的消息,索引和内容都必须先保存在磁盘上,才会处于上述状态中的一种

​ gama状态只有持久化消息才会有的状态。

​ 对于普通没有设置优先级和镜像的队列来说,backing_queue的默认实现是rabbit_variable_queue,其内部通过5个子队列Q1、Q2、delta、Q3、Q4来体现消息的各个状态。

在运行时,RabbitMQ会根据消息传递的速度定期计算一个当前内存中能够保存的最大消息数量(target_ram_count),如果alpha状态的消息数量大于此值,则会引起消息的状态转换,多余的消息可能会转换到beta、gama或者delta状态。区分这4种状态的主要作用是满足不同的内存和CPU需求。

在这里插入图片描述

消费者获取消息也会引起消息的状态转换。当消费者获取消息时

  1. 首先会从Q4中获取消息,如果获取成功则返回。
  2. 如果Q4为空,则尝试从Q3中获取消息,系统首先会判断Q3是否为空,如果为空则返回队列为空,即此时队列中无消息。
  3. 如果Q3不为空,则取出Q3中的消息;进而再判断此时Q3和Delta中的长度,如果都为空,则可以认为 Q2、Delta、 Q3、Q4 全部为空,此时将Q1中的消息直接转移至Q4,下次直接从Q4 中获取消息。
  4. 如果Q3为空,Delta不为空,则将Delta的消息转移至Q3中,下次可以直接从Q3中获取消息。在将消息从Delta转移到Q3的过程中,是按照索引分段读取的,首先读取某一段,然后判断读取的消息的个数与Delta中消息的个数是否相等,如果相等,则可以判定此时Delta中己无消息,则直接将Q2和刚读取到的消息一并放入到Q3中,如果不相等,仅将此次读取到的消息转移到Q3。

这里就有两处疑问,第一个疑问是:为什么Q3为空则可以认定整个队列为空?

  1. 如果Q3为空,Delta不为空,那么在Q3取出最后一条消息的时候,Delta 上的消息就会被转移到Q3这样与 Q3 为空矛盾;
  2. 如果Delta 为空且Q2不为空,则在Q3取出最后一条消息时会将Q2的消息并入到Q3中,这样也与Q3为空矛盾;
  3. 在Q3取出最后一条消息之后,如果Q2、Delta、Q3都为空,且Q1不为空时,则Q1的消息会被转移到Q4,这与Q4为空矛盾.
  4. 其实这一番论述也解释了另一个问题:为什么Q3和Delta都为空时,则可以认为 Q2、Delta、Q3、Q4全部为空?

通常在负载正常时,如果消费速度大于生产速度,对于不需要保证可靠不丢失的消息来说,极有可能只会处于alpha状态。

对于持久化消息,它一定会进入gamma状态,在开启publisher confirm机制时,只有到了gamma 状态时才会确认该消息己被接收,若消息消费速度足够快、内存也充足,这些消息也不会继续走到下一个状态。

为什么消息的堆积导致性能下降?

在系统负载较高时,消息若不能很快被消费掉,这些消息就会进入到很深的队列中去,这样会增加处理每个消息的平均开销。因为要花更多的时间和资源处理“堆积”的消息,如此用来处理新流入的消息的能力就会降低,使得后流入的消息又被积压到很深的队列中,继续增大处理每个消息的平均开销,继而情况变得越来越恶化,使得系统的处理能力大大降低。一般解决方案有三种

  1. 增加prefetch_count的值,即一次发送多条消息给消费者,加快消息被消费的速度。
  2. 采用multiple ack,降低处理 ack 带来的开销
  3. 流量控制

消息幂等

  1. 借助数据库唯一索引,重复插入直接报错,事务回滚。
  2. 唯一Id机制,比较通用的方式。对于每条消息我们都可以生成唯一Id,消费前判断Tair中是否存在(MsgId做Tair排他锁的key),消费成功后将状态写入Tair中,这样就可以防止重复消费了。

消息积压

消费者宕机、消费者消费能力不足、生产者发送流量太大

上线更多的消费者进行正常的消费。或者上线一批专门将消息消费之后落库,然后业务从数据库中读取出来,在慢慢处理。

消费端限流

当消息投递速度远大于消费速度的时候,随着时间的积累会出现消息积压。虽然消息中间件本身具备一定的缓冲能力,但是这个能力也是有一定容量限制的,长期运行且没有得到处理,最终会导致Broker崩溃。

可以通过如下手段来防止因为消息积压导致的Broker崩溃

  1. RabbitMQ 可以对内存和磁盘使用量设置阈值,当达到阈值后,生产者将被阻塞(block),直到对应项指标恢复正常。全局上可以防止超大流量、消息积压等导致的Broker被压垮。当内存受限或磁盘可用空间受限的时候,服务器都会暂时阻止连接,服务器将暂停从发布消息的已连接客户端的套接字读取数据。连接心跳监视也将被禁用。所有网络连接将在rabbitmqctl和管理插件中显示为“已阻止”,这意味着它们尚未尝试发布,因此可以继续或被阻止,这意味着它们已发布,现在已暂停。兼容的客户端被阻止时将收到通知。
    在这里插入图片描述

  2. RabbitMQ 还默认提供了一种基于credit flow流控机制,面向每一个连接进行流控。当单个队列达到最大流速时,或者多个队列达到总流速时,都会触发流控。触发单个链接的流控可能是因为connection、channel、queue的某一个过程处于flow状态,这些状态都可以从监控平台看到。

在这里插入图片描述

  1. RabbitMQ中有一种QoS保证机制,可以限制Channel上接收到的未被Ack的消息数量,如果超过这个数量限制RabbitMQ将不会再往消费端推送消息。这是一种流控手段,可以防止大量消息瞬时从Broker送达消费端造成消费端巨大压力(甚至压垮消费端)。比较值得注意的是QoS机制仅对于消费端推模式有效,对拉模式无效。而且不支持NONE Ack模式。执行channel.basicConsume 方法之前通过 channel.basicQoS 方法可以设置该数量。消息的发送是异步的,消息的确认也是异步的。在消费者消费慢的时候,可以设置Qos的prefetchCount,它表示broker在向消费者发送消息的时候,一旦发送了prefetchCount个消息而没有一个消息确认的时候,就停止发送。消费者确认一个,broker就发送一个,确认两个就发送两个。换句话说,消费者确认多少,broker就发送多少,消费者等待处理的个数永远限制在prefetchCount个。如果对于每个消息都发送确认,增加了网络流量,此时可以批量确认消息。如果设置了multiple为true,消费者在确认的时候,比如说id是8的消息确认了,则在8之前的所有消息都确认了。

一般来说,生产者希望自己的消息快速投递出去,但是投递太快就会导致消息积压,所以在生产者中也可以加入限流,应急开关等手段,防止超过Broker端的极限承载能力或者压垮下游消费者。

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值