接之前分享過(guò)的文章【RabbitMQ的死信隊(duì)列和延時(shí)隊(duì)列】觉至,更詳細(xì)的聊一下RabbitMQ延時(shí)隊(duì)列裆蒸。
在RabbitMQ中本身是不存在延時(shí)隊(duì)列冬殃,如果需要使用RabbitMQ來(lái)實(shí)現(xiàn)延時(shí)隊(duì)列,有兩種方式:
-
第一種:DLX+TTL(Time To Live);
設(shè)置TTL分為兩種:在隊(duì)列屬性中設(shè)置TTL白指,在消息屬性中設(shè)置TTL - 第二種:使用延時(shí)消息插件浙芙;
1. DLX+TTL模擬延時(shí)隊(duì)列
通過(guò)過(guò)期消息和死信隊(duì)列來(lái)模擬出延時(shí)隊(duì)列,消費(fèi)者監(jiān)聽(tīng)死信交換器綁定的隊(duì)列卢厂,而不要監(jiān)聽(tīng)消息發(fā)送的隊(duì)列,這樣就可以模擬出延時(shí)隊(duì)列了惠啄。
通過(guò)過(guò)期消息和死信隊(duì)列來(lái)模擬出延時(shí)隊(duì)列:
前面講了過(guò)期消息有兩種實(shí)現(xiàn)方法慎恒,第一種是通過(guò)對(duì)隊(duì)列進(jìn)行設(shè)置,第二種是通過(guò)對(duì)消息本身進(jìn)行設(shè)置撵渡。
第一種方法通過(guò)隊(duì)列設(shè)置融柬,需要在隊(duì)列聲明的時(shí)候設(shè)置才有效。而如果使用這種方法姥闭,那么每增加一個(gè)新的時(shí)間需求丹鸿,就要增加一個(gè)隊(duì)列,顯然這種方法不夠靈活棚品。
既然第一種方法不夠靈活靠欢,那么咱通過(guò)第二種方法就可以實(shí)現(xiàn)靈活性。然而事情并沒(méi)有那么簡(jiǎn)單铜跑,因?yàn)榍懊嬉呀?jīng)講了门怪,如果使用在消息屬性上設(shè)置TTL的方式,消息可能并不會(huì)按時(shí)“死亡“锅纺。
因?yàn)镽abbitMQ只會(huì)檢查隊(duì)列頭部的消息是否過(guò)期掷空,如果過(guò)期則丟到死信隊(duì)列,所以如果隊(duì)列中第一個(gè)消息的延時(shí)時(shí)長(zhǎng)很長(zhǎng)囤锉,而第二個(gè)消息的延時(shí)時(shí)長(zhǎng)很短坦弟,則第二個(gè)消息并不會(huì)優(yōu)先得到執(zhí)行。
所以不管是在消息維度或是隊(duì)列維度設(shè)置過(guò)期時(shí)間綁定死信隊(duì)列模擬延時(shí)隊(duì)列官地,歸根結(jié)底都是在隊(duì)列上實(shí)現(xiàn)消息的延遲酿傍,這樣方式存在不靈活或者不及時(shí)的時(shí)序問(wèn)題。
而使用延時(shí)消息插件驱入,是自定義交換機(jī)赤炒,讓交換機(jī)擁有了延遲發(fā)送消息的能力,從而實(shí)現(xiàn)消息的精準(zhǔn)延時(shí)亏较。下面就簡(jiǎn)單介紹一下莺褒。
2. 使用延時(shí)消息插件
使用延時(shí)消息插件需要安裝延時(shí)消息插件( rabbitmq-delayed-message-exchange),我們可以聲明 x-delayed-message 類型的 Exchange雪情,消息發(fā)送時(shí)指定消息頭 x-delay 以毫秒為單位將消息進(jìn)行延遲投遞遵岩。
實(shí)現(xiàn)原理:
使用DLX+TTL的模式,消息首先會(huì)路由到一個(gè)正常的隊(duì)列巡通,然后根據(jù)設(shè)置的TTL進(jìn)入死信隊(duì)列尘执,與之不同的是通過(guò) x-delayed-message 聲明的交換機(jī),它的消息在發(fā)布之后不會(huì)立即進(jìn)入隊(duì)列扁达,而先將消息保存至 Mnesia(一個(gè)分布式數(shù)據(jù)庫(kù)管理系統(tǒng)正卧,適合于電信和其它需要持續(xù)運(yùn)行和具備軟實(shí)時(shí)特性的 Erlang 應(yīng)用。目前資料介紹的不是很多)
這個(gè)插件將會(huì)嘗試確認(rèn)消息是否過(guò)期跪解,首先要確保消息的延遲范圍是 Delay > 0, Delay =< ?ERL_MAX_T(在 Erlang 中可以被設(shè)置的范圍為 (2^32)-1 毫秒)炉旷,如果消息過(guò)期通過(guò) x-delayed-type 類型標(biāo)記的交換機(jī)投遞至目標(biāo)隊(duì)列,整個(gè)消息的投遞過(guò)程也就完成了叉讥。
總結(jié)一下窘行,可以看得出來(lái),通過(guò)過(guò)期消息和死信隊(duì)列雖然可以模擬延時(shí)隊(duì)列图仓,但還需要?jiǎng)?chuàng)建兩個(gè)交換機(jī)(死信隊(duì)列交換機(jī)+處理隊(duì)列交換機(jī))罐盔、兩個(gè)隊(duì)列(死信隊(duì)列+處理隊(duì)列),更何況無(wú)法達(dá)到一個(gè)靈活通用的延遲隊(duì)列救崔。而使用rabbitmq的延時(shí)消息插件方式惶看,只需要?jiǎng)?chuàng)建一個(gè)交換機(jī)和一個(gè)隊(duì)列捏顺,就可以做到延時(shí)靈活,明顯這種方式使用起來(lái)更簡(jiǎn)單纬黎、更靈活通用幅骄。
局限性:
延時(shí)消息插件實(shí)現(xiàn)RabbitMQ延時(shí)隊(duì)列這種方式也不完全是一個(gè)銀彈,它將延遲消息存在于Mnesia表中本今,并且在當(dāng)前節(jié)點(diǎn)上具有單個(gè)磁盤副本拆座,它們將在節(jié)點(diǎn)重啟之后幸存。
目前該插件的當(dāng)前設(shè)計(jì)并不真正適合包含大量延遲消息(例如數(shù)十萬(wàn)或數(shù)百萬(wàn))的場(chǎng)景冠息,詳情參見(jiàn) #/issues/72 另外該插件的一個(gè)可變性來(lái)源是依賴于 Erlang 計(jì)時(shí)器挪凑,在系統(tǒng)中使用了一定數(shù)量的長(zhǎng)時(shí)間計(jì)時(shí)器之后,它們開(kāi)始爭(zhēng)用調(diào)度程序資源逛艰,并且時(shí)間漂移不斷累積躏碳。
插件的禁用要慎重,以下方式可以實(shí)現(xiàn)將插件禁用瓮孙,但是注意如果此時(shí)還有延遲消息未消費(fèi)唐断,那么禁掉此插件后所有的未消費(fèi)的延遲消息將丟失。
rabbitmq-plugins disable rabbitmq_delayed_message_exchange
如果你采用了 Delayed Message 插件這種方式來(lái)實(shí)現(xiàn)杭抠,對(duì)于消息可用性要求較高的脸甘,在發(fā)現(xiàn)消息之前可以先落入 DB 打標(biāo)記,消費(fèi)之后將消息標(biāo)記為已消費(fèi)偏灿,中間可以加入定時(shí)任務(wù)做檢測(cè)丹诀,這可以進(jìn)一步保證你的消息的可靠性。