大白話設(shè)計(jì)RocketMQ延遲消息
延遲消息一般用于:提前發(fā)送消息,延遲一段時(shí)間后才需要被處理的場(chǎng)景。比如:下單半小時(shí)后還未支付,則取消訂單 釋放庫(kù)存 等。
RocketMQ的延遲消息使用上非常便捷,但是不支持任意時(shí)間的延遲,這一點(diǎn)對(duì)于有強(qiáng)迫癥的朋友來說就比較難受,但是搞明白為什么這么設(shè)計(jì)后,就自然釋懷了。
為什么RocketMQ不支持任意時(shí)間的延時(shí)?為什么延遲時(shí)間只能是從1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h這些時(shí)間段里選?如果讓你來設(shè)計(jì)RocketMQ的延遲消息,你會(huì)怎么設(shè)計(jì)?本文從以上幾個(gè)問題聊聊RocketMQ的延遲消息。
一、使用延遲消息
RocketMQ不支持任意時(shí)間的延遲,只有18個(gè)等級(jí)的延遲時(shí)間,默認(rèn)是:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h。從頭到尾共18個(gè)等級(jí)時(shí)間,s、m、h、d,分別表示秒、分、時(shí)、天。
默認(rèn)的18個(gè)等級(jí)對(duì)應(yīng)的時(shí)間可以修改,在broker.conf中增加如下配置,根據(jù)自身需求修改時(shí)間,然后重啟broker。
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
RocketMQ發(fā)送延遲消息只需要給消息設(shè)置延遲時(shí)間級(jí)別setDelayTimeLevel。
DefaultMQProducer producer = new DefaultMQProducer("TestProducerGroup");
Message rocketMsg = new Message(topic, tags, payloads);
// delayLevel=0時(shí),無(wú)需延遲
if (delayLevel > 0) {
rocketMsg.setDelayTimeLevel(delayLevel);
}
SendResult sendResult = producer.send(rocketMsg, timeout);
二、延遲消息的原理
1.你會(huì)怎么設(shè)計(jì)
如果讓你來設(shè)計(jì)RocketMQ的延遲消息,你會(huì)怎么設(shè)計(jì)?筆者會(huì)這樣設(shè)計(jì):
- 延遲消息也是個(gè)消息,只是多了延遲時(shí)間,既然是消息,不管是不是要立刻處理,先找個(gè)臨時(shí)Topic存儲(chǔ)起來。
- Topic里面實(shí)際上是一個(gè)個(gè)隊(duì)列,那所有的延遲消息要存在一個(gè)隊(duì)列里嗎?不要放在同一個(gè)隊(duì)列里,因?yàn)橄⒏髯远加胁煌难舆t時(shí)間,如果放在一個(gè)隊(duì)列里,會(huì)牽扯到其余問題:比如排序、比如記錄消費(fèi)位置等。所以是按延遲時(shí)間分開存。
- 消息已經(jīng)存起來了,那怎么處理呢?既然涉及到了延遲時(shí)間,那自然啟動(dòng)線程去定時(shí)獲取消息,判斷消息的延遲時(shí)間是否已經(jīng)到達(dá),到達(dá)之后則取出來投放到目的Topic。
2.粗略架構(gòu)圖
講到這里,延遲消息的架構(gòu)圖基本浮現(xiàn)出來了:
3.RocketMQ的設(shè)計(jì)
實(shí)際上RocketMQ在設(shè)計(jì)延遲消息時(shí),跟上面的思路基本類似,不在贅述,額外補(bǔ)充幾點(diǎn):
- 消息進(jìn)入Broker后,會(huì)被存儲(chǔ)在TopicSCHEDULE_TOPIC_XXXX中,只是在Dashboard中看不到。
- TopicSCHEDULE_TOPIC_XXXX中有18個(gè)消息隊(duì)列,分別存儲(chǔ)18個(gè)延遲等級(jí)對(duì)應(yīng)的消息。
- RocketMQ 在啟動(dòng)時(shí),會(huì)從broker.conf中獲取18個(gè)等級(jí)對(duì)應(yīng)的時(shí)間,延遲等級(jí)和時(shí)間的關(guān)系會(huì)存在放到DelayLevelTable中。
- RocketMQ會(huì)開啟18個(gè)定時(shí)任務(wù)每隔100ms,從TopicSCHEDULE_TOPIC_XXXX判斷18個(gè)隊(duì)列里的第一個(gè)消息是否可以被投放,如果可以投放,則在投放到原本的目的Topic。判斷邏輯:存入時(shí)間+延遲時(shí)間 > 當(dāng)前時(shí)間。
三、為什么不支持自定義延時(shí)時(shí)間
說到這里,估計(jì)你也能猜到,為什么不支持自定義延遲時(shí)間了,核心原因還是性能問題。
試想一下,如果設(shè)計(jì)成任意時(shí)間,那么就不可能使用18個(gè)隊(duì)列了,更不可能使用無(wú)限個(gè)隊(duì)列了,只可能使用單個(gè)隊(duì)列。
但是如果使用單個(gè)隊(duì)列,按照先進(jìn)先出的存放的話,那出現(xiàn)需要后進(jìn)先出的消息怎么辦?那只能對(duì)整個(gè)隊(duì)列進(jìn)行排序,如果消息量很大,每次有消息進(jìn)來都需要排序,那CPU肯定會(huì)被玩爆。
而且隊(duì)列里的消息被消費(fèi)后,都會(huì)記錄偏移量,如果每次有消息進(jìn)來都要排序,那偏移量則失去意義,增加了消息丟失的風(fēng)險(xiǎn)。
所以,RocketMQ的這種18個(gè)延遲時(shí)間等級(jí)的設(shè)計(jì),雖然在延遲時(shí)間的自由度上作出了妥協(xié),但是基本滿足業(yè)務(wù),性能也很優(yōu)秀。
四、總結(jié)
本文聊了RocketMQ延遲消息的使用、原理、解答了部分疑問。核心概念:臨時(shí)Topic、目的Topic、定時(shí)任務(wù)、18個(gè)延遲等級(jí)、18個(gè)消息隊(duì)列。RocketMQ延遲消息的設(shè)計(jì)方式,是一種兼顧了性能和業(yè)務(wù)的優(yōu)秀設(shè)計(jì)。