一口氣說出 6 種實(shí)現(xiàn)延時(shí)消息的方案
點(diǎn)擊上方“碼農(nóng)突圍”,馬上關(guān)注 這里是碼農(nóng)充電第一站,回復(fù)“666”,獲取一份專屬大禮包 真愛,請?jiān)O(shè)置“星標(biāo)”或點(diǎn)個(gè)“在看”

來源:juejin.cn/post/7052894117105238053
前言
實(shí)現(xiàn)方案
1.基于外部存儲(chǔ)實(shí)現(xiàn)的方案

基于 數(shù)據(jù)庫(如MySQL)
CREATE?TABLE?`delay_msg`?(
??`id`?bigint?unsigned?NOT?NULL?AUTO_INCREMENT,
??`delivery_time`?DATETIME?NOT?NULL?COMMENT?'投遞時(shí)間',
??`payloads`?blob?COMMENT?'消息內(nèi)容',
??PRIMARY?KEY?(`id`),
??KEY?`time_index`?(`delivery_time`)
)
實(shí)現(xiàn)簡單;
B+Tree索引不適合消息場景的大量寫入;
基于 RocksDB

RocksDB LSM 樹很適合消息場景的大量寫入;
實(shí)現(xiàn)方案較重,如果你采用這個(gè)方案,需要自己實(shí)現(xiàn) RocksDB 的數(shù)據(jù)容災(zāi)邏輯;
基于 Redis

Messages Pool 所有的延時(shí)消息存放,結(jié)構(gòu)為KV結(jié)構(gòu),key為消息ID,value為一個(gè)具體的message(這里選擇Redis Hash結(jié)構(gòu)主要是因?yàn)閔ash結(jié)構(gòu)能存儲(chǔ)較大的數(shù)據(jù)量,數(shù)據(jù)較多時(shí)候會(huì)進(jìn)行漸進(jìn)式rehash擴(kuò)容,并且對于HSET和HGET命令來說時(shí)間復(fù)雜度都是O(1)) Delayed Queue是16個(gè)有序隊(duì)列(隊(duì)列支持水平擴(kuò)展),結(jié)構(gòu)為ZSET,value 為 messages pool中消息ID,score為過期時(shí)間(分為多個(gè)隊(duì)列是為了提高掃描的速度) Worker 代表處理線程,通過定時(shí)任務(wù)掃描 Delayed Queue 中到期的消息
Redis ZSET 很適合實(shí)現(xiàn)延時(shí)隊(duì)列 性能問題,雖然 ZSET 插入是一個(gè) O(logn) 的操作,但是Redis 基于內(nèi)存操作,并且內(nèi)部做了很多性能方面的優(yōu)化。
定時(shí)線程檢查的缺陷與改進(jìn)
wait-notify 來節(jié)省 CPU 資源。2. 開源 MQ 中的實(shí)現(xiàn)方案
RocketMQ
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”,18個(gè)level。SCHEDULE_TOPIC_XXXX的topic中,并根據(jù) level 存入特定的queue,queueId = delayTimeLevel – 1,即一個(gè)queue只存相同延時(shí)的消息,保證具有相同發(fā)送延時(shí)的消息能夠順序消費(fèi)。 broker會(huì)調(diào)度地消費(fèi)SCHEDULE_TOPIC_XXXX,將消息寫入真實(shí)的topic。
Level 數(shù)固定,每個(gè) Level 有自己的定時(shí)器,開銷不大 將 Level 相同的消息放入到同一個(gè) Queue 中,保證了同一 Level 消息的順序性;不同 Level 放到不同的 Queue 中,保證了投遞的時(shí)間準(zhǔn)確性; 通過只支持固定的Level,將不同延時(shí)消息的排序變成了固定Level Topic 的追加寫操作
Level 配置的修改代價(jià)太大,固定 Level 不靈活 CommitLog 會(huì)因?yàn)檠訒r(shí)消息的存在變得很大
Pulsar

內(nèi)存開銷: 維護(hù)延時(shí)消息索引的隊(duì)列是放在堆外內(nèi)存中的,并且這個(gè)隊(duì)列是以訂閱組(Kafka中的消費(fèi)組)為維度的,比如你這個(gè) Topic 有 N 個(gè)訂閱組,那么如果你這個(gè) Topic 使用了延時(shí)消息,就會(huì)創(chuàng)建 N 個(gè) 隊(duì)列;并且隨著延時(shí)消息的增多,時(shí)間跨度的增加,每個(gè)隊(duì)列的內(nèi)存占用也會(huì)上升。(是的,在這個(gè)方案下,支持任意的延時(shí)消息反而有可能讓這個(gè)缺陷更嚴(yán)重) 故障轉(zhuǎn)移之后延時(shí)消息索引隊(duì)列的重建時(shí)間開銷: 對于跨度時(shí)間長的大規(guī)模延時(shí)消息,重建時(shí)間可能會(huì)到小時(shí)級別。(摘自 Pulsar 官方公眾號文章) 存儲(chǔ)開銷: 延時(shí)消息的時(shí)間跨度會(huì)影響到 Pulsar 中已經(jīng)消費(fèi)的消息數(shù)據(jù)的空間回收。打個(gè)比方,你的 Topic 如果業(yè)務(wù)上要求支持一個(gè)月跨度的延時(shí)消息,然后你發(fā)了一個(gè)延時(shí)一個(gè)月的消息,那么你這個(gè) Topic 中底層的存儲(chǔ)就會(huì)保留整整一個(gè)月的消息數(shù)據(jù),即使這一個(gè)月中99%的正常消息都已經(jīng)消費(fèi)了。

QMQ
2 * 366 * 24 = 17568 個(gè)文件(如果需要支持的最大延時(shí)時(shí)間更短,則生成的文件更少)。hash wheel上,內(nèi)存中的hash wheel則是以500ms為一個(gè)刻度。
時(shí)間輪算法適合延時(shí)/定時(shí)消息的場景,省去延時(shí)消息的排序,插入刪除操作都是 O(1) 的時(shí)間復(fù)雜度; 通過多級時(shí)間輪設(shè)計(jì),支持了超大時(shí)間跨度的延時(shí)消息; 通過延時(shí)加載,內(nèi)存中只會(huì)有最近要消費(fèi)的消息,更久的延時(shí)消息會(huì)被存儲(chǔ)在磁盤中,對內(nèi)存友好; 延時(shí)消息單獨(dú)存儲(chǔ)(schedule log),不會(huì)影響到正常消息的空間回收;
總結(jié)
(完)
碼農(nóng)突圍資料鏈接
1、臥槽!字節(jié)跳動(dòng)《算法中文手冊》火了,完整版 PDF 開放下載!
2、計(jì)算機(jī)基礎(chǔ)知識總結(jié)與操作系統(tǒng) PDF 下載
3、艾瑪,終于來了!《LeetCode Java版題解》.PDF
4、Github 10K+,《LeetCode刷題C/C++版答案》出爐.PDF歡迎添加魚哥個(gè)人微信:smartfish2020,進(jìn)粉絲群或圍觀朋友圈
評論
圖片
表情
