关于 RabbitMQ 中 consumer 侧的 ack 属性分析

http://my.oschina.net/moooofly/blog/143883

      熟悉 RabbitMQ 的人肯定知道  no_ack 属性是在调用 Basic.Consume 方法时可以设置的一个重要参数。本文主要针对 no_ack 设置的两种情况,通过抓包分析的形式讲解下实际应用中的异同,并总结一下相关的处理经验。

============ 我是分隔线 =============

      no_ack 的用途:确保 message 被 consumer “成功”处理了。这里“成功”的意思是,(在设置了 no_ack=false 的情况下)只要 consumer 手动应答了 Basic.Ack ,就算其“成功”处理了。

情况一:no_ack=true (此时为自动应答
      在这种情况下,consumer 会在接收到 Basic.Deliver + Content-Header + Content-Body 之后,立即回复 Ack 。而这个 Ack 是 TCP 协议中的 Ack 。此 Ack 的回复不关心 consumer 是否对接收到的数据进行了处理,当然也不关心处理数据所需要的耗时。

图1:(Producer+Consumer)


图2:(Consumer)


图3:(Producer)


情况二:no_ack=false (此时为手动应答
      在这种情况下,要求 consumer 在处理完接收到的 Basic.Deliver + Content-Header + Content-Body 之后才回复 Ack 。而这个 Ack 是 AMQP 协议中的 Basic.Ack 。此 Ack 的回复是和业务处理相关的,所以具体的回复时间应该要取决于业务处理的耗时。

图4:(Producer+Consumer)


图5:(Consumer)



图6:(Producer)


总结:
  • Basic.Ack 发回给 RabbitMQ 以告知,可以将相应 message 从 RabbitMQ 的消息缓存中移除。
  • Basic.Ack 未被 consumer 发回给 RabbitMQ 前出现了异常,RabbitMQ 发现与该 consumer 对应的连接被断开,之后将该 message 以轮询方式发送给其他 consumer (假设存在多个 consumer 订阅同一个 queue)。
  • 在 no_ack=true 的情况下,RabbitMQ 认为 message 一旦被 deliver 出去了,就已被确认了,所以会立即将缓存中的 message 删除。所以在 consumer 异常时会导致消息丢失。
  • 来自 consumer 侧的 Basic.Ack 与 发送给 Producer 侧的 Basic.Ack 没有直接关系。


============ 我是分隔线 =============

最后贴上自己改造的、基于 libevent 实现的 rabbitmq-c 的测试打印。

情况一:
[warn] evsignal_init: socketpair: No error
drive_machine: [conn_init]  ---  in TCP 3-way connecting!
drive_machine: [conn_connecting]  ---  connection timeout 1 time on socket(6040)
drive_machine: [conn_connected]  ---  connected on socket(6040)

6040: conn_state change   connected ==> snd_protocol_header
  --> Send Protocol.Header!
6040: conn_state change   snd_protocol_header ==> rcv_connection_start_method
  <-- Recv Connection.Start Method frame!
6040: conn_state change   rcv_connection_start_method ==> snd_connection_start_rsp_method
  --> Send Connection.Start-Ok Method frame!
6040: conn_state change   snd_connection_start_rsp_method ==> rcv_connection_tune_method
  <-- Recv Connection.Tune Method frame!
6040: conn_state change   rcv_connection_tune_method ==> snd_connection_tune_rsp_method
  --> Send Connection.Tune-Ok Method frame!
6040: conn_state change   snd_connection_tune_rsp_method ==> snd_connection_open_method
  --> Send Connection.Open Method frame!
6040: conn_state change   snd_connection_open_method ==> rcv_connection_open_rsp_method
  <-- Recv Connection.Open-Ok Method frame!
6040: conn_state change   rcv_connection_open_rsp_method ==> snd_channel_open_method
  --> Send Channel.Open Method frame!
6040: conn_state change   snd_channel_open_method ==> rcv_channel_open_rsp_method
  <-- Recv Channel.Open-Ok Method frame!
6040: conn_state change   rcv_channel_open_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Queue Declaring!
6040: conn_state change   idle ==> snd_queue_declare_method
  --> Send Queue.Declare Method frame!
6040: conn_state change   snd_queue_declare_method ==> rcv_queue_declare_rsp_method
  <-- Recv Queue.Declare-Ok Method frame!
6040: conn_state change   rcv_queue_declare_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Queue Binding!
6040: conn_state change   idle ==> snd_queue_bind_method
  --> Send Queue.Bind Method frame!
6040: conn_state change   snd_queue_bind_method ==> rcv_queue_bind_rsp_method
  <-- Recv Queue.Bind-Ok Method frame!
6040: conn_state change   rcv_queue_bind_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Basic QoS!
6040: conn_state change   idle ==> snd_basic_qos_method
  --> Send Basic.Qos Method frame!
6040: conn_state change   snd_basic_qos_method ==> rcv_basic_qos_rsp_method
  <-- Recv Basic.Qos-Ok Method frame!
6040: conn_state change   rcv_basic_qos_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Basic Consuming!
6040: conn_state change   idle ==> snd_basic_consume_method
  --> Send Basic.Consume Method frame!
6040: conn_state change   snd_basic_consume_method ==> rcv_basic_consume_rsp_method
  <-- Recv Basic.Consume-Ok Method frame!
6040: conn_state change   rcv_basic_consume_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Start waiting to recv!
6040: conn_state change   idle ==> rcv_basic_deliver_method
drive_machine: wait for Basic.Deliver method another 10 seconds!!
drive_machine: wait for Basic.Deliver method another 10 seconds!!

  <-- Recv Basic.Deliver Method frame!
6040: conn_state change   rcv_basic_deliver_method ==> rcv_basic_content_header
  <-- Recv Content.Header frame!
6040: conn_state change   rcv_basic_content_header ==> rcv_basic_content_body
  <-- Recv Content.Body frame!
Content Body is [Hello World betty].
@@@ CB: body len : [17]    body : [Hello World betty]
6040: conn_state change   rcv_basic_content_body ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Start waiting to recv!
6040: conn_state change   idle ==> rcv_basic_deliver_method
drive_machine: wait for Basic.Deliver method another 10 seconds!!
drive_machine: wait for Basic.Deliver method another 10 seconds!!

情况二:
[warn] evsignal_init: socketpair: No error
drive_machine: [conn_init]  ---  in TCP 3-way connecting!
drive_machine: [conn_connecting]  ---  connection timeout 1 time on socket(6040)
drive_machine: connected on socket(6040)
6040: conn_state change   connected ==> snd_protocol_header
  --> Send Protocol.Header!
6040: conn_state change   snd_protocol_header ==> rcv_connection_start_method
  <-- Recv Connection.Start Method frame!
6040: conn_state change   rcv_connection_start_method ==> snd_connection_start_rsp_method
  --> Send Connection.Start-Ok Method frame!
6040: conn_state change   snd_connection_start_rsp_method ==> rcv_connection_tune_method
  <-- Recv Connection.Tune Method frame!
6040: conn_state change   rcv_connection_tune_method ==> snd_connection_tune_rsp_method
  --> Send Connection.Tune-Ok Method frame!
6040: conn_state change   snd_connection_tune_rsp_method ==> snd_connection_open_method
  --> Send Connection.Open Method frame!
6040: conn_state change   snd_connection_open_method ==> rcv_connection_open_rsp_method
  <-- Recv Connection.Open-Ok Method frame!
6040: conn_state change   rcv_connection_open_rsp_method ==> snd_channel_open_method
  --> Send Channel.Open Method frame!
6040: conn_state change   snd_channel_open_method ==> rcv_channel_open_rsp_method
  <-- Recv Channel.Open-Ok Method frame!
6040: conn_state change   rcv_channel_open_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Queue Declaring!
6040: conn_state change   idle ==> snd_queue_declare_method
  --> Send Queue.Declare Method frame!
6040: conn_state change   snd_queue_declare_method ==> rcv_queue_declare_rsp_method
  <-- Recv Queue.Declare-Ok Method frame!
6040: conn_state change   rcv_queue_declare_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Queue Binding!
6040: conn_state change   idle ==> snd_queue_bind_method
  --> Send Queue.Bind Method frame!
6040: conn_state change   snd_queue_bind_method ==> rcv_queue_bind_rsp_method
  <-- Recv Queue.Bind-Ok Method frame!
6040: conn_state change   rcv_queue_bind_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Basic QoS!
6040: conn_state change   idle ==> snd_basic_qos_method
  --> Send Basic.Qos Method frame!
6040: conn_state change   snd_basic_qos_method ==> rcv_basic_qos_rsp_method
  <-- Recv Basic.Qos-Ok Method frame!
6040: conn_state change   rcv_basic_qos_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Basic Consuming!
6040: conn_state change   idle ==> snd_basic_consume_method
  --> Send Basic.Consume Method frame!
6040: conn_state change   snd_basic_consume_method ==> rcv_basic_consume_rsp_method
  <-- Recv Basic.Consume-Ok Method frame!
6040: conn_state change   rcv_basic_consume_rsp_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Start waiting to recv!
6040: conn_state change   idle ==> rcv_basic_deliver_method
drive_machine: wait for Basic.Deliver method another 10 seconds!!
drive_machine: wait for Basic.Deliver method another 10 seconds!!
drive_machine: wait for Basic.Deliver method another 10 seconds!!

  <-- Recv Basic.Deliver Method frame!
6040: conn_state change   rcv_basic_deliver_method ==> rcv_basic_content_header
  <-- Recv Content.Header frame!
6040: conn_state change   rcv_basic_content_header ==> rcv_basic_content_body
  <-- Recv Content.Body frame!
Content Body is [Hello World betty].
@@@ CB: body len : [17]    body : [Hello World betty]
6040: conn_state change   rcv_basic_content_body ==> snd_basic_ack_method
  --> Send Basic.Ack Method frame!
6040: conn_state change   snd_basic_ack_method ==> idle

drive_machine: [conn_idle]  ---  [CONSUMER]: Start waiting to recv!
6040: conn_state change   idle ==> rcv_basic_deliver_method
drive_machine: wait for Basic.Deliver method another 10 seconds!!
drive_machine: wait for Basic.Deliver method another 10 seconds!!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现RabbitMQ的手动ackacknowledge)代码,您可以按照以下步骤进行操作: 1. 首先,在RabbitMQ配置文件或者使用代码配置的方式,设置`acknowledge="manual"`,表示使用手动确认模式。 2. 创建一个消费者类(比如MqConsumer),并实现接口ChannelAwareMessageListener,该接口有一个方法onMessage用于接收消息。 3. 在onMessage方法,处理接收到的消息,并在处理完成后调用channel.basicAck方法来手动确认消息的消费。 例如,可以在onMessage方法的代码如下所示: ```java @Override public void onMessage(Message message, Channel channel) throws Exception { try { // 处理接收到的消息 // ... // 手动确认消息的消费 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { // 发生异常时可以选择进行消息的拒绝或者重新投递 // ... } } ``` 4. 如果在处理消息时发生异常,您可以选择进行消息的拒绝或者重新投递,这取决于您的业务需求。 请注意,在手动确认模式下,如果消费者未调用channel.basicAck方法确认消息消费,消息将会一直保留在RabbitMQ的消息缓存,直到消费者重新连接或者超时。因此,在实现手动ack代码时,确保正确处理消息的消费以及异常情况的处理是非常重要的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [rabbitmq自动及手动ACK](https://blog.csdn.net/qq_18671415/article/details/115349452)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值