1. 程式人生 > >分散式系統基礎-訊息佇列之JMS

分散式系統基礎-訊息佇列之JMS

JMS並沒有定義訊息的網路報文格式及相關的通訊命令協議,但它以Java API的方式給出了一個可以納入到J2EE環境中的訊息中介軟體所應具備的程式設計級介面。同時,JMS歸納總結了兩種通用的訊息傳遞模型,深入理解這兩種訊息模型有助於我們準確把握訊息中介軟體的原理和典型的使用場景。

第一個訊息模型是點對點訊息通訊模型。如下圖所示,傳送方Client1(Producer/Sender) 與消費者Client 2 (Comnsumer/Receiver)通過一個特定的訊息佇列(Queue)聯絡起來,傳送方產生一個訊息(Message)並且將其傳送到指定的Queue中,Broker隨後通知消費者去處理 (Consume)此訊息,訊息處理完成後,消費者傳送回執(Acknowledge)給MQ (通常回執的傳送邏輯包括在MQ API內,不需要我們程式設計呼叫),於是MQ認為此訊息已經“可靠消費/投遞”,然後從其持久化儲存中刪除此訊息。

這裡寫圖片描述

一般情況下,消費者可以通過下面的兩種方式獲取新訊息。

  1. Push方式:MQ收到新訊息後,主動呼叫消費者的新訊息通知介面,在這個介面方法中(onMessage)消費者完成訊息的處理過程,這種方式下,消費者只能被動等待訊息通知,J2EE規範裡的Message Bean就是這種模式的體現。
  2. Pull方式:消費者輪詢呼叫MQ API去獲取新訊息,在ActiveMQ中對應的方法是consumer.receive(),客戶端的處理邏輯相對比較複雜,但擁有更多的主動權。

那麼,Pull方式與Push方式的本質差別究竟體現在哪裡?答案是Pull方式下消費者需要消耗一個獨立的執行緒去拉取並處理新訊息,並不佔用MQ寶貴的執行緒資源;而在Push方式下,訊息的處理過程會佔用MQ的有限執行緒,因此Push方式很難適應極高速、高併發的訊息傳遞場景。此外,Push模式下,當消費者離線時,MQ通常都需要持久化新訊息,這需要佔用大量的儲存空間,當消費者恢復以後,大量積壓的訊息需要MQ執行緒去處理,因此也會拖累其他消費者。總體來說,J2EE的Push模式雖然程式設計簡單,但由於存在上述嚴重問題,因此新一代的訊息中間 件Kafka拋棄了 Push模式並全面擁抱了Pull模式。

第二個訊息模型是釋出/訂閱模型。如下圖所示,在這種模式下,訊息生產者也被稱為釋出者(Publisher),訊息消費者被稱為訂閱者(Subscriber),訊息會被髮送到一個名為主題(Topic)的虛擬通道中,並且每個Topic可以被多個Subscriber訂閱,因此這種訊息模式非常類似於“廣播”或者TCP/IP中的組播。釋出/訂閱模型的訊息傳輸機制是Push模式,只需保持Subscriber線上即可。

這裡寫圖片描述

釋出/訂閱模型中的Subscriber通常有兩種型別:永續性的和臨時性的,區別在於Subscriber離線時的表現,臨時性的Subscriber離線後將會錯過新訊息,但 MQ 會為永續性的Subscriber持久儲存離線時收到的訊息副本,因此,持 久 性 的Subscriber會在恢復後重新收到之前錯過的訊息。為什麼會有這兩種Subscriber的存在呢?因為釋出/訂閱模型主要是為了實現訊息“廣播”。我們知道,廣播的主要目的是集中通知所有在場(線上 )的人而不是不在場的人,個別人沒有被通知也不是一個嚴重的問題,因此釋出/訂閱模型中的臨時性Subscriber很有價值。

在理解了 JMS的兩種訊息模型之後,我們來看看JMS提供的API及基本用法,如下所示是 JMS API中涉及的主要物件及收發訊息的流程示意圖。

這裡寫圖片描述

首先,通 過 ConnectionFactory建立一個Connection物件,用於傳輸訊息報文,這裡採用的最重要的設計模式是工廠模式,目標是為不同的JMS Provider實現者提供統一、標準的JMS API入口,同時面向終端使用者遮蔽各個不同的JMS Provider底層的不同實現,例如訊息報文的格式、編碼解碼的邏輯、是否採用了 NIO技術及訊息究竟是如何被持久化儲存的等細節問題。因為幾乎所有的TCP通訊都會涉及會話狀態保持及大量業務相關的程式設計邏輯,所以從SOLID設計原則中的 “單一職責原則”來看,最好將這部分邏輯從負責具體網路通訊的Connection物件中剝離出來,因此我們能理解為什麼有接下來的Session會話物件了。

然後通過Session物件,我們可以建立一個JMS訊息並且通過Message Producer物件將其傳送到指定的目標地址(Destination),也可以建立一個Message Consumer物件來從指定的目標地址上收取並處理訊息。

最後,在應用不再需要收發訊息後,我們可以關閉Connection,釋放資源。

JMS訊息系統中的兩個決定性因素:訊息可靠性與系統性能。這兩個因素就好像硬幣的正反面,既矛盾又對立統一。通常來說,訊息的可靠性越高則意味著訊息系統的整體吞吐量和效能越低。作為架構師,我們需要考慮系統中各類訊息的特點,結合業務場景的特點,在可靠性與效能之間做出合理的折中選擇。下面我們看看JMS中介軟體都提供了哪些可用的特性以供我們選擇、決策。

首先,訊息可以分為永續性訊息和非永續性訊息兩類。永續性訊息具有高可靠性的特點,可以保證“傳送且只傳送一次”。為了做到這一點,永續性訊息通常在訊息生命週期的兩個關鍵環節中多出了 “回執” (Acknowledge)的行為,這兩個環節即訊息傳送時(被遞到訊息系統時)和訊息消費時(被消費方完成消費時)。非永續性訊息降低了訊息可靠性的等級,只保證“最多傳送一次”,因此,如果MQ出現故障,則該訊息可能會永遠丟失,但好處是系統性能的提升。通常情況下,系統日誌、告警、事件(非業務事件)都可以被定義為非永續性訊息,以提升訊息的吞吐量。

其次,在最影響訊息投遞速度的消費者回執 Acknowledge) 環節,JMS提供了多種回執模式。

  1. AUTO_ACKNOWLEDGE模式:MQ API自動逐條傳送訊息回執,開銷最大,但可以保證訊息逐條傳送的可靠性。
  2. CLIENT_ACKNOWLEDGE模式:需要客戶端自己程式設計傳送回執,如果按批次傳送確認,則需要的頻寬開銷較小,但代價是程式設計更復雜。
  3. DUPS_OK_ACKNOWLEDGE模式:無須程式設計,MQ API會採用某種最佳方式傳送回執,開銷最小,但代價是消費者可能收到重複的訊息。
  4. NO_ACKNOWLEDGE模式:不傳送回執,因此可以提供最佳效能,代價是可能會丟失訊息。

最後,JMS訊息中介軟體通常還提供了叢集機制,當單機無法承載當前的設計目標時,還可以通過擴充套件為叢集的方式來提升系統的吞吐量與效能。

參考:架構揭祕從分散式到微服務