使用 Pub/Sub 的事件驅動架構

本文將說明內部部署的訊息佇列驅動架構,與在 Pub/Sub 上實作的雲端事件驅動架構之間的差異。如果嘗試將內部部署模式直接套用至雲端技術,可能會錯失讓雲端技術具備吸引力的獨特價值。

本文件適用於將設計從內部架構遷移至雲端架構的系統架構師。本文假設您對訊息系統有初步的瞭解。

下圖概略說明訊息佇列模型和 Pub/Sub 模型。

比較使用 Pub/Sub 的訊息佇列模型架構與事件驅動模型架構。

在上圖中,我們比較了訊息佇列模型和 Pub/Sub 事件串流模型。在訊息佇列模型中,發布者會將訊息推送至佇列,每位訂閱者都可以聆聽特定佇列。在使用 Pub/Sub 的事件串流模型中,發布端會將訊息推送至多個訂閱端可聽取的主題。我們將在以下各節說明這些模型之間的差異。

比較事件串流和佇列式訊息

如果您使用的是內部部署系統,那麼您應該已經熟悉企業服務總線 (ESB)訊息佇列。事件串流是一種全新的模式,與現代即時系統的具體優勢有重大差異。

本文件將討論事件驅動架構中傳輸機制和酬載資料的主要差異。

訊息傳輸

在這些模型中移動資料的系統稱為訊息中介服務,其中實作了各種架構。其中一個基本概念是,將訊息從發布者傳送至接收者的基礎機制。在內部訊息架構中,原始系統會使用訊息佇列做為傳輸工具,向下游處理系統發出明確、遠端、解耦的訊息。

下圖顯示訊息佇列模型:

發布者傳送的訊息會推送至每位訂閱者的專屬佇列。

在上圖中,訊息會透過訊息佇列從上游發布者程序流向下游訂閱者程序。

系統 A (發布端) 會將訊息傳送至訊息中介軟體的佇列,該佇列專門用於系統 B (訂閱端)。雖然佇列的訂閱者可以包含多個用戶端,但這些用戶端都是為了擴充和可用性而部署的系統 B 重複執行個體。如果其他下游程序 (例如系統 C) 需要從產生者 (系統 A) 取用相同的訊息,就需要建立新的佇列。您需要更新產生器,才能將訊息發布至新佇列。這個模型通常稱為「訊息傳遞」

這些佇列的訊息傳輸層可能會提供訊息順序保證,也可能不會。通常,訊息佇列會提供有序保證模型,並以嚴格的先進先出 (FIFO) 存取模型排序資料,類似於工作佇列。這種模式一開始很容易實作,但最終會出現資源調度和作業方面的問題。如要實作有序訊息,系統需要集中式程序來整理資料。這個程序會限制擴充功能,並降低服務可用性,因為這是單一故障點。

這些架構中的訊息中介軟體通常會實作額外邏輯,例如追蹤哪些訂閱者收到哪些訊息,以及監控訂閱者負載。訂閱者通常只會回應,不瞭解整體系統,只會在收到訊息時執行函式。這類架構稱為「智慧管道」(訊息佇列系統) 和「無腦端點」 (訂閱者)。

Pub/Sub 傳輸

與以訊息為導向的系統類似,事件串流系統也會將訊息從來源系統傳送至解耦的目的地系統。不過,事件導向系統通常會將訊息發布至共用主題,而不是將每則訊息傳送至以程序為目標的佇列,然後一或多個接收器訂閱該主題,以便監聽相關訊息。

下圖顯示上游發布端如何將各種訊息傳送至單一主題,然後將其路由至相關的下游訂閱端:

發布者的訊息會推送至單一主題,供所有訂閱者使用。

這就是「pub/sub」 一詞的由來。這也是 Google Cloud Pub/Sub 產品的基礎。在本文件中,「pubsub」是指模式,「Pub/Sub」則是指產品。

在 pubsub 模型中,訊息系統不需要瞭解任何訂閱者。它不會追蹤已收到的訊息,也不會管理使用程序的負載。相反地,訂閱者會追蹤已收到哪些訊息,並負責自行管理負載層級和調整。

其中一個重大優點是,如果您在 pubsub 模型中發現資料有新用途,就不需要更新原始系統,以便發布至新佇列或複製資料。您可以將新消費者連結至新訂閱項目,而不會影響現有系統。

事件串流系統中的呼叫幾乎都是非同步的,也就是說,這些呼叫會傳送事件,但不會等待任何回應。非同步事件可讓生產者和消費者有更多擴充選項。不過,如果您希望能保證 FIFO 訊息順序,這種非同步模式可能會造成挑戰。

訊息佇列資料

在訊息佇列系統和 Pub/Sub 系統中,系統之間傳遞的資料通常會在兩種情況下稱為「訊息」。不過,呈現資料的模型不同。在訊息佇列系統中,訊息會反映指令,該指令旨在變更下游資料的狀態。如果您查看內部部署訊息佇列系統的資料,發布者可能會明確指出消費者應採取的動作。舉例來說,商品目錄訊息可能會指出下列事項:

<m:SetInventoryLevel>
    <inventoryValue>3001</inventoryValue>
</m: SetInventoryLevel>

在這個範例中,生產者會告知消費者,需要將商品目錄層級設為 3001。這種做法可能會很困難,因為產生器需要瞭解每個使用者的商業邏輯,並為不同的用途建立個別的訊息結構。這套訊息佇列系統是大多數企業實作大型單體架構的常見做法。不過,如果您想加快速度、擴大規模,並進行更多創新,這些集中式系統可能會成為瓶頸,因為變更的風險和速度都很高。

這種模式也會帶來營運上的挑戰。當發生錯誤資料、重複記錄或其他問題,需要進行修正時,這種訊息傳遞模型就會帶來重大挑戰。舉例來說,如果您需要回復先前範例中使用的訊息,由於沒有先前的狀態參照,因此不知道要將修正值設為何值。您無法得知在傳送該訊息之前,廣告空間價值是 3000 還是 4000。

Pub/Sub 資料

事件是另一種傳送訊息資料的方式。特別的是,事件驅動系統著重於發生的事件,而非應發生的結果。這類資料不會傳送指出消費者應採取哪些動作的資料,而是著重於實際產生的事件詳細資料。您可以在各種平台上實作事件驅動系統,但這類系統通常會出現在以 pubsub 為基礎的系統中。

舉例來說,商品目錄事件可能會像這樣:

{ "inventory":-1 }

先前的事件資料指出,發生了某個事件,導致廣告空間減少 1。這些訊息著重於過去發生的事件,而非日後要變更的狀態。發布者可以以非同步方式傳送訊息,因此事件導向系統比訊息佇列模型更容易擴充。在發布/訂閱模型中,您可以將業務邏輯分離,讓生產端只需瞭解對其執行的動作,而不需要瞭解下游程序。資料訂閱者可以選擇如何處理收到的資料。由於這些訊息並非必要指令,因此訊息的順序就沒那麼重要。

使用這種模式,您就能更輕鬆地復原變更。在本範例中,您可以將商品目錄值設為負值,以便將其移至相反方向,因此不需要額外資訊。您也不必再擔心訊息延遲或順序錯亂的問題。

模式比較

在這個情境中,你的商品目錄中含有四件相同產品。一位顧客退回一項產品,下一位顧客購買了三項相同產品。在這個情境中,假設退回產品的訊息延遲傳送。

下表比較了在正確順序中接收商品數量的訊息佇列模型,與接收商品數量順序不正確的相同模型的商品層級:

訊息佇列 (正確順序) 訊息佇列 (順序錯亂)
初始商品目錄:4 初始商品目錄:4
訊息 1:setInventory(5) 訊息 2:setInventory(2)
訊息 2:setInventory(2) 訊息 1:setInventory(5)
廣告空間層級: 2 廣告空間層級:5

在訊息佇列模型中,訊息的接收順序非常重要,因為訊息包含預先計算的值。在這個範例中,如果訊息依正確順序傳送,則廣告空間等級為 2。不過,如果訊息傳送順序不正確,則商品目錄等級為 5,這不正確。

下表比較了以 pubsub 為基礎的系統,在正確順序接收廣告空間計數的廣告空間層級,以及同一個系統在錯誤順序接收廣告空間計數的廣告空間層級:

Pubsub (正確順序) Pubsub (順序錯誤)
初始商品目錄:4 初始商品目錄:4
訊息 2:"inventory":-3 訊息 1:"inventory":+1
訊息 1:"inventory":+1 訊息 2:"inventory":-3
廣告空間層級:2 廣告空間層級:2

在以 Pub/Sub 為基礎的系統中,訊息的順序並不重要,因為系統會根據產生事件的服務來通知訊息。無論訊息傳送的順序為何,廣告空間層級都會正確。

下圖顯示在訊息佇列模型中,佇列如何執行指令,告知訂閱端應如何變更狀態;而在 pubsub 模型中,訂閱端會回應事件資料,說明發布端發生了什麼事:

請查看比較回應指令和回應事件的範例。

導入事件導向架構

實作事件驅動架構時,需要考量各種概念。以下各節將介紹其中幾個主題。

關於通知訊息的注意事項

在系統討論中,有一個概念是訊息傳送保證的可靠性。不同供應商和系統提供的可靠度可能不同,因此請務必瞭解這些差異。

第一類保證會提出一個簡單的問題:如果傳送訊息,是否保證會送達?這就是所謂的「至少一次」傳送。系統保證至少會傳送一次訊息,但可能會傳送多次。

另一種保證類型是「最多傳送一次」。使用「最多一次」傳送模式時,系統只會傳送一次訊息,但無法保證訊息會實際傳送。

最終的放送保證變化版本是「確切一次放送」。在這個模型中,系統會傳送一封且只有一封郵件,且保證會送達。

訂單和重複項目

在內部部署架構中,訊息通常會遵循先進先出 (FIFO) 模式。為了實現這個模型,集中處理系統會管理訊息的排序,確保正確的排序。有序訊息會造成挑戰,因為對於任何失敗的訊息,都必須依序重新傳送所有訊息。任何集中式系統都可能會影響可用性和可擴充性。如要擴充管理訂單的中央系統,通常只能在現有機器上新增更多資源。當單一系統管理訂單時,任何可靠性問題都會影響整個系統,而非僅限於該機器。

可高度擴充且可用性高的訊息服務通常會使用多個處理系統,確保至少傳送一次訊息。在許多系統中,我們無法保證訊息的順序。

以事件為核心的架構不依賴訊息順序,因此可以容許重複訊息。如果需要排序,子系統可以實作匯總和視窗技術;不過,這種做法會犧牲該元件的可擴充性和可用性。

篩選和分支技巧

由於事件串流可能包含每個訂閱者可能或不需的資料,因此通常需要限制特定訂閱者收到的資料。管理這項需求有兩種模式:事件篩選器和事件分散。

下圖顯示事件驅動系統,其中事件篩選器會為訂閱者篩選訊息:

事件驅動模型,搭配用於篩選訂閱者訊息的事件篩選器。

在上圖中,事件篩選器會使用篩選機制,限制傳送至訂閱者的事件。在這個模型中,單一主題包含訊息的所有變化版本。訊息系統中的篩選邏輯會評估訊息,而非由訂閱者讀取每則訊息並驗證是否適用。如果訊息不符合條件,系統就會將其從其他訂閱者中移除。

下圖顯示事件篩選器模式的變化版本,稱為事件分支,可使用多個主題:

事件驅動模型,可透過事件分散功能重新發布主題訊息。

在上圖中,主要主題包含訊息的所有變化版本,但事件分支機制會重新發布與該子集訂閱者相關的主題訊息。

未處理的訊息佇列

即使是最佳系統,也可能發生故障。未處理的訊息佇列是處理這類失敗情況的一種方法。在大多數事件驅動架構中,訊息系統會持續向訂閱者提供訊息,直到訂閱者確認為止。

如果訊息發生問題 (例如訊息內文含有無效字元),訂閱者可能無法確認訊息。系統可能無法處理情境,甚至會終止程序。

系統通常會重試未確認或發生錯誤的訊息。如果無效訊息在預定時間內未收到確認,就會逾時,並從主題中移除。從作業角度來看,最好是查看訊息,而不是讓訊息消失。這時,未處理訊息佇列就派上用場。系統不會從主題中移除訊息,而是將訊息移至其他主題,以便重新處理或查看訊息,瞭解訊息為何發生錯誤。

串流記錄和重播

事件串流是持續流動的資料。存取這類歷來資料很有用。您可能想瞭解系統如何達到特定狀態。您可能會遇到需要稽核資料的安全性相關問題。在事件驅動系統的長期運作中,記錄事件的歷史記錄至關重要。

歷史事件資料的常見用途之一,就是與重播系統搭配使用。重播功能僅供測試之用。您可以在其他環境 (例如階段和測試) 中重播實際工作環境的事件資料,藉此驗證新功能是否符合實際資料集。您也可以重播歷來資料,從失敗狀態復原。如果系統發生故障或資料遺失,團隊可以從已知的良好時間點重播事件記錄,服務就能重新建構遺失的狀態。

當訂閱者需要在不同時間存取一系列事件時,在以記錄為基礎的佇列或記錄串流中擷取這些事件也很實用。在具備離線功能的系統中,您可以查看記錄串流。您可以使用串流記錄,從「上次讀取」指標開始讀取串流,藉此處理最新的項目。

資料檢視:即時和近乎即時

所有資料都會透過系統傳送,因此您必須能夠使用資料。您可以透過多種方式存取及使用這些事件串流,但常見的用途是瞭解特定時刻的資料整體狀態。這些通常是計算導向的問題,例如「有多少」或「目前等級」,可供其他系統或人類使用。以下是可回答這些問題的多種實作方式:

  • 即時系統可以持續運作,並追蹤目前狀態;不過,由於系統僅有記憶體內計算,因此任何停機時間都會將計算設為零。
  • 系統可以為每項要求計算歷史資料表中的值,但這可能會造成問題,因為在資料量增加的情況下,嘗試為每項要求計算值最終可能會變得不可行。
  • 系統可在特定間隔時間內建立計算作業的快照,但僅使用快照無法反映即時資料。

實用的實作模式是Lambda 架構,可同時提供近乎即時和即時功能。舉例來說,電子商務網站上的產品頁面可以使用近乎即時的庫存資料檢視畫面。當消費者下單時,系統會使用即時服務,確保庫存資料的狀態能即時更新。為實作此模式,服務會回應快照資料表的近乎即時要求,該資料表包含特定間隔的計算值。即時要求會同時使用快照資料表和記錄資料表中的值,自上次快照起,以取得確切的目前狀態。這些事件串流的具體檢視畫面可提供可供採取行動的資料,以推動實際的業務流程。

後續步驟