RabbitMQ學習(十七):消息確認之消費者確認模式 II

說明

在上篇博文《RabbitMQ學習(十六):消息確認之消費者確認模式 I》中對消息確認的必要性和確認相關的傳輸標籤,消費者確認方法等內容進行了翻譯學習,本篇博文我將繼續翻譯學習消息者確認模式的剩餘內容,主要包含了積極消極確認的方式,批量確認消息,通道Prefetch值的設置以及它和確認模式對吞吐量的影響,還有異常時的自動重新入隊,客戶端常見錯誤等內容。

正文

積極確認

在客戶端包中對消息進行確認的api方法通常被當作爲是通道上的操作來提供。java客戶端使用Channel#basicAck和Channel#basicNack方法來分別代表basic.ack和basic.nack方法。以下是使用java客戶端進行積極確認的示例:

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();
             // positively acknowledge a single delivery, the message will
             // be discarded
             channel.basicAck(deliveryTag, false);
         }
     });

一次確定多個消息

手動確認的方式可以進行批量確認來減少網絡傳輸。批量確認可以通過設置確認方法的multiple參數值爲true實現。注意,basic.reject方法沒有該參數,這就是爲什麼RabbitMQ要引進basic.nack方法作爲協議的補充。

當multiple的值設置爲true時,RabbitMQ將確認指定傳輸標籤以及之前所有未被確認的消息。與單個確認相同,批量確認的作用域爲每個通道。例如:通道Ch上有四個未被確認的消息,標籤分別爲5,6,7,8;當一個delivery_tag值爲8並且multiple值爲true的確認消息到達通道時,所有5到8的標籤都會被確認。如果multiple值設置爲false,標籤爲5,6,7的消息將不會被確認。

以下是使用java客戶端,將Channel#basicAck方法的multiple值設置true進行批量確認的示例:

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();
             // positively acknowledge all deliveries up to
             // this delivery tag
             channel.basicAck(deliveryTag, true);
         }
     });

消息的消極確認和重新入隊

有時一個消費者不能立即處理一個髮送的消息,當時其他消費者可以,這種情況下就需要將消息重新入隊讓其他消費者接收處理。RabbitMQ提供了basic.reject和basic.nack兩種協議方法實現消息的重新入隊。

這兩個方法一般被用來對一個消息進行消極確認,例如,消息需要被服務器丟棄或者重新入隊。這個操作通過requeue這個參數進行控制,當這個參數被設置爲true時,服務器將把指定的傳輸標籤的消息重新入隊。

這些方法與積極確認一樣都是通道上的操作,java客戶端使用Channel#basicReject和Channel#basicNack方法分別代表basic.reject和basic.nack方法。以下是進行丟棄消息和重新入隊的示例:

丟棄消息:

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();
             // negatively acknowledge, the message will
             // be discarded
             channel.basicReject(deliveryTag, false);
         }
     });

重新入隊:

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();
             // requeue the delivery
             channel.basicReject(deliveryTag, true);
         }
     });

當消息被重新入隊時,如果可能消息會被放置在之前在隊列的原始位置,如果不能(由於多個消費者共同消費一個隊列,會進行同時傳輸和確認),消息將被儘可能地放到接近隊頭的位置。

重新入隊的消息可能會被立即發送,但這個取決於它在隊列中的位置,及活躍消費者在通道上設置的Prefetch的值。這意味着如果所有的消息者因爲某些原因不能處理消息而全部將消息重新入隊,這個將導致重新入隊和重新發送的循環。這樣的循環將不斷消耗網絡帶寬和CPU資源。消費者可以追蹤消息的重新發送次數和併合理的拒絕消息(丟棄它們),或者在延遲一段時間後再重新入隊。

可能存在需要一次拒絕或重新入隊多個消息的情況,這種情況可以使用basic.nack方法。這就是basic.nack方法和basic.reject的不同之處,該方法有一個可選參數multiple來進行批量操作。以下是使用java客戶端進行消息批量重新入隊。

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();
             // requeue all unacknowledged deliveries up to
             // this delivery tag
             channel.basicNack(deliveryTag, true, true);
         }
     });

通道Prefetch值的設置(QoS)

由於消息被髮送到客戶端是異步的,所以在任何時刻通道中都會有多個消息正在被傳遞。另外,客戶端的異步確認也是異步的。所以對於通道中的未被確認的傳輸標籤有個滑動窗口,開發者通常設置窗口的大小來避免在消費者出現無邊界緩衝區的問題。這個限制可以通過使用basic.qos方法設置一個“prefetch count”值來實現。一旦未被確認的消息數到達設定的值,RabbitMQ將停止往該通道傳輸更多的消息,除非通道中至少有一個消息被確認。

例如,在通道Ch中有四個未被確認的消息,傳輸標籤分別爲5,6,7,8;並且通道的prefetch值設置爲4,這時RabbitMQ將不再往該通道推送更多的消息,直到其中最少一個消息被確認。當一個delivery_tag值爲5(或者是6,7,8)的確認消息到達該通道時,RabbitMQ將注意到未被確認的數量已經降到限制值以下,並且發送消息到該通道。當一次確認多個消息時,將使得更多的消息可以被髮送。

我們必須注意到消息的發送和客戶端的手動確認完全都是異步的,因此如果在傳輸過程中prefetch的值發生了改變,這出現自然的競爭條件,此時通道會暫時出現未確認消息的數量大於設定的值。

Qos prefetch值的設置不會對使用basic.get方式獲取的消息造成影響,即使是使用手動確認模式。


對吞吐量的影響

消息確認模式和Qos prefetch值對消費者的吞吐量有明顯的影響。通常,增加prefetch值將提高消息到消息者的發送速率,自動確認模式可以得到最好的發送速率。然而,消息的發送數量和未被確認的消息數量也會同時增長,這些將增加消費者的內存消耗。

自動確認模式或者手動確認模式在使用時如果沒有設置prefetch值時,需要十分小心。消費者消費大量的消息並且沒有進行確認時,將消費者所在節點的內存消耗增長。尋找一個合適的prefetch值時十分重要的,這需要不斷的測試,並且該值隨着工作負載的不同而變化。一般地,該值在100到300之間可以提供較優的吞吐量並且不會明顯增加消費者內存溢出的風險。注意,值越高,收益越低。

prefetch值爲1是最保守的策略,在特定環境下如消費者連接延遲較高時,會明顯地降低吞吐量。對於大多數程序來說,一個較高的值是合適和最優的。


自動重新入隊

當使用手動確認時,當在傳輸時發生了通道或者連接關閉,任何未被確認的消息都會自動重新入隊。這包括了客戶端的TCP連接丟失,消息者程序的崩潰和通道協議異常的發生。

注意,發生不可用的客戶端需要耗費一段時間。

由於重新入隊,消費者必須準備處理重新發送的消息,否則要考慮處理上的冪等性。重新發送有一個特殊的Boolean屬性—redeliver,被RabbitMQ設置爲true。在第一次發送時,該值被設置爲false。注意,一個消費者可能會接收到之前被髮送到其他消費者的消息。


客戶端錯誤:雙重確認和未知標籤

如果客戶端多次確認相同的標籤,RabbitMQ將導致通道異常,如PRECONDITION_FAILED - unknown delivery tag 100。同樣地,如果使用了一個未知的傳輸標籤,相同的通道異常將被拋出。

服務器因未知標籤拋出異常的另一種情況是:客戶端嘗試在與接收到的消息不同的通道上進行確認,不管是積極確認還是消極確認。因此,傳輸必須在同一個通道上確認

原文地址:https://www.rabbitmq.com/confirms.html

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