高可用延遲隊(duì)列設(shè)計(jì)與實(shí)現(xiàn)

延遲隊(duì)列:一種帶有 延遲功能 的消息隊(duì)列

  1. 延時(shí) → 未來一個(gè)不確定的時(shí)間
  2. mq → 消費(fèi)行為具有順序性

這樣解釋,整個(gè)設(shè)計(jì)就清楚了苛谷。你的目的是 延時(shí),承載容器是 mq独悴。

背景

列舉一下我日常業(yè)務(wù)中可能存在的場(chǎng)景:

  1. 建立延時(shí)日程赫蛇,需要提醒老師上課
  2. 延時(shí)推送 → 推送老師需要的公告以及作業(yè)

為了解決以上問題,最簡(jiǎn)單直接的辦法就是定時(shí)去掃表:

服務(wù)啟動(dòng)時(shí)悟耘,開啟一個(gè)異步協(xié)程 → 定時(shí)掃描 msg table织狐,到了事件觸發(fā)事件,調(diào)用對(duì)應(yīng)的 handler

幾個(gè)缺點(diǎn):

  1. 每一個(gè)需要定時(shí)/延時(shí)任務(wù)的服務(wù)旺嬉,都需要一個(gè) msg table 做額外存儲(chǔ) → 存儲(chǔ)與業(yè)務(wù)耦合
  2. 定時(shí)掃描 → 時(shí)間不好控制厨埋,可能會(huì)錯(cuò)過觸發(fā)時(shí)間
  3. 對(duì) msg table instance 是一個(gè)負(fù)擔(dān)。反復(fù)有一個(gè)服務(wù)不斷對(duì)數(shù)據(jù)庫(kù)產(chǎn)生持續(xù)不斷的壓力

最大問題其實(shí)是什么雨效?

調(diào)度模型基本統(tǒng)一废赞,不要做重復(fù)的業(yè)務(wù)邏輯

我們可以考慮將邏輯從具體的業(yè)務(wù)邏輯里面抽出來,變成一個(gè)公共的部分据悔。

而這個(gè)調(diào)度模型耘沼,就是 延時(shí)隊(duì)列

其實(shí)說白了:

延時(shí)隊(duì)列模型群嗤,就是將未來執(zhí)行的事件提前存儲(chǔ)好,然后不斷掃描這個(gè)存儲(chǔ)浸赫,觸發(fā)執(zhí)行時(shí)間則執(zhí)行對(duì)應(yīng)的任務(wù)邏輯。

那么開源界是否已有現(xiàn)成的方案呢既峡?答案是肯定的。Beanstalk (https://github.com/beanstalkd/beanstalkd) 它基本上已經(jīng)滿足以上需求

設(shè)計(jì)目的

  1. 消費(fèi)行為 at least
  2. 高可用
  3. 實(shí)時(shí)性
  4. 支持消息刪除

依次說說上述這些目的的設(shè)計(jì)方向:

消費(fèi)行為

這個(gè)概念取自 mq 校仑。mq 中提供了消費(fèi)投遞的幾個(gè)方向:

  • at most once → 至多一次传惠,消息可能會(huì)丟,但不會(huì)重復(fù)
  • at least once → 至少一次羊瘩,消息肯定不會(huì)丟失盼砍,但可能重復(fù)
  • exactly once → 有且只有一次,消息不丟失不重復(fù)浇坐,且只消費(fèi)一次。

exactly once 盡可能是 producer + consumer 兩端都保證擒贸。當(dāng) producer 沒辦法保證是觉渴,那 consumer 需要在消費(fèi)前做一個(gè)去重,達(dá)到消費(fèi)過一次不會(huì)重復(fù)消費(fèi)蜕猫,這個(gè)在延遲隊(duì)列內(nèi)部直接保證哎迄。

最簡(jiǎn)單:使用 redis 的 setNX 達(dá)到 job id 的唯一消費(fèi)

高可用

支持多實(shí)例部署。掛掉一個(gè)實(shí)例后漱挚,還有后備實(shí)例繼續(xù)提供服務(wù)。

這個(gè)對(duì)外提供的 API 使用 cluster 模型蹬屹,內(nèi)部將多個(gè) node 封裝起來,多個(gè) node 之間冗余存儲(chǔ)贩耐。

為什么不使用 kafka厦取?

考慮過類似基于 kafka/rocketmq 等消息隊(duì)列作為存儲(chǔ)的方案,最后從存儲(chǔ)設(shè)計(jì)模型放棄了這類選擇虾攻。

舉個(gè)例子,假設(shè)以 Kafka 這種消息隊(duì)列存儲(chǔ)來實(shí)現(xiàn)延時(shí)功能奇钞,每個(gè)隊(duì)列的時(shí)間都需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的 topic(如: Q1-1s, Q1-2s..)漂坏。這種設(shè)計(jì)在延時(shí)時(shí)間比較固定的場(chǎng)景下問題不太大,但如果是延時(shí)時(shí)間變化比較大會(huì)導(dǎo)致 topic 數(shù)目過多纠亚,會(huì)把磁盤從順序讀寫會(huì)變成隨機(jī)讀寫從導(dǎo)致性能衰減筋夏,同時(shí)也會(huì)帶來其他類似重啟或者恢復(fù)時(shí)間過長(zhǎng)的問題图呢。

  1. topic 過多 → 存儲(chǔ)壓力
  2. topic 存儲(chǔ)的是現(xiàn)實(shí)時(shí)間,在調(diào)度時(shí)對(duì)不同時(shí)間 (topic) 的讀取蛤织,順序讀 → 隨機(jī)讀
  3. 同理,寫入的時(shí)候順序?qū)?→ 隨機(jī)寫

架構(gòu)設(shè)計(jì)

image

API 設(shè)計(jì)

producer

  1. producer.At(msg []byte, at time.Time)
  2. producer.Delay(body []byte, delay time.Duration)
  3. producer.Revoke(ids string)

consumer

  1. consumer.Consume(consume handler)

使用延時(shí)隊(duì)列后,服務(wù)整體結(jié)構(gòu)如下摊鸡,以及隊(duì)列中 job 的狀態(tài)變遷:

image
  1. service → producer.At(msg []byte, at time.Time) → 插入延時(shí)job到 tube 中
  2. 定時(shí)觸發(fā) → job 狀態(tài)更新為 ready
  3. consumer 獲取到 ready job → 取出 job免猾,開始消費(fèi);并更改狀態(tài)為 reserved
  4. 執(zhí)行傳入 consumer 中的 handler 邏輯處理函數(shù)

生產(chǎn)實(shí)踐

主要介紹一下在日常開發(fā)猎提,我們使用到延時(shí)隊(duì)列的哪些具體功能。

生產(chǎn)端

  1. 開發(fā)中生產(chǎn)延時(shí)任務(wù)疙教,只需確定任務(wù)執(zhí)行時(shí)間
    1. 傳入 At() producer.At(msg []byte, at time.Time)
    2. 內(nèi)部會(huì)自行計(jì)算時(shí)間差值,插入 tube
  2. 如果出現(xiàn)任務(wù)時(shí)間的修改贞谓,以及任務(wù)內(nèi)容的修改
    1. 在生產(chǎn)時(shí)可能需要額外建立一個(gè) logic_id → job_id 的關(guān)系表
    2. 查詢到 job_id → producer.Revoke(ids string) 经宏,對(duì)其刪除,然后重新插入

消費(fèi)端

首先烁兰,框架層面保證了消費(fèi)行為的 exactly once ,但是上層業(yè)務(wù)邏輯消費(fèi)失敗或者是出現(xiàn)網(wǎng)絡(luò)問題广辰,亦或者是各種各樣的問題主之,導(dǎo)致消費(fèi)失敗,兜底交給業(yè)務(wù)開發(fā)做槽奕。這樣做的原因:

  1. 框架以及基礎(chǔ)組件只保證 job 狀態(tài)的流轉(zhuǎn)正確性
  2. 框架消費(fèi)端只保證消費(fèi)行為的統(tǒng)一
  3. 延時(shí)任務(wù)在不同業(yè)務(wù)中行為不統(tǒng)一
    1. 強(qiáng)調(diào)任務(wù)的必達(dá)性,則消費(fèi)失敗時(shí)需要不斷重試直到任務(wù)成功
    2. 強(qiáng)調(diào)任務(wù)的準(zhǔn)時(shí)性所森,則消費(fèi)失敗時(shí)夯接,對(duì)業(yè)務(wù)不敏感則可以選擇丟棄

這里描述一下框架消費(fèi)端是怎么保證消費(fèi)行為的統(tǒng)一:

分為 cluster 和 node。cluster

https://github.com/tal-tech/go-queue/blob/master/dq/consumer.go#L45

  1. cluster 內(nèi)部將 consume handler 做了一層再封裝
  2. 對(duì) consume body 做hash晴弃,并使用此 hash 作為 redis 去重的key
  3. 如果存在逊拍,則不做處理,丟棄

node

https://github.com/tal-tech/go-queue/blob/master/dq/consumernode.go#L36

  1. 消費(fèi) node 獲取到 ready job旗国;先執(zhí)行 Reserve(TTR)注整,預(yù)訂此job度硝,將執(zhí)行該job進(jìn)行邏輯處理
  2. 在 node 中 delete(job)寿冕;然后再進(jìn)行消費(fèi)
    1. 如果失敗,則上拋給業(yè)務(wù)層驼唱,做相應(yīng)的兜底重試

所以對(duì)于消費(fèi)端,開發(fā)者需要自己實(shí)現(xiàn)消費(fèi)的冪等性辨赐。

image

項(xiàng)目地址

go-queue 是基于 go-zero 實(shí)現(xiàn)的京办,go-zero 在 github 上 Used by 有300+,開源一年獲得11k+ stars.

歡迎使用并 star 支持我們不恭!

微信交流群

關(guān)注『微服務(wù)實(shí)踐』公眾號(hào)并點(diǎn)擊 交流群 獲取社區(qū)群二維碼财饥。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沾瓦,隨后出現(xiàn)的幾起案子谦炒,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件响驴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡秽誊,警方通過查閱死者的電腦和手機(jī)琳骡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來最易,“玉大人,你說我怎么就攤上這事藻懒。” “怎么了归敬?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鄙早,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我舱污,道長(zhǎng)扳缕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任驴剔,我火速辦了婚禮粥庄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惜互。我一直安慰自己,他們只是感情好描验,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布坑鱼。 她就那樣靜靜地躺著,像睡著了一般呼股。 火紅的嫁衣襯著肌膚如雪画恰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天缠局,我揣著相機(jī)與錄音,去河邊找鬼甩鳄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛档泽,可吹牛的內(nèi)容都是我干的揖赴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼燥滑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了赃蛛?” 一聲冷哼從身側(cè)響起搀菩,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歧蒋,沒想到半個(gè)月后州既,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阐虚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年蚌卤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诫龙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出签赃,到底是詐尸還是另有隱情,我是刑警寧澤锦聊,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布孔庭,位于F島的核電站,受9級(jí)特大地震影響圆到,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜马绝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一挣菲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧白胀,春花似錦、人聲如沸纹份。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)元暴。三九已至,卻和暖如春茉盏,著一層夾襖步出監(jiān)牢的瞬間枢冤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工讶迁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留核蘸,地道東北人巍糯。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓祟峦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親宅楞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容