讀構建可擴充套件分散式系統:方法與實踐06非同步訊息傳遞

躺柒發表於2024-09-17

1. 非同步訊息傳遞

1.1. 通訊是分散式系統的基礎,也是架構師需要納入其系統設計的主要問題

1.2. 客戶端傳送請求並等待伺服器響應

  • 1.2.1. 這就是大多數分散式通訊的設計方式,因為客戶端需要得到即時響應後才能繼續

  • 1.2.2. 並非所有系統都有這個要求

1.3. 使用非同步通訊的方式,客戶端(稱為生產者)將其請求傳送到中間訊息傳遞服務

1.4. 生產者對他們傳送的請求“發後即忘”(fire and forget)

  • 1.4.1. 一旦請求被傳遞到訊息傳遞服務,生產者就會進入其邏輯中的下一步,並確信它傳送的請求最終得到處理

  • 1.4.2. 訊息機制提高了系統的響應能力,因為生產者不必等到請求處理完成

1.5. 非同步訊息傳遞是可擴充套件系統架構的一個組成部分

1.6. 訊息傳遞機制在經歷請求高峰和低谷的系統中特別有吸引力

  • 1.6.1. 在高峰時段,生產者可以將請求新增到佇列中並快速響應客戶端,而無須等待請求被處理

1.7. 訊息佇列可以分佈在多個代理之間以擴充套件訊息吞吐量,也可以複製佇列來提高可用性

1.8. 訊息機制並非不存在風險

  • 1.8.1. 將訊息副本放在佇列中,如果佇列保留在記憶體中,則訊息可能會丟失

1.9. 將訊息副本放在佇列中,如果佇列保留在記憶體中,則訊息可能會丟失

2. 訊息傳遞簡介

2.1. 非同步訊息傳遞平臺是一個成熟的技術領域

  • 2.1.1. 久負盛名的IBM MQ系列出現於1993年,至今仍是企業系統的中流砥柱

  • 2.1.2. Java訊息傳遞服務(JMS)是一種API級別的規範,由多個JEE供應商實現和支援

  • 2.1.3. RabbitMQ,可以說是部署最廣泛的開源訊息傳遞系統

2.2. 訊息傳遞原語

  • 2.2.1. 訊息佇列

  • 2.2.1.1. 儲存一系列訊息的佇列

  • 2.2.2. 生產者

  • 2.2.2.1. 將訊息傳送到佇列

  • 2.2.2.2. 生產者將訊息傳送到代理上的命名佇列

  • 2.2.3. 消費者

  • 2.2.3.1. 從佇列中取出訊息

  • 2.2.3.2. 多個消費者可以從同一個佇列中獲取訊息

  • 2.2.3.3. 消費者獲取訊息有兩種行為模式,即拉取或推送

>  2.2.3.3.1. 在拉取(也稱為輪詢)模式中,消費者向代理傳送請求,代理用下一條可供處理的訊息進行響應

>  2.2.3.3.2. 在推送模式下,消費者告知代理自己希望從佇列中接收訊息

  >   2.2.3.3.2.1. 消費者提供了一個回撥函式,當訊息可用時應呼叫該函式

  >   2.2.3.3.2.2. 然後消費者就會阻塞(或執行其他工作)​,訊息代理會在有訊息可用時將訊息傳遞給回撥函式進行處理

  >   2.2.3.3.2.3. 使用推送模式更加高效

2.2.3.3.2.3.1. 避免了代理可能被來自多個消費者的請求壓垮,並使代理能更高效地實現訊息傳遞

2.2.3.3.2.3.2. 消費者確認後,代理就可以將訊息標記為已傳遞,並將其從佇列中刪除

2.2.3.3.2.3.3. 如果使用自動確認,訊息傳遞給消費者之後,在訊息處理之前代理就會收到確認

  • 2.2.4. 訊息代理

  • 2.2.4.1. 訊息代理是一個服務,管理著一個或多個佇列

  • 2.2.4.2. 訊息代理可以在同一硬體上管理多個佇列

2.3. 通常會有消費者希望確保訊息在確認之前得到完全處理

  • 2.3.1. 它將使用手動確認的方式

  • 2.3.2. 可以防止出現訊息已經被傳遞給消費者,但由於消費者崩潰而導致訊息未被處理的問題

  • 2.3.3. 確實會增加訊息確認的延遲

2.4. 無論選擇何種確認模式,未確認的訊息都有效地保留在佇列中,並將在稍後的某個時間傳遞給另一個消費者處理

2.5. 訊息持久化

  • 2.5.1. 預設情況下,訊息佇列通常儲存在記憶體中,以便為生產者和消費者提供儘可能快的服務

  • 2.5.2. 只要記憶體充足,在記憶體中管理佇列的開銷就是最小的

  • 2.5.2.1. 如果伺服器崩潰,那麼它確實有丟失訊息的風險

  • 2.5.3. 為了防止訊息丟失,佇列可以設定成可持久化的

  • 2.5.3.1. 當生產者將訊息放入佇列時,只有訊息寫入磁碟後操作才會完成

  • 2.5.3.2. 如果訊息代理發生故障,在重新啟動時它可以將佇列內容恢復到失敗前的狀態,並且不會丟失任何訊息

  • 2.5.4. 持久佇列會固有地增加傳送操作的響應時間,但資料安全性卻得到了提高

  • 2.5.5. 代理通常會在記憶體和磁碟上維護佇列內容,這樣就能在正常操作時以最小的開銷將訊息傳送給消費者

2.6. 釋出-訂閱

  • 2.6.1. 在釋出-訂閱系統中,訊息佇列被稱為主題

  • 2.6.2. 一個主題一般都是一個訊息佇列,它會將每個釋出的訊息傳遞給多個訂閱者之一

  • 2.6.3. 釋出者與訂閱者分離,訂閱者的數量可以動態變化

  • 2.6.3.1. 須對現有系統進行任何更改即可新增新的訂閱者,架構具有高度的可擴充套件性

  • 2.6.4. 釋出-訂閱模式給訊息代理帶來了額外的效能負擔

  • 2.6.4.1. 利用推送的訊息消費模型為釋出-訂閱架構提供了最有效的解決方案

  • 2.6.5. 釋出-訂閱訊息傳遞機制是構建分散式事件驅動架構的關鍵元件

  • 2.6.5.1. 在事件驅動的架構中,多個服務可以使用訊息代理主題釋出與某些狀態變更相關的事件

  • 2.6.5.2. 服務可以透過訂閱主題來註冊感興趣的各種事件型別

  • 2.6.5.3. 該主題釋出的每個事件都會傳送給所有感興趣的消費者服務

2.7. 訊息複製

  • 2.7.1. 在非同步系統中,訊息代理可能會是一個潛在的故障點

  • 2.7.2. 系統或網路故障可能導致代理不可用,從而使系統無法正常執行

  • 2.7.3. 大多數訊息代理都允許在多個代理之間以物理方式複製邏輯佇列和主題,每個代理都在自己的節點上執行

  • 2.7.4. 訊息佇列複製的最常見方法是領導者-追隨者(leader-follower)架構

  • 2.7.4.1. 一個代理被指定為領導者,生產者和消費者分別透過該領導者傳送和接收訊息

  • 2.7.4.2. 追隨者被稱為熱備用,是領導者的副本,如果領導者發生故障,則追隨者頂上

  • 2.7.5. 在故障場景下,生產者和消費者可以透過切換訪問追隨者來繼續操作,稱之為故障轉移

  • 2.7.5.1. 故障轉移在訊息代理的客戶端庫中實現,因此對生產者和消費者來說是透明的

  • 2.7.6. 實現一個可以執行佇列複製的代理是一件複雜的事情

  • 2.7.6.1. 不要考慮使用自己的複製方案或任何其他複雜的分散式演算法

  • 2.7.6.2. 你的解決方案將不如現有解決方案有效,開發成本將超出你的預期

3. RabbitMQ

3.1. 是分散式系統中使用最廣泛的訊息代理之一

3.2. RabbitMQ代理採用Erlang語言構建,主要為AMQP(Advanced Message Queuing Protocol,高階訊息佇列協議)開放標準提供支援

  • 3.2.1. AMQP誕生於金融行業,致力於定義合作協議

  • 3.2.1.1. 它是一種二進位制協議,為執行該協議的不同產品提供互操作性

  • 3.2.2. RabbitMQ支援開箱即用,支援AMQP v0-9-1,並透過外掛支援v1.0

3.3. 訊息、交換機和佇列

  • 3.3.1. 代理基於一個被稱為交換機(exchange)的概念實現了一個訊息傳遞模型,它為建立訊息傳遞拓撲提供了一種靈活的機制

  • 3.3.2. 交換是對接收生產者訊息並傳遞給代理佇列的過程的一種抽象

  • 3.3.3. 直連交換機通常用於根據匹配路由鍵將每條訊息傳遞到一個目標佇列

3.4. 分發和併發

  • 3.4.1. 通道不是執行緒安全的,這意味著每個執行緒都需要對通道進行獨佔訪問

  • 3.4.2. 執行緒的生命週期和呼叫由伺服器平臺控制,而不是由你的程式碼控制

  • 3.4.3. 輪詢效率很低,因為它涉及繁忙的等待,即使沒有訊息可用,也要求消費者不斷詢問訊息

  • 3.4.3.1. 高效能系統中不會使用這種方法

  • 3.4.4. 推送模型

3.5. 與大多數訊息代理一樣,RabbitMQ在消費速率跟生產速率相當時表現最佳

  • 3.5.1. 當佇列增長到大約有數萬條訊息時,管理佇列的執行緒將承受更多的開銷

  • 3.5.2. 預設情況下,代理使用執行節點40%的可用記憶體

3.6. 資料安全與效能權衡

  • 3.6.1. 所有訊息傳遞系統都面臨著效能與可靠性權衡的兩難境地

  • 3.6.2. 核心問題是訊息傳遞的可靠性,通常稱為資料安全

3.7. 可用性與效能權衡

  • 3.7.1. 單個代理發生故障屬於單點故障,因此如果代理崩潰或經歷短暫的網路故障,就會導致系統不可用

  • 3.7.2. 高可用性的典型解決方案是代理和佇列複製

  • 3.7.3. RabbitMQ提供了兩種支援高可用性的方法,分別是映象佇列和仲裁佇列

  • 3.7.3.1. 需要部署兩個或多個RabbitMQ代理並配置成一個叢集

  • 3.7.3.2. 每個佇列都有一個領導者版本,以及一個或多個追隨者

  • 3.7.3.3. 釋出者向領導者傳送訊息,領導者負責將每條訊息複製給追隨者

  • 3.7.3.4. 消費者也連線到領導者,當領導者收到訊息成功處理的應答時,訊息也會從追隨者中刪除

  • 3.7.3.5. 由於所有釋出者和消費者的佇列行為都由領導者執行,仲裁佇列和映象佇列雖然都提升了可用性,但不支援負載均衡

>  3.7.3.5.1. 訊息吞吐量受到領導者副本的效能限制
  • 3.7.3.6. 關鍵的區別在於如何複製訊息,以及在領導者發生故障的情況下如何選擇新的領導者
>  3.7.3.6.1. 仲裁本質上意味著超過半數

>  3.7.3.6.2. 如果有五個佇列副本,那麼至少需要三個副本(領導者和兩個追隨者)來持久儲存新發布的訊息

>  3.7.3.6.3. 仲裁佇列實現了RAFT演算法以便管理副本,並在領導者不可用時選擇新的領導者

>  3.7.3.6.4. 仲裁佇列必須是永續性的,因此主要適用於資料安全性和可用性優先於效能的用例

  >   3.7.3.6.4.1. 在故障處理方面,它比映象佇列的實現更有優勢

4. 訊息傳遞模式

4.1. 競爭消費者

  • 4.1.1. 訊息傳遞系統的一個常見需求是儘可能快地消費佇列中的訊息

4.2. 可用性

  • 4.2.1. 如果一個消費者發生故障,系統仍然可用,這個消費者的訊息份額只是簡單地分發給其他競爭消費者

4.3. 故障處理

  • 4.3.1. 如果一個消費者發生故障,它未確認的訊息將傳遞給另一個佇列消費者

4.4. 動態負載均衡

  • 4.4.1. 新的消費者可以在高負載期間啟動並在負載減少時停止,而無須更改任何佇列或消費者配置

4.5. 嚴格遵守一次處理原則

  • 4.5.1. 在非同步訊息傳遞系統中,重複處理訊息來源於兩個問題

  • 4.5.1.1. 第一個是來自發布者的重複釋出

>  4.5.1.1.1. 一些訊息代理提供了對這種重複檢測的支援,從而確保重複的訊息不會被髮布到佇列中

>  4.5.1.1.2. 利用每條訊息在客戶端生成的唯一冪等鍵值

  >   4.5.1.1.2.1. 釋出者只需要將特定的訊息屬性設定為唯一值

>  4.5.1.1.3. 代理利用快取來儲存冪等鍵值並檢測重複項,有效地消除了佇列中的重複訊息,解決了第一個問題

>  4.5.1.1.4. 在消費者端,代理將訊息傳遞給消費者後,消費者對訊息進行了處理,但之後無法傳送應答(消費者崩潰或網路丟失應答)​,就會發生重複給消費者傳遞訊息的情況
  • 4.5.1.2. 第二個是消費者多次消費
>  4.5.1.2.1. 消費者有義務防止重複處理

>  4.5.1.2.2. 為已處理的訊息維護一個快取或冪等鍵值資料庫

>  4.5.1.2.3. 大多數代理將設定一個訊息頭,指示訊息是否為重新傳遞

  >   4.5.1.2.3.1. 消費者可以用它來實現冪等性

  >   4.5.1.2.3.2. 它不能保證消費者已經處理了該訊息

  >   4.5.1.2.3.3. 它只是告訴你代理已傳遞過該訊息並且訊息仍未得到應答
  • 4.5.1.3. 兩者都需要解決,以確保每條訊息都只處理一次

4.6. 有害訊息

  • 4.6.1. 最常見的可能是生產者傳送了錯誤的訊息,消費者無法處理

  • 4.6.2. 導致消費者崩潰

  • 4.6.2.1. 在研發和測試系統中最為常見

  • 4.6.2.2. 有時這些問題也會流入生產環境,而消費者發生故障肯定會導致一些嚴重的運營難題

  • 4.6.3. 導致消費者拒絕訊息,因為它無法成功處理訊息負載

  • 4.6.4. 有害訊息將被傳遞給另一個消費者,會得到可預測的、不良結果

  • 4.6.4.1. 如果沒辦法檢測到有害訊息,則會無限期地傳遞它們

  • 4.6.4.2. 最好的結果是隻佔用處理能力,減少系統吞吐量

  • 4.6.5. 有害訊息處理的方案是限制重新傳遞訊息的次數

  • 4.6.5.1. 當達到重新傳遞的限制時,訊息會自動轉移到一個收集問題請求的佇列

>  4.6.5.1.1. 這個佇列在傳統上被稱為死信佇列
  • 4.6.6. 每個訊息傳遞平臺實現有害訊息處理的確切機制都有所不同

  • 4.6.7. 有害訊息處理的最後一部分是診斷出訊息被重定向到死信佇列的原因

  • 4.6.8. 最重要的是你需要設定某種形式的監視警報給工程師傳送訊息處理失敗的通知

相關文章