RabbitMQ 延時訊息設計
問題背景
- 所謂"延時訊息"是指當訊息被髮送以後,並不想讓消費者立即拿到訊息,而是等待指定時間後,消費者才拿到這個訊息進行消費。
- 場景一:客戶A在十二點下了一個訂單,我想半個小時後來檢查一下這個訂單的付款狀態,根據付款狀態來作下一步的處理。 a. 針對場景一,建議採用方案資料庫儲存+schedule的方式也許更合適。
- 場景二:mdc系統更新了一個A資訊,我要通知給A門店資訊發生了變化,通知他們回撥API來讀取最新的值。
如果拿到訊息後立即回撥,可能因為mdc事務、快取、從庫延遲等原因,拿到變化前的資訊,所以
目標
- 可以實現訊息按照自定義的時間延遲傳送。
- 最好做到對訊息生產者和消費者透明,不修改現有應用程式。
總體方案
3.1. 實現原理
- AMQP和RabbitMQ本身沒有直接支援延遲佇列功能,但是可以通過以下特性模擬出延遲佇列的功能。
- RabbitMQ可以針對Queue和Message設定 x-message-ttl,來控制訊息的生存時間,如果超時,則訊息變為dead letter
- RabbitMQ的Queue可以配置x-dead-letter-exchange 和 x-dead-letter-routing-key(可選)兩個引數,用來控制佇列內出現了
結合以上兩個特性,就可以模擬出延遲訊息的功能
參考資料:
- https://www.cloudamqp.com/docs/delayed-messages.html
- http://www.rabbitmq.com/ttl.html
- http://www.rabbitmq.com/dlx.html
- http://www.rabbitmq.com/maxlength.html
3.2. 技術方案
|
方案一:針對Queue設定延遲時間 |
方案二:針對Message設定延遲時間 |
|
|
|
|
|
方案關鍵點 |
針對需要延遲的Queue配置ttl引數 |
針對Queue設定最大延遲時間 |
|
|
1. 優點: |
傳送方對每個Message設定有效延遲時間 |
|
|
a. 維護簡單 |
|
|
|
b. 客戶端完全透明 |
1. 優點 |
|
|
c. 針對每個延遲時間建立一個延遲隊 |
a. 傳送時可以自定義延遲時間 |
|
|
列 |
2. 缺點 |
|
|
2. 缺點: |
a. 需要升級客戶端,對客戶端不透明 |
|
|
a. 傳送方無法自定義延遲時間 |
b. 需要針對每個佇列建立不同的延遲 |
|
|
b. 延遲時間在建Queue時確定,修改 |
佇列 |
|
|
不便 |
c. 帶延遲引數的send方法容易誤用, |
|
|
c. 修改延遲時間需要在MQ叢集重新進 |
很難發現 |
|
|
行配置 |
|
|
選擇結果 |
最終選擇了方案一 |
|
|
3.3. 訊息傳送流程
3.3.1. 原有業務流程
3.3.2. 自產自消的延遲訊息流程
3.3.3. 普通的延遲訊息流程
4. 操作步驟
4.1. 建立 delay.exchange
注意事項:
1. 不要設定為Internal,否則將無法接受dead letter
4.2. 建立延時佇列(delay queue)
注意事項:
- 按照延期時間配置queue的名字,建議命名規則為delay.{time}.queue,使用delay字首方便排序和做一些許可權控制
- cos線上叢集預設配置了delay.1m.queue、delay.5m.queue、delay.15m.queue三個佇列
- 通過TTL設定佇列的延期時間,對應不同的Queue
- MAX length為最大的積壓的訊息個數,推薦設定為100w~500w之間,推測方法見積壓測試結果,
- 超過MAX length限制以後,佇列頭部的訊息會立即轉到delay.exchange進行投遞,不會造成訊息丟失
- 設定dead letter exchange為剛才配置的 delay.exchange
注意不要配置"dead letter routing key"否則會覆蓋掉訊息傳送時攜帶的routingkey,導致後面無法路由
4.3. 配置延時路由規則
4.3.1. 需要延時的訊息到exchange後先路由到指定的延時佇列
4.3.2. delay.exchange 再重新把訊息路由到正常的queue或exchang中
4.3.3. 消費者和以前一樣從正常queue中接收消費訊息
5. 積壓訊息測試結果
5.1. 積壓測試結論
從實現原理上看,對於持久化訊息,記憶體主要儲存的是訊息的索引資料,從測試結果也可以驗證,可以得出以下資料:
- 記憶體佔用方面估算
- 訊息佔用記憶體的大小確實和訊息體本身大小無關,和訊息個數直接相關。
- 訊息體為40位元組的字串,積壓10萬訊息,佔用101MB記憶體,23萬訊息,佔用230MB記憶體
- 訊息體為一個整數,積壓7萬訊息,佔用64MB記憶體,24萬訊息,佔用240MB記憶體
- 一個訊息約佔1KB的記憶體,以10GB記憶體,留一半餘量:5GB/1K=500 0000
- 線上單個queue推薦線上的max length不要超過500萬,根據延遲時間和訊息量來調整此值的大小。
- 訊息佔用記憶體的大小確實和訊息體本身大小無關,和訊息個數直接相關。
- 訊息量估算
- 以30分鐘延遲,每秒傳送1000個訊息為例,則最大值為:30*60*1000=180 0000
- 倒推:以500萬,30分鐘延遲為例,則每秒最多傳送的訊息個數為:5000000/30/60=2777
- 訊息積壓數超過max length以後,訊息不會丟失,只會導致訊息會被提前消費。
結論:1. MAX length推薦這個值設定為100萬~500萬,既可以滿足業務需求,又不超過記憶體限制
5.2. 40位元組訊息積壓26萬
5.3. 4位元組訊息積壓7萬
5.4. 4位元組訊息積壓24萬
5.5. 4位元組訊息積壓31萬
5.6. 4位元組訊息積壓64萬
總結:經過壓測及基礎配置,延時訊息可以滿足目前的需求,實現訊息的延遲處理。
不知道延遲佇列加上惰性佇列的組合,即可以減小記憶體佔用,又可以實現訊息的延遲處理,也是一個非常好的方案。蛋糕和咖啡真的很配。