天天看點

事件驅動資料管理 微服務和分布式資料管理問題

單體應用程式通常具有一個單一的關系型資料庫。使用關系型資料庫的一個主要優點是您的應用程式可以使用 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% 可靠的審計日志,可以實作在任何時間點對實體進行時間查詢以确定狀态。事件溯源的另一個主要好處是您的業務邏輯包括松耦合的交換事件業務實體,這使得從單體應用程式遷移到微服務架構将變得更加容易。

總結

繼續閱讀