事件驱动数据管理 微服务和分布式数据管理问题
單體應用程序通常具有一個單一的關系型數據庫。使用關系型數據庫的一個主要優點是您的應用程序可以使用 ACID 事務,這些事務提供了以下重要保障:
- 原子性( Atomicity) 所作出的改變是原子操作,不可分割
- 一致性( Consistency) 數據庫的狀態始終保持一致
- 隔離性( Isolation) 即使事務并發執行,但他們看起來更像是串行執行
- 永久性( Durable) 一旦事務提交,它將不可撤銷
因此,您的應用程序可以很容易地開始事務、更改(插入、更新和刪除)多個行,并提交事務。
使用關系數據庫的另一大好處是它提供了 SQL,這是一種豐富、聲明式和標準化的查詢語言。您可以輕松地編寫一個查詢來組合來自多個表的數據,之后,RDBMS 查詢計劃程序將確定執行查詢的最佳方式。您不必擔心如何訪問數據庫等底層細節。因為您所有的應用程序數據都存放在同個數據庫中,因此很容易查詢。
很不幸的是,當我們轉向微服務架構時,數據訪問將變得非常復雜。這是因為每個微服務所擁有的數據對當前微服務來說是私有的,只能通過其提供的 API 進行訪問。封裝數據可確保微服務松耦合,獨立演進。如果多個服務訪問相同的數據,模式( schema)更新需要對所有服務進行耗時、協調的更新。
更糟糕的是,不同的微服務經常使用不同類型的數據庫。現代應用程序存儲和處理著各種數據,而關系型數據庫并不總是最佳選擇。在某些場景,特定的 NoSQL 數據庫可能具有更方便的數據模型,提供了更好的性能和可擴展性。例如,存儲和查詢文本的服務使用文本搜索引擎(如 Elasticsearch)是合理的。類似地,存儲社交圖數據的服務應該可以使用圖數據庫,例如 Neo4j。因此,基于微服務的應用程序通常混合使用 SQL 和 NoSQL 數據庫,即所謂的混合持久化(polyglot persistence)方式。
一個分區的數據存儲混合持久化架構具有許多優點,包括了松耦合的服務以及更好的性能與可擴展性。然而,它也引入了一些分布式數據管理方面的挑戰。
第一個挑戰是如何實現維護多個服務之間的業務事務一致性。要了解此問題,讓我們先來看一個在線 B2B 商店的示例。 Customer Service (顧客服務)維護客戶相關的信息,包括信用額度。 Order Service (訂單)負責管理訂單,并且必須驗證新訂單,不得超過客戶的信用額度。在此應用程序的單體版本中, OrderService 可以簡單地使用 ACID 交易來檢查可用信用額度并創建訂單。
相比之下,在微服務架構中, ORDER (訂單)和 CUSTOMER (顧客)表對其各自的服務都是私有的,如圖 5-1 所示:
Order Service 無法直接訪問 CUSTOMER 表。它只能使用客戶服務提供的 API。訂單服務可能使用了分布式事務,也稱為兩階段提交(2PC)。然而,2PC 在現代應用中通常是不可行的。CAP 定理要求您在可用性與 ACID 式一致性之間做出選擇,可用性通常是更好的選擇。此外,許多現代技術,如大多數 NoSQL 數據庫,都不支持 2PC。維護服務和數據庫之間的數據一致性至關重要,因此我們需要另一套解決方案。
第二個挑戰是如何實現從多個服務中檢索數據。例如,我們假設應用程序需要顯示一個顧客和他最近的訂單。如果 Order Service 提供了用于檢索客戶訂單的 API,那么您可以使用應用程序端連接以檢索數據。應用程序從 Customer Service 中檢索客戶,并從 Order Service 中檢索客戶的訂單。但是,假設 Order Service 僅支持通過主鍵查找訂單(也許它使用了僅支持基于主鍵檢索的 NoSQL 數據庫)。在這種情況下,沒有有效的方法來檢索所需的數據。
事件驅動架構
許多應用使用了事件驅動架構作為解決方案。在此架構中,微服務在發生某些重要事件時發布一個事件,例如更新業務實體時。其他微服務訂閱了這些事件,當微服務接收到一個事件時,它可以更新自己的業務實體,這可能導致更多的事件被發布。
您可以使用事件實現跨多服務的業務事務。一個事務由一系列的步驟組成。每個步驟包括了微服務更新業務實體和發布事件所觸發的下一步驟。下圖依次展示了如何在創建訂單時使用事件驅動方法來檢查可用信用額度。
微服務通過 Message Broker(消息代理)進行交換事件:
- Order Service(訂單服務)創建一個狀態為 NEW 的訂單,并發布一個 OrderCreated(訂單創建)事件。
- Customer Service (客戶服務)消費了 Order Created 事件,為訂單預留信用額度,并發布 Credit Reserved 事件。
- Order Service 消費了 Credit Reserved(信用預留)事件并將訂單的狀態更改為 OPEN。
更復雜的場景可能會涉及額外的步驟,例如在檢查客戶信用的同時保留庫存。
假設(a)每個服務原子地更新數據庫并發布事件,稍后再更新,(b)Message Broker 保證事件至少被傳送一次,您可以實現跨多服務的業務事務。需要注意的是,這些并不是 ACID 事務。它們只提供了更弱的保證,如 最終一致性。該事務模型稱為 BASE 模型。
您還可以使用事件來維護多個微服務預先加入所擁有的數據的物化視圖(materialized view)。維護視圖的服務訂閱了相關事件并更新視圖。圖 5-5 展示了 Customer Order View UpdaterService(客戶訂單視圖更新服務)根據 Customer Service 和 Order Service 發布的事件更新 Customer Order View (客戶訂單服務)。
當 Customer Order View Updater Service 接收到 Customer 或 Order 事件時,它會更新 Customer Order View 數據存儲。您可以使用如 MongoDB 之類的文檔數據庫實現 Customer Order View,并為每個 Customer 存儲一個文檔。 Customer OrderView Query Service (客戶訂單視圖查詢服務)通過查詢 Customer Order View 數據存儲來處理獲取一位客戶和最近的訂單的請求。
事件驅動的架構有幾個優點與缺點。它能夠實現跨越多服務并提供最終一致性事務。另一個好處是它還使得應用程序能夠維護物化視圖。
一個缺點是其編程模型比使用 ACID 事務更加復雜。通常,您必須實現補償事務以從應用程序級別的故障中恢復。例如,如果信用檢查失敗,您必須取消訂單。此外,應用程序必須處理不一致的數據。因為未提交的事務所做的更改是可見的。如果從未更新的物化視圖中讀取,應用程序依然可以看到不一致性。另一個缺點是訂閱者必須要檢測和忽略重復的事件。
實現原子性
在事件驅動架構中,同樣存在著原子更新數據庫和發布事件相關問題。例如, OrderService 必須在 ORDER 表中插入一行數據,并發布 Order Created 事件。這兩個操作必須原子完成。如果在更新數據庫后但在發布事件之前發生服務崩潰,系統將出現不一致性。確保原子性的標準方法是使用涉及到數據庫和 Message Broker 的分布式事務。然而,由于上述原因,如 CAP 定理,這并不是我們想做的。
使用本地事務發布事件
實現原子性的一種方式是應用程序使用 僅涉及本地事務的多步驟過程 來發布事件。訣竅在于存儲業務實體狀態的數據庫中有一個用作消息隊列的 EVENT 表。應用程序開啟一個(本地)數據庫事務,更新業務實體狀態,將事件插入到 EVENT 表中,之后提交事務。一個單獨的應用程序線程或進程查詢 EVENT 表,將事件發布到 Message Broker,然后使用本地事務將事件標記為已發布。設計如圖 5-6 所示。
Order Service 將一行記錄插入到 ORDER 表中,并將一個 Order Created 事件插入到 EVENT 表中。
Event Publisher(事件發布者)線程或進程從 EVENT 表中查詢未發布的事件,之后發布這些事件,最后更新 EVENT 表以將事件標記為已發布。
這種方法有好有壞。好處是它保證了被發布的事件每次更新都不依賴于 2PC。此外,應用程序發布業務級事件,這些事件可以消除推斷的需要。這種方法的缺點是它很容易出錯,因為開發人員必須要記得發布事件。這種方法的局限性在于,由于其有限的事務和查詢功能,在使用某些 NoSQL 數據庫時,實現起來將是一大挑戰。
該方法通過讓應用程序使用本地事務更新狀態和發布事件來消除對 2PC 的依賴。現在我們來看一下通過應用程序簡單地更新狀態來實現原子性的方法。
挖掘數據庫事務日志
不依靠 2PC 來實現原子性的另一種方式是使用線程或進程發布事件,該線程或進程對數據庫的事務或者提交日志進行挖掘。當應用程序更新數據庫時,更改信息被記錄到數據庫的事務日志中。 Transaction Log Miner 線程或進程讀取事務日志并向 MessageBroker 發布事件。設計如圖 5-7 所示。
使用這種方法的一個示例是 LinkedIn Databus 開源項目。Databus 挖掘 Oracle 事務日志并發布與更改相對應的事件。 LinkedIn 使用 Databus 保持與記錄系統一致的各種派生數據存儲。
另一個例子是 AWS DynamoDB 中的流機制,它是一個托管的 NoSQL 數據庫。 DynamoDB 流包含了在過去 24 小時內對 DynamoDB 表中的項進行的更改(創建、更新和刪除操作),其按時間順序排列。應用程序可以從流中讀取這些更改,比如,將其作為事件發布。
事務日志挖掘有各種好處與壞處。一個好處是它能保證被發布的事件每次更新都不依賴于 2PC。事務日志挖掘還可以通過將事件發布與應用程序的業務邏輯分離來簡化應用程序。一個主要的缺點是事務日志的格式對于每個數據庫來說都是專有的,甚至在數據庫版本之間格式就發生了改變。而且,記錄于事務日志中的低級別更新可能難以對高級業務事件進行逆向工程。
事務日志挖掘消除了應用程序在做一件事時對 2PC 的依賴:更新數據庫。現在我們來看看另一種可以消除更新并僅依賴于事件的不同方式。
使用事件溯源
事件溯源通過使用完全不同的、不間斷的方式來持久化業務實體,實現無 2PC 原子性。應用程序不存儲實體的當前狀態,而是存儲一系列狀態改變事件。該應用程序通過回放事件來重建實體的當前狀態。無論業務實體的狀態何時發生變化,其都會將新事件追加到事件列表中。由于保存事件是一個單一操作,因此具有原子性。
要了解事件溯源的工作原理,以 Order(訂單)實體為例。在傳統方式中,每個訂單都與 ORDER 表中的某行記錄相映射,也可以映射到例如 ORDER_LINE_ITEM 表中的記錄。
但當使用事件溯源時,Order Service 將以狀態更改事件的形式存儲 Order:Created(創建)、Approved(批準)、Shipped(發貨)、Cancelled(取消)。每個事件包含足夠的數據來重建 Order 的狀態。
事件被持久化在事件存儲中,事件存儲是一個事件數據庫。該存儲有一個用于添加和檢索實體事件的 API。
但當使用事件溯源時, Order Service 將以狀態更改事件的形式存儲 Order: Created(創建)、 Approved(批準)、 Shipped(發貨)、 Cancelled(取消)。每個事件包含足夠的數據來重建 Order 的狀態。事件存儲還與我們之前描述的架構中的 Message Broker 類似。它提供了一個 API,使得服務能夠訂閱事件。事件存儲向所有感興趣的訂閱者派發所有事件。可以說事件存儲是事件驅動微服務架構的支柱。
事件溯源有幾個好處。它解決了實現事件驅動架構的關鍵問題之一,可以在狀態發生變化時可靠地發布事件。因此,它解決了微服務架構中的數據一致性問題。此外,由于它持久化的是事件,而不是領域對象,所以它主要避免了對象關系阻抗失配問題。事件溯源還提供了對業務實體所做更改的 100% 可靠的審計日志,可以實現在任何時間點對實體進行時間查詢以確定狀態。事件溯源的另一個主要好處是您的業務邏輯包括松耦合的交換事件業務實體,這使得從單體應用程序遷移到微服務架構將變得更加容易。
事件溯源同樣有缺點。這是一種不同而陌生的編程風格,因此存在學習曲線。事件存儲僅支持通過主鍵查找業務實體。您必須使用命令查詢責任分離(CQRS)來實現查詢。因此,應用程序必須處理最終一致的數據。
總結
在微服務架構中,每個微服務都有自己私有的數據存儲。不同的微服務可能會使用不同的 SQL 或者 NoSQL 數據庫。雖然這種數據庫架構具有明顯的優勢,但它創造了一些分布式數據管理挑戰。第一個挑戰是如何實現維護多個服務間的業務事務一致性。第二個挑戰是如何實現從多個服務中檢索數據。
大部分應用使用的解決方案是事件驅動架構。實現事件驅動架構的一個挑戰是如何以原子的方式更新狀態以及如何發布事件。有幾種方法可以實現這點,包括了將數據庫作為消息隊列、事務日志挖掘和事件溯源。
總結
以上是生活随笔為你收集整理的事件驱动数据管理 微服务和分布式数据管理问题的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        