什麼是訊息佇列
訊息佇列是一種非同步的服務間通訊方式,適用於無伺服器和微服務架構。
訊息在被處理和刪除之前一直儲存在佇列上。每條訊息僅可被一位使用者處理一次。
訊息佇列可被用於分離重量級處理、緩衝或批次處理工作以及緩解高峰期工作負載。
提供訊息的我們稱為生產者;接收訊息的我們稱為消費者。
為什麼要用訊息佇列
目的:解耦、非同步、削峰。
一、解耦
通常我們的程式碼是存在呼叫鏈的,如果是一個單體裡面,直接函數呼叫,等待返回就好了。但是在分散式、微服務中,我們可能不知道上下游的改動或者可靠性。
比如A給BCD提供訊息。
- 如果要新增E或者去掉C,則需要改A的程式碼。
- 如果B掛了,A怎麼給B補發?
- 總之,BCD要改,A必須要跟著改。
改成:
A給訊息佇列提供訊息。BCD從訊息佇列拿。(釋出訂閱模型)
- 新增、刪除 BCD自己決定。
- 消費者成功與否,生產者不必關心。
總結:通過一個 MQ,Pub/Sub 釋出訂閱訊息這麼一個模型,A 系統就跟其它系統徹底解耦了。
帶來問題:
- 丟資料。 B取了資料,消費的過程中掛了。則資料丟失。改:加入返回機制,消費完了通知A。
- 重複消費。B消費了但是沒通知A消費了,然後掛了,再獲取的時候,會把掛之前取的訊息再消費一遍。
二、非同步
順序執行:A-B-C
改成:先寫入佇列,直接返回,ABC並行執行。
問題:順序性。邏輯是要順序,但是並行可能導致亂序。
三、削峰
當上遊的流量衝擊過大, 想要不被打垮,要麼拋棄這些流量,要麼儲存這些流量。很顯然,存起來在大多數時候是更好地選擇。
注意這裡是大多數,有時候一些具有時效性的場景,拋棄可能反而更好。
將流量存起來,在空閒的時候慢慢消費,緩衝上下游的瞬時突發流量,使其更平滑,增加系統的可用性,這就是削峰填谷。
訊息佇列的兩種形式
對等
- 很原始的模式,如果消費者要增加則必須要改生產者。優點是順序消費。
- 像打電話。
訂閱釋出:
- 生產者不用關心消費者的數量。可以自由擴充套件數量。
- 像訂報紙。
訊息佇列帶來的問題
沒有銀彈,都是驅虎吞狼而已。
- 可用性降低:訊息佇列掛了,整個系統全掛。
- 複雜度增加:要解決 訊息丟失、重複消費、順序性等問題。
- 一致性問題:BCD並行執行,如果B掛了,那麼CD產生的資料就會有缺陷。
常見問題:
delivery guarantee:
- At most once:訊息可能會丟,但絕不會重複傳輸
- At least one:訊息絕不會丟,但可能會重複傳輸
- Exactly once:每條訊息肯定會被傳輸一次且僅傳輸一次,很多時候這是使用者所想要的。
1. 重複訊息
消費者消費了但是還沒來得及記錄已讀標記就掛了,重啟後則會重複讀取。
最常見的解決方案是消費者保持冪等性。即多次消費結果不變。主要是因為訊息丟失更不可控。
程式碼層一般是新增特殊標記,如果已經消費過了則過濾掉。
比較複雜的還有雙刪和事務。
2. 訊息丟失
- 生產者丟資料:生產者在收到訊息佇列的接收確認後再刪除訊息(ACK)。比較耗效能。
- MQ丟資料:本地持久化、冗餘備份、叢集高可用。
- 消費者丟資料:消費者先記錄標記,但是沒有消費就掛了,重啟後則該訊息丟失。所以一般是先消費再標記(commit),寧可多訊息不願少訊息。
3. 順序消費
- 分散式事務模型。即全域性鎖之類的方式。
- 將需要順序執行的事情給一個服務做。通過雜湊等演演算法保證同一個標誌(比如使用者ID)的訊息總是給固定的服務處理,在單一服務內部是順序的。
- 讓BCD之間橫向通訊。比如B的邏輯需要CD來補充,則CD做完了通知B一下,B去補充自己的邏輯即可。
4. 訊息積壓的處理
- 緊急擴容消費者,加快消費速度。
- 過期失效。拋棄一部分訊息,降級服務,優先保證系統整體的穩定性。
- 訊息緊急性,讓緊急的事情優先處理。
消費模型 Push vs Pull
消費者拉取 Pull
即消費者自己找訊息佇列拿,自己一口一口吃。
- 消費速度 消費者自己決定
- Consumer 可以自己控制消費方式——即可批次消費也可逐條消費,同時還能選擇不同的提交方式從而實現不同的傳輸語意。
- 消費者需要輪訓。(可能造成無效等待,可以採用長輪訓)
- 長輪詢:沒有訊息就等30秒,還是沒有再斷開重連,減少了握手開銷,但是依然會佔用連線數。
- 訊息反壓 :消費者速度慢,反壓上游。
訊息佇列推播 Push
即生產者強行推給消費者,即養豬,不管吃不吃的完,先倒進去再說。
- 減少了消費者的無效等待。
- 消費者處理不過來的時候可能會丟失。
- 可以考慮有訊息的時候廣播 ,消費者自己維護一個佇列。