1. 非同步消息傳遞 1.1. 通信是分散式系統的基礎,也是架構師需要納入其系統設計的主要問題 1.2. 客戶端發送請求並等待伺服器響應 1.2.1. 這就是大多數分散式通信的設計方式,因為客戶端需要得到即時響應後才能繼續 1.2.2. 並非所有系統都有這個要求 1.3. 使用非同步通信的方式,客戶端( ...
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. 最重要的是你需要設置某種形式的監視警報給工程師發送消息處理失敗的通知