訊息佇列簡介

2023-03-21 15:01:52

什麼是訊息佇列

訊息佇列是一種非同步的服務間通訊方式,適用於無伺服器和微服務架構。

訊息在被處理和刪除之前一直儲存在佇列上。每條訊息僅可被一位使用者處理一次。

訊息佇列可被用於分離重量級處理、緩衝或批次處理工作以及緩解高峰期工作負載。

提供訊息的我們稱為生產者;接收訊息的我們稱為消費者。

為什麼要用訊息佇列

目的:解耦、非同步、削峰。

一、解耦

通常我們的程式碼是存在呼叫鏈的,如果是一個單體裡面,直接函數呼叫,等待返回就好了。但是在分散式、微服務中,我們可能不知道上下游的改動或者可靠性。

比如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. 重複訊息

消費者消費了但是還沒來得及記錄已讀標記就掛了,重啟後則會重複讀取。

最常見的解決方案是消費者保持冪等性。即多次消費結果不變。主要是因為訊息丟失更不可控。

程式碼層一般是新增特殊標記,如果已經消費過了則過濾掉。

比較複雜的還有雙刪和事務。

  • 雙刪:先記錄已讀index在本地,消費完後通知訊息佇列(commit),刪除已讀index。重啟時如果有已讀index,則從該index開始讀取,沒有則看訊息佇列上的標記。(由於commit和刪除index的操作很快,所以失敗一個可能性較小)。

  • 事務:消費過程和commit做成事務,但是會影響效能。

2. 訊息丟失

  • 生產者丟資料:生產者在收到訊息佇列的接收確認後再刪除訊息(ACK)。比較耗效能。
  • MQ丟資料:本地持久化、冗餘備份、叢集高可用。
  • 消費者丟資料:消費者先記錄標記,但是沒有消費就掛了,重啟後則該訊息丟失。所以一般是先消費再標記(commit),寧可多訊息不願少訊息。

3. 順序消費

  • 分散式事務模型。即全域性鎖之類的方式。
  • 將需要順序執行的事情給一個服務做。通過雜湊等演演算法保證同一個標誌(比如使用者ID)的訊息總是給固定的服務處理,在單一服務內部是順序的。
  • 讓BCD之間橫向通訊。比如B的邏輯需要CD來補充,則CD做完了通知B一下,B去補充自己的邏輯即可。

4. 訊息積壓的處理

  • 緊急擴容消費者,加快消費速度。
  • 過期失效。拋棄一部分訊息,降級服務,優先保證系統整體的穩定性。
  • 訊息緊急性,讓緊急的事情優先處理。

消費模型 Push vs Pull

消費者拉取 Pull

即消費者自己找訊息佇列拿,自己一口一口吃。

  • 消費速度 消費者自己決定
  • Consumer 可以自己控制消費方式——即可批次消費也可逐條消費,同時還能選擇不同的提交方式從而實現不同的傳輸語意。
  • 消費者需要輪訓。(可能造成無效等待,可以採用長輪訓)
    • 長輪詢:沒有訊息就等30秒,還是沒有再斷開重連,減少了握手開銷,但是依然會佔用連線數。
  • 訊息反壓 :消費者速度慢,反壓上游。

訊息佇列推播 Push

即生產者強行推給消費者,即養豬,不管吃不吃的完,先倒進去再說。

  • 減少了消費者的無效等待。
  • 消費者處理不過來的時候可能會丟失。
  • 可以考慮有訊息的時候廣播 ,消費者自己維護一個佇列。