RabbitMQ 消費者確認(Consumer Acknowledgements)

摘要

RabbitMQ關於數據安全有兩個特性:發佈者確認(Publisher Confirms)和消費者確認(Consumer Acknowledegements)。前者用於MQ服務器(broker)告訴發佈者傳遞消息的結果,它是消息協議的拓展內容;後者用於消費者告訴MQ服務器消息傳遞的結果,是消息協議包含的定義。本文主要介紹RabbitMQ對消費者確認的實現。

一條消息傳遞的標識:傳遞標籤(delivery tags)

消息的傳遞是如何被標識的呢?
當一個消費者被註冊後,RabbitMQ通過basic.delivery方法傳遞消息,該方法會攜帶一個傳遞標籤,它唯一的標識了通道上某條消息的傳遞。因此傳遞標籤是按通道分的。
傳遞標籤是單調遞增的正整數,消費者客戶端確認消息的方法將會將傳遞標籤作爲參數。因爲傳遞標籤是按照通道分的,如果消息A是從通道1傳遞到消費者服務器的,那麼消息A的確認也必須通過通道1,如果錯誤的通過通道2確認,RabbitMQ會拋出協議異常並關閉通道2。

消費者確認的模式

當一個MQ服務器節點傳遞一條消息到消費者服務器,它需要決定什麼時候認爲這條消息已經被消費者成功處理了。消息協議通常會提供一個確認機制,允許消費者向他們連接的MQ服務器發送確認。確認機制是否啓用一般在消費者訂閱的時候決定。

取決於使用的確認模式,RabbitMQ可以在消息從MQ服務器發送出(寫入TCP Socket)後立刻就將其當做已成功處理,或者當收到來自消費者顯示的(手工的)的確認後。

手動確認模式

消費者手工發送的確認可以是以下一種協議方法:

  • basic.ack(deliveryTag,multiple) 用來確認成功消息(positive acknowledgements)
  • basic.nack(deliveryTag,requeue,multiple) 用來確認失敗的消息(negative acknowledgements)
  • basic.reject(deliveryTa,requeue) 用來確認失敗的消息

確認成功簡單的令rabbitMQ將消息記錄爲已發送並丟棄。使用basic.reject
方法令RabbitMQ記錄消息爲發送失敗,但仍然需要丟棄。

自動確認模式

在自動確認模式(automatic acknowledgement)中,消息在發出去後就被認爲是已成功處理。這種模式損失數據安全性來換取消息的高吞吐量,如果與消費者的TCP連接或者消息通道在成功發送前關閉了,則MQ服務器發送出的消息就丟失了。因此自動確認模式應該被認爲是非數據安全的,應謹慎使用。

如果要使用自動確認模式,還有個要注意的地方是消費者的負載。手動確認模式典型的使用會在消費者端定義一個有限大小的隊列(prefetch)用於存放未處理狀態的消息。然而自動確認模式根本無此限制,因此有可能造成消費者服務器超出負荷,導致堆內存不夠用而被操作系統終止進程。因此,只有在消費者能夠已穩定並高效的處理消息的前提下,才建議使用確認模式。

RabbitMQ手動確認示例

1,確認一條消息傳遞成功

JAVA庫使用 Channel#basicAckChannel#basicNack方法分別來實現協議中定義的 basic.ackbasic.nack方法。示例如下:

// 假設已存在channel實例

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // 確認一條消息成功傳遞,消息將會被RabbitMQ丟棄
             channel.basicAck(deliveryTag, false);
         }
     });

2,一次確認多條消息傳遞成功

手工確認模式可以通過批次確認來減少網絡流程,通過將 確認方法的的multiple參數設置爲true。注意,basic.reject是沒有這個參數的,RabbitMQ引入basic.nack方法帶這個參數,該方法作爲拓展協議的一部分。

multiple參數被設爲true。RabbitMQ將會確認所有傳遞標籤小於給定數值的消息。比如通道Ch上有未確認消息,它們的傳遞標籤是5,6,7,8,如果有確認帶的傳遞標籤是8,且multiple參數被設爲true,則5-8消息都會被確認;如果multiple參數被設爲false,則5,6,7消息仍未被確認。示例如下:

// 假設已存在channel實例

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             //確認所有傳遞標籤小於deliveryTag的消息已成功傳遞,並丟棄它們
             channel.basicAck(deliveryTag, true);
         }
     });

3, 確認消息失敗並重新入隊消息

有時消費者無法立即處理傳遞來的消息,但其他消費者有能力處理。在這種情況下,可能需要讓消息重新入隊並讓另一個消費者接收和處理它。basic.rejectbasic.nack是用於此的兩種協議方法。
上面兩個方法通常被用於確認消息傳遞失敗,MQ服務器可以丟棄這些消息或重新入隊。可通過requeue參數來控制,當設爲true時MQ服務器會將指定傳遞標籤的消息重新入隊。示例如下:

// 假設已存在channel實例

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // 確認傳遞標籤爲deliveryTag的消息傳遞失敗,並丟棄它
             channel.basicReject(deliveryTag, false);
         }
     });
// 假設已存在channel實例

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // 傳遞失敗,重新入隊
             channel.basicReject(deliveryTag, true);
         }
     });

如果可能RabbitMQ會將重新入隊的消息還放在它原來的位置,否則就放到儘可能離隊首近的位置。假設某一瞬間出現,所有消費者的預取隊列(prefetch)都已經滿了(無法再接收消息),則會出現一個重新入隊/重新傳遞的循環,造成網絡帶寬和內存資源的消耗。消費者需要追蹤重新傳遞的數量,丟棄確認失敗的消息,或經過一定時延後再重新入隊。

使用 basic.nack可以同時入隊多條消息,它比basic.reject方法多了一個multiple參數,示例如下:

// 假設已存在channel實例

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "a-consumer-tag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             long deliveryTag = envelope.getDeliveryTag();
             // 確認所有傳遞標籤小於deliveryTag的消息傳遞失敗,並重新入隊它們
             channel.basicNack(deliveryTag, true, true);
         }
     });

在手動確認模式下,如果傳遞的通道關閉或失去連接,則通道上未收到確認的消息會自動重新入隊。這包括客戶端的TCP連接丟失,消費者應用程序(進程)故障以及通道級協議異常。

考慮到消息會重新入隊,消費者需要保證對消息操作的冪等性。被重發的消息,攜帶的redeliver會被RabbitMQ設置爲true,這個參數在消息首次發送時是false。注意,一個消費者可能會收到上一次被其他消費者收到過的消息。

消費者常見錯誤:重複確認和未知標籤

如果消費者對某個傳遞標籤重複確認,會導致RabbitMQ報通道異常:PRECONDITION_FAILED - unknown delivery tag 100。如果使用不存在的傳遞標籤頁會報相同異常。

其他會報unknown delivery tag的場景是,確認消息的通道與接收消息的通道不同。謹記,消息傳遞確認和消息傳遞必須是同一通道。

通道預取設置(QoS)

因爲消息是異步發送(推送)到客戶端的,所以在任何給定時刻,通常有一個以上的消息“正在運行”。另外,來自客戶端的手動確認本質上也本質上是異步的。因此,存在一個未確認的傳遞標籤滑動窗口。開發人員通常會希望限制此窗口的大小,以避免在用戶端出現無限制的緩衝區問題。消息協議通過使用basic.qos方法設置“預取計數”值來實現 。該值定義通道上允許的未確認交付的最大數量。一旦數量達到配置的數量,RabbitMQ將停止在通道上傳遞更多消息,除非已確認至少一個未處理的消息。

例如,假設在通道Ch上有未確認的傳遞標籤5、6、7和8,並且通道 Ch的預取計數設置爲4,RabbitMQ將不會在Ch上傳遞任何消息,除非至少有一個未完成的傳遞被確認。當消費者在通道Ch上發送確認,deliveryTag設置爲5,RabbitMQ會注意到並再傳遞一條消息。

預取計數對消息吞吐量的影響

通常,增加預取將提高向消費者傳遞消息的速度,但傳遞但尚未處理的消息的數量也會增加,從而增加了消費者的RAM消耗。找到合適的預取值是一個反覆試驗的問題,並且會因工作負載而異。100到300範圍內的值通常可提供最佳吞吐量,並且不會帶來壓倒消費者的巨大風險。更高的取值經常會碰到收益遞減的規律

參考資料

Consumer Acknowledgements and Publisher Confirms

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章