在先前文章中,我們談到 Database per Service 是微服務架構中比較合理的資料庫設計方式。它能有效確保「低耦合、高自治」,讓每個服務可以獨立演進。然而,一旦我們放棄了「共享資料庫(Shared Database)」這條捷徑,隨之而來的挑戰就是 跨服務的交易一致性與查詢難度。
今天,我們要深入探討四個關鍵的資料模式:
• Saga:跨服務交易一致性的解決方案
• Aggregate:一致性邊界的最小單位
• Event Sourcing:用事件取代狀態,記錄完整業務歷程
• Domain Event:用事件驅動服務之間的協作
這四個模式構成了微服務架構中「資料一致性」的核心工具箱。
在過去,我們只要一個 資料庫交易(Transaction) 就能保證 ACID(原子性、一致性、隔離性、持久性)。但在微服務架構下,每個服務都有獨立的資料庫,傳統的分散式交易(Two-Phase Commit, 2PC)不但效能差,還和 NoSQL 資料庫的特性不相容。
Saga 將一個跨服務的「長交易」拆解成一連串的「本地交易」。每個本地交易都只會修改單一服務的資料,並且在完成後 觸發下一個服務的交易。如果中間有步驟失敗,Saga 就會呼叫「補償交易(Compensation Transaction)」來回滾。
(1) Orchestration
由一個「協調者」負責控制交易流程,決定下一步由哪個服務執行,是一種「集中管理」的處理模式。
【範例】
訂單服務啟動 Saga,依序呼叫「支付服務 → 庫存服務 → 出貨服務」。若庫存不足,協調者會通知支付服務 進行退款。
(2) Choreography
沒有中央協調者,服務跟服務之間透過事件互相觸發,在這個模式下高度解耦,擴展性佳,但難以追蹤管控。
【範例】
訂單服務發佈「OrderCreated」事件 → 支付服務監聽後處理扣款 → 扣款成功再發佈「PaymentCompleted」事件 → 庫存服務接手。
Saga 模式可以解決「微服務的自治性」,不依賴共用資料庫的交易管理特性,也讓我們得以選用 NoSQL 的技術實現分散式運算的機制。但,它與傳統模式不同的是開發複雜度較高,可能還需要實作「補償交易」等相關機制。開發複雜度高也就暗示設計錯誤可能遭致「資料不一致」的狀況。
在 Saga 中提到,我們可能要實作「補償交易」等相關機制,回想我們目前的營運工作,你有多少次直接打開 DB 工具修改裡面的資料?在微服務的架構下,你繞過應用程式修改一個資料可能就會造成系統間資料不一致的狀況 (事件可能都沒被正常的丟出來)。
Aggregate 是領域驅動設計(DDD)戰術設計的核心概念,也是微服務中劃分服務邊界的重要參考。
Aggregate 指的是一群高度相關的 Entity(實體) 與 Value Object(值物件) 的集合,並由一個 Aggregate Root(聚合根) 作為唯一入口。任何修改操作,都必須透過聚合根來進行。
從圖中,我們也可以看出上述元件之間互相的關係。
(1) 一致性邊界:Aggregate 內的資料必須在交易完成後保持一致。
(2) Aggregate Root:外部只能透過 Root 存取 Aggregate。
(3) 邊界大小:太大會導致更新衝突,太小會產生過多跨 Aggregate 交易。
【範例】
(1) Entity : 訂單、商品、庫存以上是訂單管理系統的三個概念
(2) 假設上述三個概念個自形成三個 aggregate 的話,意即我們有可能建立出三個微服務
(3) Value Object: 商品架構、送貨地址等相關資訊 (沒有 Identity 的物件)
(4) 確保一致性的業務規則:一張訂單中的所有項目必須在提交時同時驗證庫存
4.1. 下單的時候,訂單狀態為「WAIT_STOCK」等待庫存確認
4.2. 待「庫存」服務確認後,訂單狀態才會被修改為「CONFIRMED」
訂單不直接查/改庫存;庫存是否足夠由庫存聚合保證,訂單只依據「已預留成功」這個外部事實決定是否確認。
Aggregate 提供了判斷「服務邊界」的一個標準:
在傳統的 State Sourcing 下,我們只保存資料的「最新狀態」。但有時候,我們需要知道「資料是如何演變而來的」。
Event Sourcing 的核心思想是:
目前我見過的應用程式,99.9% 都是 State Sourcing 的實作模式 (那麼那 0.1% 是怎麼來的?從教學上看來的!)
特性 | State Sourcing | Event Sourcing |
---|---|---|
儲存內容 | 最新狀態 | 所有事件 |
儲存量 | 小 | 大 |
可追溯性 | 低 | 高 |
查詢便利性 | 高 | 需重播或建立投影 |
一致性支援 | ACID | 最終一致性 |
適用場景 | 傳統 CRUD 系統 | 金融交易、稽核、IoT 事件流 |
【範例】
Event Sourcing 模式天生就具備「稽核」的能力,所有的「事件」都被保存下來,具備「重播事件」的能力,通常會跟 CQRS 與 Saga 模式搭配使用。
但,不方便的地方是「事件儲存量大」依據交易的不等,你的儲存媒體可能需要數十倍、數百倍以上的擴張。而且,完全依賴「重播」的機制來算出最終結果可能效能反應上也不佳,所以在處理上可能會建立一個當前狀態的「副本」,也就是 State Sourcing 跟 Event Sourcing 都存在的狀況。
再者,近期也有許多文章探討「事件的演進模式」,也就是隨著需求的改變事件本身可能也會改變,而 Event Sourcing 的模式又將所有的事件儲存下來。此時,你會考慮將前面所有的事件都改寫成新的,還是需要相容多版本的事件?這部分就必須在實施的時候進一步的研究業務情境來做決策。
Domain Event 是一種表達「某件事已經發生」的訊號,通常以過去式命名,例如:OrderPlaced、PaymentCompleted。(Saga Choreography 的模式就是此原理的實作)。
【範例】
假設今天我們在處理一個貸款的業務,那麼當一個使用者申請貸款的時候,會產生一個「貸款進件」的 Domain Event。接著,就會有審查系統訂閱此事件開始確認申請人的信用狀態,在審核完畢後也會發送一個「審核完畢」的事件讓後續其他服務接手,例如:貸款撥付跟貸款通知等。
一個事件可以由多個「訂閱者」進行後續的處理作業,是一個高度解耦的應用架構。
Domain Event 是微服務實現 事件驅動架構(EDA) 的基礎。它既是 DDD 的戰術設計工具,也是微服務協作的通道。
模式|問題|場景|缺點
------ | ------ | ------ | ------ |
Saga|跨服務交易一致性|訂單、支付、庫存|複雜度高,需要補償交易
Aggregate|定義一致性邊界|訂單與訂單項目|邊界劃分錯誤會導致耦合
Event Sourcing|保存完整歷史|金融、稽核、IoT|儲存膨脹、查詢困難
Domain Event|服務間鬆散耦合|通知、報表|最終一致性、Debug 複雜
這些模式不是互斥的,而是可以 搭配使用:
微服務架構下,資料一致性不再是單純的 ACID 問題,而是需要透過不同模式協同解決的挑戰。
這四個模式共同構築了微服務的「資料治理基石」,幫助我們在分散式系統的複雜環境中,依然能維持業務的穩定性與彈性