rabbitmq之消息应答 , 手动应答 与 自动应答

什么是消息应答

  消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成 了部分突然它挂掉了,会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息,便立即将该消 息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续 发送给该消费这的消息,因为它无法接收到。 为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制,消息应答就是:消费者在接收 到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。

  消费者收到的每一条消息都必须进行确认。消息确认后,RabbitMQ才会从队列删除这条消息,RabbitMQ不会为未确认的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。

消息应答方式

   RabbitMQ中消息的应答,共有两种方式:

        自动应答

        手动应答

 

自动应答

    消费者在消费消息时,可以指定autoAck参数,当autoAck=true时,一旦消费者接收到了消息,就视为自动应答了消息。

    RabbitMQ中声明消费者处理业务逻辑之前自动确认就已经完成了(推送Consume、消息拉取Ge都是如此),可由下面代码进行测试,如下:


    上述代码我们采用了自动确认,并且在业务处理的最后手动抛出异常( 模拟出错 ),然后我们进行测试,发现重启消费者后不会再收到消息( 本来是应该再次接收到的,因为我们模拟出错了,这条消息应该算是消费失败的 ),说明该消息已经被消费者消费掉并确认了,RabbitMQ已经将其移除了,我们模拟出错了 但是消息仍然被移除了 , 可以理解为消息丢失了 , 自动确认优点是快 , 缺点是有丢失消息的风险

手动应答

  手动应答分为三种情况: 

      (1) 手动不确认

      channel.basicNack(deliveryTag, false, true);
        basic.nack方法为不确认deliveryTag对应的消息,第二个参数是否应用于多消息(这个参数不细讲了 ,不建议使用 , 有丢失消息风险),第三个参数是否requeue,与basic.reject区别就是同时支持多个消息,可以nack该消费者先前接收未ack的所有消息。nack后的消息也会被自己消费到。

      (2) 手动拒绝

      channel.basicReject(deliveryTag, true);
        basic.reject方法拒绝deliveryTag对应的消息,第二个参数是否requeue,true则重新入队列,否则丢弃或者进入死信队列。

        该方法reject后,该消费者还是会消费到该条被reject的消息。

    (3) 手动确认应答

      从手动应答的介绍,我们发现在自动确认机制,如果消费者在处理消息的过程中,出了错,就没有什么办法重新处理这条消息,所以我们很多时候,需要在消息处理成功后,再确认消息,这就需要手动确认。

      当autoAck=false时,RabbitMQ会等待消费者手动发回ack信号后 , 才从内存(和磁盘,如果是持久化消息的话)中移除消息。

      采用消息确认机制后,只要令autoAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者手动调用channel.basicAck为止。

      当autoAck=false时,对于RabbitMQ服务器端而言,如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。

      这里我们启动了手动确认后,就必须调用channel.basicAck方法进行确认,否则的话RabbitMQ会一直进行等待,当我们这个消费者关闭后,RabbitMQ会将该条消息再发给对应的消费者进行消费,直到有消费者对该条消息进行消费并应答完成。

 

        看到上述对消息的处理后,我们还发现一个问题,如果channel.basicAck收到确认前的代码有问题,会抛出异常无法进行手动确认怎么办,一般消费者也不会连接中断,那么该消息就一直无法被处理,连被其他消费者处理的机会都没有

        所以一般我们会进行try-catch处理,处理成功则手动确认,失败或有异常则拒绝。

 

 

 

 

 

上面抄袭自 : https://blog.csdn.net/newbie0107/article/details/106820615

 

下面是我觉得需要补充的几点:

  1)上面的例子中有一点 ,我觉得非常重要 .

    "在手动应答的情况下 ,  如果channel.basicAck收到确认前的代码有问题,会抛出异常,导致无法进行手动确认,一般消费者也不会连接中断,那么该消息就一直无法被处理,连被其他消费者处理的机会都没有,所以一般我们会进行try-catch处理,处理成功则手动确认,失败或有异常则拒绝。"

    这点我自己写了例子验证过的,确实是这样, 所以一定要进行try-catch处理 ,不然这个消费者就会卡住不动 ,神马操作都不做 ,也不会往后继续消费别的消息.

  2) 我看完博客一直有疑问 , 在手动应答的情景中 , 如果程序都没有问题 , 但是我就是一直不确认 , 会发生什么事情,

    结果就是如下图:

    

 

     队列还是会被消费, 只是从ready状态转换成了 unacked状态 , 而且当前消费者 ,消费完这200个后,不会因为这200个处于unacked状态 , 就重复再把这200个再消费一边 , 但是 !!! 如果重新打开一个新的消费者(或者重启当前消费者) , 这个新的消费者会把这200个unacked的再消费一遍 , 这个新消费者消费完这200个之后 , 也就停住脚步了 , 等待新的消息进来, 不会一遍又一遍的重复消费这200个

 

    3)

     手动确认模式,消息手动拒绝中如果requeue为true会重新放入队列,但是如果消费者在处理过程中一直抛出异常,会导致入队-》拒绝-》入队的循环,该怎么处理呢?

        第一种方法是根据异常类型来选择是否重新放入队列。

        第二种方法是先成功确认,然后通过channel.basicPublish()重新发布这个消息。重新发布的消息网上说会放到队列后面,进而不会影响已经进入队列的消息处理。

      

推荐一篇关于rabbitmq消息应答的特别有意思的博客

https://www.cnblogs.com/englefly/p/8435998.html

posted @ 2021-11-11 16:40  代达罗斯之殇  阅读(1468)  评论(0编辑  收藏  举报