RabbitMQ--過期時間TTL、死信隊列、延遲隊列和優先隊列

過期時間TTL

TTL,Time to Live的簡稱,即過期時間。RabbitMQ 可以對消息和隊列設置TTL。

設置消息的TTL:

目前有兩種方法可以設置消息的TTL。

  1. 第一種方法是通過隊列屬性設置,隊列中所有消息都有相同的過期時間。
  2. 第二種方法是對消息本身進行單獨設置,每條消息的TTL可以不同。

如果兩種方法一起使用,則消息的TTL以兩者之間較小的那個數值爲準。
消息在隊列中的生存時間一旦超過設置的TTL值時,就會變成“死信”(DeadMessage),消費者將無法再收到該消息(這點不是絕對的)。

通過隊列屬性設置消息TTL的方法是在channel.queueDeclare 方法中加入x-message-ttl參數實現的,這個參數的單位是毫秒。

//設置TTL
Map<String, Object> argss = new HashMap<>();
argss.put("x-message-ttl", 6000);
channel.queueDeclare(QUEUE_NAME, true, false, false, argss);

同時也可以通過Pllicy的方式來設置TTL:

rabbitmqctl set_policy TTL ".*" '{"message-ttl"":60000}' --apply-to queues

如果不設置TTL,則表示此消息不會過期;
如果將TTL設置爲0,則表示除非此時可以直接將消息投遞到消費者,否則該消息會被立即丟棄,這個特性可以部分替代RabbitMQ 3.0版本之前的immediate 參數,之所以部分代替,是因爲immediate參數在投遞失敗時會用Basic. Return將消息返回(這個功能可以用死信隊列來實現)

針對每條消息設置TTL的方法是在channel.basicPublish方法中加入expiration的屬性參數,單位爲毫秒:

AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.deliveryMode(2);//持久化消息
builder.expiration("60000");//設置TTL=60000ms
AMQP.BasicProperties properties = builder.build();
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, false, properties, "ttlTest".getBytes());

對於第一種設置隊列TTL屬性的方法,一旦消息過期,就會從隊列中抹去,而在第二種方法中,即使消息過期,也不會馬上從隊列中抹去,因爲每條消息是否過期是在即將投遞到消費者之前判定的。

爲什麼這兩種方法處理的方式不一樣?因爲第一種方法裏,隊列中已過期的消息肯定在隊列頭部,RabbitMQ只要定期從隊頭開始掃描是否有過期的消息即可。而第二種方法裏,每條消息的過期時間不同,如果要刪除所有過期消息勢必要掃描整個隊列,所以不如等到此消息即將被消費時再判定是否過期,如果過期再進行刪除即可。

設置隊列的TTL:
通過channel. queueDeclare方法中的x-expires參數可以控制隊列被自動刪除前處於未使用狀態的時間。未使用的意思是隊列上沒有任何的消費者,隊列也沒有被重新聲明,並且在過期時間段內也未調用過Basic.Get命令。

設置隊列裏的TTL可以應用於類似RPC方式的回覆隊列,在RPC中,許多隊列會被創建出來,但是卻是未被使用的。
RabbitMQ會確保在過期時間到達後將隊列刪除,但是不保障刪除的動作有多及時。在RabbitMQ重啓後,持久化的隊列的過期時間會被重新計算。

用於表示過期時間的x-expires參數以毫秒爲單位,並且服從和x-message-ttl一樣的約束條件,不過不能設置爲0。比如該參數設置爲1000,則表示該隊列如果在1秒鐘之內未使用則會被刪除。

//設置隊列TTL
Map<String, Object> arg = new HashMap<>();
arg.put("x-expires", 1800000);
channel.queueDeclare("myqueue", false, false, false, arg);

死信隊列

DLX,全稱爲Dead-Letter-Exchange,可以稱之爲死信交換器,也有人稱之爲死信郵箱。當消息在一個隊列中變成死信( dead message)之後,它能被重新被髮送到另一個交換器中,這個交換器就是DLX,綁定DLX的隊列就稱之爲死信隊列。

消息變成死信一般是由於以下幾種情況:

  • 消息被拒絕(Basic. Reject/Basic.Nack),並且設置requeue參數爲false;
  • 消息過期;
  • 隊列達到最大長度。

DLX也是一個正常的交換器,和一般的交換器沒有區別,它能在任何的隊列上被指定,實際上就是設置某個隊列的屬性。當這個隊列中存在死信時,RabbitMQ就會自動地將這個消息重新發布到設置的DLX上去,進而被路由到另一個隊列,即死信隊列。可以監聽這個隊列中的消息以進行相應的處理,這個特性與將消息的TTL設置爲0配合使用可以彌補immediate參數的功能。

通過在channel. queueDeclare方法中設置x-dead- letter-exchange參數來爲這個隊列添加DLX

//設置死信隊列
channel.exchangeDeclare("dlx_exchange", "direct");//創建DLX
Map<String, Object> arg = new HashMap<>();
arg.put("x-dead-letter-exchange", "dlx_exchange");
//爲隊列添加myqueue添加DLX
channel.queueDeclare("myqueue", false, false, false, arg);

也可以爲這個DLX指定路郵鍵,如果沒有特殊指定,則使用原隊列的路由鍵:

arg.put("x-dead-letter-routing-key", "dlx-routing-key");

也可以通過Policy的方式設置:

rabbitmqctl set_policy DLX ".*" '{"dead-letter-exchange":"dlx_exchange"}' --apply-to queues

示例:創建一個隊列,爲其設置TTL和DLX:

channel.exchangeDeclare("exchange.dlx", "direct", true);
channel.exchangeDeclare("exchange.normal", "fanout", true);
Map<String, Object> arg = new HashMap<>();
arg.put("x-message-ttl", 10000);
arg.put("x-dead-letter-exchange", "exchange.dlx");
arg.put("x-dead-letter-routing-key", "routingkey");
channel.queueDeclare("queue.normal", true, false, false, arg);
channel.queueBind("queue.normal", "exchange.normal", "");
channel.queueDeclare("queue.dlx", true, false, false, null);
channel.queueBind("queue.dlx", "exchange.dlx", "routingkey");
channel.basicPublish("exchange.normal", "rk", 
        MessageProperties.PERSISTENT_TEXT_PLAIN, "dlx".getBytes());

這裏創建了兩個交換器exchange.normal 和exchange.dlx, 分別綁定兩個隊列queue.normal和queue.dlx.

由Web管理頁面可以看出,兩個隊列都被標記了“D”,這個是durable的縮寫,即設置了隊列持久化。queue.normal 這個隊列還配置了TTL、DLX和DLK,其中DLX指的是x-dead- letter-routing-key這個屬性。

在這裏插入圖片描述

生產者首先發送一條攜帶路由鍵爲“rk”的消息,然後經過交換器exchange.normal順利地存儲到隊列queue.normal中。由於隊列queue.normal設置了過期時間爲10s,在這10s 內沒有消費者消費這條消息,那麼判定這條消息爲過期。由於設置了DLX,過期之時,消息被丟給交換器exchange.dlx中,這時找到與exchange.dlx 匹配的隊列queue.dlx, 最後消息被存儲在queue.dlx這個死信隊列中。

在這裏插入圖片描述
對於RabbitMQ來說,DLX是一個非常有用的特性。它可以處理異常情況下,消息不能夠被消費者正確消費(消費者調用了Basic.Nack或者Basic. Reject)而被置入死信隊列中的情況,後續分析程序可以通過消費這個死信隊列中的內容來分析當時所遇到的異常情況,進而可以改善和優化系統。DLX配合TTL使用還可以實現延遲隊列的功能。

延遲隊列

延遲隊列存儲的對象是對應的延遲消息,所謂“延遲消息”是指當消息被髮送以後,並不想讓消費者立刻拿到消息,而是等待特定時間後,消費者才能拿到這個消息進行消費。

延遲隊列的使用場景有很多,比如:

  • 在訂單系統中, 一個用戶下單之後通常有30分鐘的時間進行支付,如果30分鐘之內沒有支付成功,那麼這個訂單將進行異常處理,這時就可以使用延遲隊列來處理這些訂單了。
  • 用戶希望通過手機遠程遙控家裏的智能設備在指定的時間進行工作。這時候就可以將用戶指令發送到延遲隊列,當指令設定的時間到了再將指令推送到智能設備。

在AMQP協議中,或者RabbitMQ本身沒有直接支持延遲隊列的功能,但是可以通過前面所介紹的DLX和TTL模擬出延遲隊列的功能。

在上節的圖示中,不僅展示的是死信隊列的用法,也是延遲隊列的用法,對於queue.dlx這個死信隊列來說,同樣可以看作延遲隊列。假設一個應用中需要將每條消息都設置爲10秒的延遲,生產者通過exchange.normal這個交換器將發送的消息存儲在queue.normal這個隊列中。消費者訂閱的並非是queue.normal這個隊列,而是queue.dlx這個隊列。當消息從queue.normal這個隊列中過期之後被存入queue.dlx這個隊列中,消費者就恰巧消費到了延遲10秒的這條消息。

在真實應用中,對於延遲隊列可以根據延遲時間的長短分爲多個等級,-般分爲5秒、10秒、30秒、1分鐘、5分鐘、10分鐘、30分鐘、1小時這幾個維度,當然也可以再細化一下。

爲了簡化說明,這裏只設置了5秒、10秒、30秒、1分鐘這四個等級。根據應用需求的不同,生產者在發送消息的時候通過設置不同的路由鍵,以此將消息發送到與交換器綁定的不同的隊列中。這裏隊列分別設置了過期時間爲5秒、10秒、30秒、1分鐘,同時也分別配置了DLX和相應的死信隊列。當相應的消息過期時,就會轉存到相應的死信隊列(即延遲隊列)中,這樣消費者根據業務自身的情況,分別選擇不同延遲等級的延遲隊列進行消費。

在這裏插入圖片描述

優先隊列

優先級隊列,顧名思義,具有高優先級的隊列具有高的優先權,優先級高的消息具備優先被消費的特權。

可以通過設置隊列的x-max-priority參數來實現:

//設置一個隊列的最大優先級
Map<String, Object> arg = new HashMap<>();
arg.put("x-max-priority", 10);
channel.queueDeclare("queue.priority", true, false, false, arg);

通過Web管理頁面可以看到Pri標誌:
在這裏插入圖片描述

上面的代碼演示的是如何配置一個隊列的最大優先級。在此之後,需要在發送時在消息中設置消息當前的優先級。

//設置消息優先級
//設置消息優先級
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.priority(5);
AMQP.BasicProperties properties = builder.build();
channel.basicPublish("exchange_priority", "rk_priority", properties, "messages".getBytes());

上面的代碼中設置消息的優先級爲5。默認最低爲0,最高爲隊列設置的最大優先級。優先級高的消息可以被優先消費,這個也是有前提的:如果在消費者的消費速度大於生產者的速度且Broker中沒有消息堆積的情況下,對發送的消息設置優先級也就沒有什麼實際意義。因爲生產者剛發送完一條消 息就被消費者消費了,那麼就相當於Broker中至多隻有一條消息,對於單條消息來說優先級是沒有什麼意義的。

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