由于生产者和消费者不直接通信,生产者只负责把消息发送到队列,消费者只负责从队列获取消息(不管是push还是pull).

消息被"消费"后,是需要从队列中删除的.那怎么确认消息被"成功消费"了呢?

是消费者从队列获取到消息后,broker 就从队列中删除该消息?

那如果消费者收到消息后,还没来得及"消费"它,或者说还没来得及进行业务逻辑处理时,消费者所在的信道或者连接因某种原因断开了,

那这条消息岂不是就被无情的抛弃了...

我们更期望的是,消费者从队列获取到消息后,broker 暂时不删除该条消息,

等到消费者"成功消费"掉该消息后,再删除它.

所以需要一个机制来确认生产者发送的消息被消费者"成功消费".

RabbitMQ 提供了一种叫做"消费者确认"的机制.

消费者确认

消费者确认分两种:自动确认手动确认.

在自动确认模式中,消息在发送到消费者后即被认为"成功消费".这种模式可以降低吞吐量(只要消费者可以跟上),以降低交付和消费者处理的安全性.这种模式通常被称为“即发即忘”.与手动确认模型不同,如果消费者的TCP连接或通道在真正的"成功消费"之前关闭,则服务器发送的消息将丢失.因此,自动消息确认应被视为不安全,并不适用于所有工作负载.

使用自动确认模式时需要考虑的另一件事是消费者过载.手动确认模式通常与有界信道预取(BasicQos方法)一起使用,该预取限制了信道上未完成(“进行中”)的消息的数量.但是,自动确认没有这种限制.因此,消费者可能会被消息的发送速度所淹没,可能会导致消息积压并耗尽堆或使操作系统终止其进程.某些客户端库将应用TCP反压(停止从套接字读取,直到未处理的交付积压超过某个限制).因此,仅建议能够以稳定的速度有效处理消息的消费者使用自动确认模式.

1.自动确认 autoAck : true

下面是消费者的部分代码,我们故意每次只推送一条消息,并且让每条消息的处理都超过10秒.

            channel.BasicQos(0, 1, false);//将Qos预取值设置为1,这表示设置broker每次只推送队列里面的一条消息到消费者,只有在确认这条消息"成功消费"后,才会继续推送
consumer.Received += (s, e) =>
{
string str = Encoding.Default.GetString(e.Body);
Thread.Sleep(10);
Console.WriteLine("consumer1 receive : " + str);
};
channel.BasicConsume(queue: QueueName, autoAck: true, consumer: consumer);

下面是生产者的部分代码

                for (byte i = ; i < ; i++)
{
string msg = "hello world " + i;
channel.BasicPublish("", QueueName, null, Encoding.Default.GetBytes(msg));
Console.WriteLine($"send {msg}");
}

运行结果:

   

从管理后台可以看到,消费者还没打印"receive"那句话,该队列中就已经没有任何消息了.

2.手动确认 autoAck : false

手动确认又分两种:肯定确认否定确认.

1)肯定确认 BasicAck

消费者部分代码:

            channel.BasicQos(, , false);//设置broker每次只推送队列里面的一条消息到消费者,只有在确认这条消息"成功消费"后,才会继续推送
consumer.Received += (s, e) =>
{
string str = Encoding.Default.GetString(e.Body);
Console.WriteLine("consumer1 receive : " + str);
Thread.Sleep();
//deliveryTag 传递标签,ulong 类型.它的范围隶属于每个信道.因此必须在收到消息的相同信道上确认.不同的信道将导致“未知的传递标签”协议异常并关闭通道.
//multiple 确认一条消息还是多条.false 表示只确认 e.DelivertTag 这条消息,true表示确认 小于等于 e.DelivertTag 的所有消息
channel.BasicAck(deliveryTag: e.DeliveryTag, multiple: false);
Console.WriteLine("consumer1 Ack : " + str);
};
channel.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer);

生产者代码不变.

当消费者收到一条消息,但是还没有肯定确认时,从管理后台可以清晰的看到,队列中一共有5条消息,其中4条尚未推送,1条已经推送但尚未确认.

         

当消费者确认后(立马又接收了一条),这时候,队列中一共只有4条了,"成功消费"的那条已经被broker从队列中删掉了.剩余3条尚未推送,1条已推送但尚未确认.

         

2)否定确认  BasicNack , BasicReject

否定确认的场景不多,但有时候某个消费者因为某种原因无法立即处理某条消息时,就需要否定确认了.

否定确认时,需要指定是丢弃掉这条消息,还是让这条消息重新排队,过一会再来,又或者是让这条消息重新排队,并尽快让另一个消费者接收并处理它.

i.丢弃  requeue: false

消费者部分代码:

            channel.BasicQos(, , false);
consumer.Received += (s, e) =>
{
string str = Encoding.Default.GetString(e.Body);
Thread.Sleep();
channel.BasicNack(deliveryTag: e.DeliveryTag, multiple: false, requeue: false);
Console.WriteLine("consumer1 Nack : " + str);
};
channel.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer);

ii.重新排队 requeue: true

消费者部分代码:

            channel.BasicQos(, , false);//设置broker每次只从推送队列里面的一条消息到消费者,只有在确认这条消息"成功消费"后,才会继续推送
consumer.Received += (s, e) =>
{
string str = Encoding.Default.GetString(e.Body);
Thread.Sleep();
channel.BasicNack(deliveryTag: e.DeliveryTag, multiple: false, requeue: true);
Console.WriteLine("consumer1 Nack : " + str);
};
channel.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer);

运行结果:

        

可以看到,消费者收到的一直是"hello world 0"这条消息,而管理后台一直显示 4,1,5.这是为什么呢?

首先,我们设置的是每次只推送一条消息给消费者,否定确认中我们选择的是重新排队,所以"hello world 0"这条消息被否定确认后,被broker安排去重新排队了.当消息被重新排队时,如果可能的话,它将被放置在其队列中的原始位置.也就是说"hello world 0"这条消息又被放到了队列头,因为它的原始位置就是队列头.所以结果就变成了消费之一直在消费"hello world 0",并且一直在否定确认.

感觉这种方式的代价是不是有点大...消息重新排队,还要回到之前的位置,还要重新发送一次....感觉代价有点小贵啊...而且其他消息貌似永远只有ready...

但,如果多个消费者共享队列时,该消息将被重新排队到更靠近队列头的位置,并且会被聪明的broker从队列中推送到其他队列.

测试:

我们重新创建两个消费者:consumer1 否定确认,3秒一次;consumer2 肯定确认,1秒一次.两个消费共享一个队列(公平分发)

            channel.BasicQos(, , false);
consumer.Received += (s, e) =>
{
string str = Encoding.Default.GetString(e.Body);
Thread.Sleep();
channel.BasicNack(e.DeliveryTag, false, true);
Console.WriteLine($"{DateTime.Now} consumer1 Nack : " + str);
};
channel.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer);
            channel.BasicQos(, , false);
consumer.Received += (s, e) =>
{
string str = Encoding.Default.GetString(e.Body);
Thread.Sleep();
channel.BasicAck(deliveryTag: e.DeliveryTag, multiple: false);
Console.WriteLine($"{DateTime.Now} consumer2 Ack : " + str);
};
channel.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer);

运行结果:

一切尽在图中.

BasicReject 方法和 BasicNack 方法基本一样,唯一的区别是没有 multiple 这个入参.

消费者确认模式,预取和吞吐量

确认模式和QoS预取值对消费者吞吐量具有显着影响。通常,增加预取将提高向消费者传递消息的速率。自动确认模式可以产生最佳的交付率。但是,在这两种情况下,已传送但尚未处理的消息的数量也将增加,从而增加了消费者的RAM消耗。

应谨慎使用具有无限预取功能的自动确认模式或手动确认模式。在没有确认的情况下消耗大量消息的消费者将导致他们所连接的节点上的内存消耗增长。找到合适的预取值需要不断试验,并且会因工作负载而异。100到300范围内的值通常可提供最佳吞吐量,并且不会面临压倒性消费者的重大风险。较高的价值往往会影响收益递减规律。

预取值1是最保守的。它将显着降低吞吐量,特别是在消费者连接延迟较高的环境中。对于许多应用来说,更高的值是合适的和最佳的。

当消费者失败或失去连接时:自动重新排队

使用手动确认时,除了我们主动让消息重新排队外,任何未确认的消息都将在关闭发生传递的信道(或连接)时自动重新排队。这包括客户端的TCP连接丢失,消费者应用程序(进程)故障和通道级协议异常.请注意,检测不可用的客户端需要一段时间。

由于这种行为,消费者必须准备好处理重新发送,否则就要考虑到幂等性。BasicDeliverEventArgs 有一个特殊的布尔属性 : Redelivered,如果该消息是第一次交付,它将被设置为false.否则为 true.

测试:

还是借用上一个测试的代码,只是分别加了一句话:

Console.WriteLine($"{str} 是否是重复发送 : " + e.Redelivered);

运行结果:

这里要特别注意,consumer2 收到 "hello world 0"的时候, Redelivered 的值依然是 true . 因为 Redelivered 属性的维度是消息,不是消费者.

RabbitMQ (十一) 消息确认机制 - 消费者确认的更多相关文章

  1. RabbitMQ (十二) 消息确认机制 - 发布者确认

    消费者确认解决的问题是确认消息是否被消费者"成功消费". 它有个前提条件,那就是生产者发布的消息已经"成功"发送出去了. 因此还需要一个机制来告诉生产者,你发送 ...

  2. RabbitMQ消息发布和消费的确认机制

    前言 新公司项目使用的消息队列是RabbitMQ,之前其实没有在实际项目上用过RabbitMQ,所以对它的了解都谈不上入门.趁着周末休息的时间也猛补习了一波,写了两个窗体应用,一个消息发布端和消息消费 ...

  3. 消息队列RabbitMQ(三):消息确认机制

    引言 RabbitMQ的模型是生产者发送信息到 Broker (代理),消费者从 Broker 中取出信息.但是生产者怎么知道消息是否真的发送到 Broker 中了呢?Broker 又怎么知道消息到底 ...

  4. RabbitMq之消息确认

    最近阅读了rabbitmq的官方文档,然后结合之前面试时被问到关于消息队列的问题来探索一下关于消息队列的消息确认机制. 其实消息确认就是消费者确认消息被消费了, 生产者确认消息已经发送到了消息队列中了 ...

  5. 7.RabbitMQ--消息确认机制(confirm)

    RabbitMQ--消息确认机制(confirm) Confirm模式 RabbitMQ为了解决生成者不知道消息是否真正到达broker这个问题,采用通过AMQP协议层面为我们提供了事务机制方案,但是 ...

  6. RabbitMQ 消费者的消息确认机制

    消息确认的几点说明: 1. Basic.Ack 发回给 RabbitMQ 以告知,可以将相应 message 从 RabbitMQ 的消息缓存中移除.2. Basic.Ack 未被 consumer ...

  7. RabbitMQ 消息确认机制

    消息确认机制 在之前异常处理部分就已经写了,对于consumer的异常退出导致消息丢失,可以时候consumer的消息确认机制.重复的就不说了,这里说一些不一样的. consumer的消息确认机制 当 ...

  8. (转)RabbitMQ消息队列(九):Publisher的消息确认机制

    在前面的文章中提到了queue和consumer之间的消息确认机制:通过设置ack.那么Publisher能不到知道他post的Message有没有到达queue,甚至更近一步,是否被某个Consum ...

  9. RabbitMQ消息队列(九):Publisher的消息确认机制

    在前面的文章中提到了queue和consumer之间的消息确认机制:通过设置ack.那么Publisher能不到知道他post的Message有没有到达queue,甚至更近一步,是否被某个Consum ...

随机推荐

  1. webservice 针对WebService服务,客户端调用时报序列化的最大项数maxItemsInObjectGraph超过65536问题

    今天在使用webservice服务时候,报异常“The InnerException message was 'Maximum number of items that can be serializ ...

  2. Jmeter-8-FTP测试

    1. 此处要深刻理解FTP的用法. 2. Get的时候填写的Remote File 路径/, 此处是相对路径. 实际为/home/user/ 3. Local file 此处要写到具体的文件. 4. ...

  3. 【BZOJ4869】相逢是问候 [线段树][欧拉定理]

    相逢是问候 Time Limit: 40 Sec  Memory Limit: 512 MB[Submit][Status][Discuss] Description Informatikverbin ...

  4. 【洛谷 P1419】 寻找段落(二分答案,单调队列)

    题目链接 开始还以为是尺取.发现行不通. 一看标签二分答案,恍然大悟. 二分一个\(mid\)(实数),把数列里每个数减去\(mid\),然后求前缀和,在用单调队列维护\(sum[i-t\text{~ ...

  5. Call Mode feature

    起源 user 在插著 充電器 打電話的狀況下, 為了安全起見, 避免 充電器在這時損害手機,間接造成 user 的傷害, 而有了這 feature, 在 battery voltage Vbat & ...

  6. monkey测试===ios-monkey测试工具

    iOSmonkey测试工具: crashmonkey 特点: 支持**真机测试.模拟器测试** 支持收集**系统日志(Systemlog)**.**崩溃日志(Crashlog)**.***instru ...

  7. sicily 1009. Mersenne Composite N

    Description One of the world-wide cooperative computing tasks is the "Grand Internet Mersenne P ...

  8. python安装基础

    . python安装 //先查看是否存在python的包,如果没有,那可以用yum或去python的官网安装 [root@localhost ~]# rpm -qa|grep python pytho ...

  9. dubbo支持的远程调用方式

    dubbo RPC(二进制序列化 + tcp协议).http invoker(二进制序列化 + http协议,至少在开源版本没发现对文本序列化的支持).hessian(二进制序列化 + http协议) ...

  10. CentOS 7下安装php-redis扩展及简单使用

    前言: 在本篇文章中,我将给大家介绍如何在CentOS7上安装PHP-Redis扩展以及一些简单的实用,关于如何在Centos上安装redis的,可以参考 Redis在CentOS 7上的安装部署   ...