詳解RabbitMQ中死信隊列和延遲隊列的使用詳解
簡介
本文介紹RabbitMQ的死信隊列和延遲隊列。
本內(nèi)容也是Java后端面試中常見的問題。
死信隊列
簡介
DLX,全稱為Dead-Letter-Exchange,可以稱之為死信交換器,也有人稱之為死信郵箱。當(dāng)消息在一個隊列中變成死信(dead message)之后,它能被重新被發(fā)送到另一個交換器中,這個交換器就是DLX,綁定DLX的隊列就稱之為死信隊列。
以下幾種情況會導(dǎo)致消息變成死信:
- 消息被拒絕(Basic.Reject/Basic.Nack),并且設(shè)置requeue參數(shù)為false;
- 消息過期;
- 隊列達到最大長度。
DLX是一個正常的交換器,和一般的交換器沒有區(qū)別,它能在任何的隊列上被指定,實際上就是設(shè)置某個隊列的屬性。當(dāng)這個隊列中存在死信時,RabbitMQ就會自動地將這個消息重新發(fā)布到設(shè)置的DLX上去,進而被路由到另一個隊列,即死信隊列??梢员O(jiān)聽這個隊列中的消息以進行相應(yīng)的處理,這個特性與將消息的TTL設(shè)置為0配合使用可以彌補immediate參數(shù)的功能。
為隊列添加DLX的方法
法1:代碼方式
//創(chuàng)建 DLX: dlx_exchange channel.exchangeDeclare("dlx_exchange", "direct" ); Map<String, Object> args = new HashMap<String, Object>; args.put("x-dead-letter-exchange", "dlx_exchange"); //為隊列myqueue添加DLX channel.queueDeclare("myqueue", false, false, false, args);
也可以為這個DLX指定路由鍵。(如果沒有特殊指定,則使用原隊列的路由鍵)
args.put("x-dead-letter-routing-key","dlx-routing-key");
法2:命令方式
rabbitmqctl set_policy DLX ".*" '{"dead-letter-exchange":"dlx_exchange"}' --apply-to queues
示例
代碼
channel.exchangeDeclare("exchange.dlx", "direct", true); channel.exchangeDeclare("exchange.normal", "fanout", true); Map<String, Object> args = new HashMap<String, Object>(); args.put("x-message-ttl", 10000); args.put("x-dead-letter-exchange" , "exchange.dlx"); args.put("x-dead-letter-routing-key" , "routingkey"); channel.queueDeclare("queue.normal" , true, false, false, args); 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());
這里創(chuàng)建了兩個交換器exchange.normal和exchange.dlx,分別綁定兩個隊列queue.normal和queue.dlx。
Web管理頁面結(jié)果
由下圖(圖1-1)的Web管理頁面可以看出,兩個隊列都被標記了“D”,這個是durable的縮寫,即設(shè)置了隊列持久化。queue.normal這個隊列還配置了TTL、DLX和DLK,其中DLX指的是
x-dead-letter-routing-key這個屬性。
圖1-1
案例分析
參考下圖(圖1-2),生產(chǎn)者首先發(fā)送一條攜帶路由鍵為“rk”的消息,然后經(jīng)過交換器exchange.normal順利地存儲到隊列queue.normal中。由于隊列queue.normal設(shè)置了過期時間為10s,在這10s內(nèi)沒有消費者消費這條消息,那么判定這條消息為過期。由于設(shè)置了DLX,過期之時,消息被丟給交換器exchange.dlx中,這時找到與exchange.dlx匹配的隊列queue.dlx,最后消息被存儲在queue.dk這個死信隊列中。
圖1-2
對于RabbitMQ來說,DLX是一個非常有用的特性。它可以處理異常情況下,消息不能夠被消費者正確消費(消費者調(diào)用了Basic.Nack或者Basic.Reject)而被置入死信隊列中的情況,后續(xù)分析程序可以通過消費這個死信隊列中的內(nèi)容來分析當(dāng)時所遇到的異常情況,進而可以改善和優(yōu)化系統(tǒng)。DLX配合TTL使用還可以實現(xiàn)延遲隊列的功能,詳細請看下一節(jié)。
延遲隊列
簡介
延遲隊列用來存放延遲消息。延遲消息:指當(dāng)消息被發(fā)送以后,不想讓消費者立刻拿到消息,而是等待特定時間后,消費者才能拿到這個消息進行消費。
在AMQP協(xié)議中,或者RabbitMQ本身沒有直接支持延遲隊列的功能,但是有兩種方案來間接實現(xiàn):
- 方案1:采用rabbitmq-delayed-message-exchange 插件實現(xiàn)。(RabbitMQ 3.6.x開始支持)
- 方案2:通過前面所介紹的DLX和TTL模擬出延遲隊列的功能。
在圖1-2中,不僅展示的是死信隊列的用法,也是延遲隊列的用法,對于queue.dlx這個死信隊列來說,同樣可以看作延遲隊列。假設(shè)一個應(yīng)用中需要將每條消息都設(shè)置為10秒的延遲,
生產(chǎn)者通過exchange.normal這個交換器將發(fā)送的消息存儲在queue.normal這個隊列中。消費者訂閱的并非是queue.normal這個隊列,而是queue.dlx這個隊列。當(dāng)消息從queue.normal這個隊列中過期之后被存入queue.dlx這個隊列中,消費者就恰巧消費到了延遲10秒的這條消息。
在真實應(yīng)用中,對于延遲隊列可以根據(jù)延遲時間的長短分為多個等級,一般分為5秒、10秒、30秒、1分鐘、5分鐘、10分鐘、30分鐘、1小時這幾個維度,當(dāng)然也可以再細化一下。
以下圖(圖2-1)為例進行說明。為簡化,只設(shè)置5秒、10秒、30秒、1分鐘這四個等級。根據(jù)需求的不同,生產(chǎn)者發(fā)送消息的時候通過設(shè)置不同的路由鍵,將消息發(fā)送到與交換器綁定的不同的隊列中。這里隊列也分別配置了DLX和相應(yīng)的死信隊列,當(dāng)相應(yīng)的消息過期時,就會轉(zhuǎn)存到相應(yīng)的死信隊列(即延遲隊列)中,這樣消費者根據(jù)業(yè)務(wù)自身的情況,分別選擇不同延遲等級的延遲隊列進行消費。
圖2-1
使用場景
延遲隊列的使用場景有很多,比如:
用戶下訂單場景:用戶下單后有30分鐘的時間支付,若30分鐘內(nèi)沒有支付,則將這個訂單取消。
方案:用戶下單后將取消訂單的消息發(fā)送到延遲隊列,延遲時間設(shè)置為30分鐘。取消訂單這個消息的訂閱者程序在30分鐘后收到消息,判斷該訂單的狀態(tài)是否為已支付,若還沒支付,則將該訂單狀態(tài)設(shè)置為:已取消。
定時遙控場景:用戶想用手機遠程遙控家里的智能設(shè)備在指定的時間工作。
方案:假設(shè)用戶想要的操作是:開啟熱水器。首先,將開啟熱水器這個消息發(fā)送到延遲隊列,延遲時間設(shè)置到用戶想要的時間到現(xiàn)在時間的差值。開啟熱水器這個消息的訂閱者程序在指定時間收到消息,再將指令推送到智能設(shè)備。
需要注意的是,延遲隊列的消息是不能取消的,解決方案是:在消費消息的時候判斷這個消息對應(yīng)的業(yè)務(wù)的當(dāng)前狀態(tài)。例如:對于取消訂單來說,收到消息時,讀取這個消息所對應(yīng)的數(shù)據(jù)庫信息,如果已經(jīng)是已付款狀態(tài)了,就不進行任何操作了,如果是未支付狀態(tài),則改為已取消。
到此這篇關(guān)于詳解RabbitMQ中死信隊列和延遲隊列的使用詳解的文章就介紹到這了,更多相關(guān)RabbitMQ死信隊列 延遲隊列內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文秒懂IDEA中每天都在用的Project Structure知識
這篇文章主要介紹了一文秒懂IDEA中每天都在用的Project Structure知識,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10Spring Boot 整合 Shiro+Thymeleaf過程解析
這篇文章主要介紹了Spring Boot 整合 Shiro+Thymeleaf過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10@RunWith(SpringJUnit4ClassRunner.class)報錯問題及解決
這篇文章主要介紹了@RunWith(SpringJUnit4ClassRunner.class)報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04Java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)折半查找的算法過程解析
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)折半查找的算法過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03注解、原生Spring、SchemaBased三種方式實現(xiàn)AOP代碼案例
這篇文章主要介紹了注解、原生Spring、SchemaBased三種方式實現(xiàn)AOP的方法介紹,文中有詳細的代碼示例,對我們的學(xué)習(xí)有一定的幫助,需要的朋友可以參考下2023-06-06