[譯] 微服務從設計到部署(五)事件驅動資料管理

StackGC發表於2017-09-18

https://github.com/oopsguy/microservices-from-design-to-deployment-chinese 譯者oopsguy.com

本書主要介紹如何使用微服務構建應用程式,這是本書的第五章。第一章介紹了微服務架構模式,討論了使用微服務的優點與缺點。第二第三章描述了微服務架構內通訊方式的對比。第四章探討了與服務發現相關的內容。在本章中,我們稍微做了點調整,研究微服務架構中出現的分散式資料管理問題。

5.1、微服務和分散式資料管理問題

單體應用程式通常具有一個單一的關係型資料庫。使用關係型資料庫的一個主要優點是您的應用程式可以使用 ACID 事務,這些事務提供了以下重要保障:

  • 原子性(Atomicity) — 所作出的更改是原子操作,不可分割
  • 一致性(Consistency) — 資料庫的狀態始終保持一致
  • 隔離性(Isolation) — 即使事務併發執行,但他們看起來更像是序列執行
  • 永久性(Durable) — 一旦事務提交,它將不可撤銷

因此,您的應用程式可以很容易地開始事務、更改(插入、更新和刪除)多行記錄,並提交事務。

使用關係型資料庫的另一大好處是它提供了 SQL,這是一種豐富、宣告式和標準化的查詢語言。您可以輕鬆地編寫一個查詢,組合來自多個表的資料,之後,RDBMS 查詢計劃程式將確定執行查詢的最佳方式。您不必擔心如何訪問資料庫等底層細節。因為您所有的應用程式資料都存放在同個資料庫中,因此很容易查詢。

很不幸的是,當我們轉向微服務架構時,資料訪問將變得非常複雜。因為每個微服務所擁有的資料對當前微服務來說是私有的,只能通過其提供的 API 進行訪問。封裝資料可確保微服務鬆耦合、獨立演進。如果多個服務訪問相同的資料,模式(schema)更新需要對所有服務進行耗時、協調的更新。

更糟糕的是,不同的微服務經常使用不同型別的資料庫。現代應用程式儲存和處理著各種資料,而關係型資料庫並不總是最佳選擇。在某些場景,特定的 NoSQL 資料庫可能具有更方便的資料模型,提供了更好的效能和可擴充套件性。例如,儲存和查詢文字的服務使用文字搜尋引擎(如 Elasticsearch)是合理的。類似地,儲存社交圖資料的服務應該可以使用圖資料庫,例如 Neo4j。因此,基於微服務的應用程式通常混合使用 SQL 和 NoSQL 資料庫,即所謂的混合持久化(polyglot persistence)方式。

分割槽的資料儲存混合持久化架構具有許多優點,包括了鬆耦合的服務以及更好的效能與可擴充套件性。然而,它也引入了一些分散式資料管理方面的挑戰。

第一個挑戰是如何實現業務的事務在多個服務之間保持一致性。要了解此問題,讓我們先來看一個線上 B2B 商店的示例。Customer Service (顧客服務)維護客戶相關的資訊,包括信用額度。Order Service (訂單)負責管理訂單,並且必須驗證新訂單,不得超過客戶的信用額度。在此應用程式的單體版本中,Order Service 可以簡單地使用 ACID 事務來檢查可用信用額度並建立訂單。

相比之下,在微服務架構中,ORDER (訂單)和 CUSTOMER (顧客)表對其各自的服務都是私有的,如圖 5-1 所示:

圖 5-1、每個微服務都有各自的資料

Order Service 無法直接訪問 CUSTOMER 表。它只能使用客戶服務提供的 API。訂單服務可能使用了分散式事務,也稱為兩階段提交(2PC)。然而,2PC 在現代應用中通常是不可行的。CAP 定理要求您在可用性與 ACID 式一致性之間做出選擇,可用性通常是更好的選擇。此外,許多現代技術,如大多數 NoSQL 資料庫,都不支援 2PC。維護服務和資料庫之間的資料一致性至關重要,因此我們需要另一套解決方案。

第二個挑戰是如何實現從多個服務中檢索資料。例如,我們假設應用程式需要顯示一個顧客和他最近的訂單。如果 Order Service 提供了用於檢索客戶訂單的 API,那麼您可以使用應用程式端連線以檢索資料。應用程式從 Customer Service 中檢索客戶,並從 Order Service 中檢索客戶的訂單。但是,假設 Order Service 僅支援通過主鍵查詢訂單(也許它使用了僅支援基於主鍵檢索的 NoSQL 資料庫)。在這種情況下,沒有有效的方法來檢索所需的資料。

5.2、事件驅動架構

許多應用使用了事件驅動架構作為解決方案。在此架構中,微服務在發生某些重要事件時釋出一個事件,例如更新業務實體時。其他微服務訂閱了這些事件,當微服務接收到一個事件時,它可以更新自己的業務實體,這可能導致更多的事件被髮布。

您可以使用事件實現跨多服務的業務的事務。一個事務由一系列的步驟組成。每個步驟包括了微服務更新業務實體和釋出一個事件來觸發下一步驟。下圖依次展示瞭如何在建立訂單時使用事件驅動方法來檢查可用信用額度。

微服務通過 Message Broker (訊息代理)進行交換事件:

  • Order Service (訂單服務)建立一個狀態為 NEW 的訂單,併釋出一個 Order Created (訂單建立)事件。

圖 5-2、Order Service 釋出一個事件

  • Customer Service (客戶服務)消費了 Order Created 事件,為訂單預留信用額度,併發布 Credit Reserved 事件。

圖 5.3、Customer Service 響應

  • Order Service 消費了 Credit Reserved (信用預留)事件並將訂單的狀態更改為 OPEN。

圖 5-4、Order Service 作用於響應

更復雜的場景可能會涉及額外的步驟,例如在檢查客戶信用的同時保留庫存。

假設(a)每個服務原子地更新資料庫併釋出一個事件 (稍後再詳細說明),(b)Message Broker 保證事件至少被送達一次,然後您就實現了跨多服務的業務事務。需要注意的是,這些並不是 ACID 事務。它們只提供了更弱的保證,如最終一致性。該事務模型稱為 BASE 模型

屬於多個微服務的資料構成的物化檢視,你也可以用事件來維護。維護檢視的服務訂閱了相關事件並更新檢視。圖 5-5 展示了 Customer Order View Updater Service (客戶訂單檢視更新服務)根據 Customer Service 和 Order Service 釋出的事件更新 Customer Order View (客戶訂單檢視)。

圖 5-5 Customer Order View 被兩個服務訪問

當 Customer Order View Updater Service 接收到 Customer 或 Order 事件時,它會更新 Customer Order View 的資料儲存。您可以使用如 MongoDB 之類的文件資料庫實現 Customer Order View,併為每個 Customer 儲存一個文件。Customer Order View Query Service(客戶訂單檢視查詢服務)通過查詢 Customer Order View 資料儲存來處理獲取一位客戶和最近的訂單的請求。

事件驅動的架構有幾個優點與缺點。它能夠實現跨越多服務並提供最終一致性的事務。另一個好處是它還使得應用程式能夠維護物化檢視

一個缺點是其程式設計模型比使用 ACID 事務更加複雜。通常,您必須實現補償事務以從應用程式級別的故障中恢復。例如,如果信用檢查失敗,您必須取消訂單。此外,應用程式必須處理不一致的資料。因為未提交的事務所做的更改是可見的。如果從未更新的物化檢視中讀取,應用程式依然可以看到不一致性。另一個缺點是訂閱者必須要檢測和忽略重複的事件。

5.3、實現原子性

在事件驅動架構中,同樣存在著原子更新資料庫和釋出事件相關問題。例如,Order Service 必須在 ORDER 表中插入一行資料,併發布 Order Created 事件。這兩個操作必須原子完成。如果在更新資料庫後但在釋出事件之前發生服務崩潰,系統將出現不一致性。確保原子性的標準方法是使用涉及到資料庫和 Message Broker 的分散式事務。然而,由於上述原因,如 CAP 定理,這並不是我們想做的。

5.4、使用本地事務釋出事件

實現原子性的一種方式是應用程式使用僅涉及本地事務的多步驟過程來發布事件。訣竅在於儲存業務實體狀態的資料庫中有一個用作訊息佇列的 EVENT 表。應用程式開啟一個(本地)資料庫事務,更新業務實體狀態,將事件插入到 EVENT 表中,之後提交事務。一個單獨的應用程式執行緒或程式查詢 EVENT 表,將事件釋出到 Message Broker,然後使用本地事務將事件標記為已釋出。設計如圖 5-6 所示。

圖 5-6、本地事務實現原子性

Order Service 將一行記錄插入到 ORDER 表中,並將一個 Order Created 事件插入到 EVENT 表中。Event Publisher(事件釋出者)執行緒或程式從 EVENT 表中查詢未釋出的事件,之後釋出這些事件,最後更新 EVENT 表將事件標記為已釋出。

這種方法有好有壞。好處是它保證了被髮布的事件每次更新都不依賴於 2PC。此外,應用程式釋出業務級事件,這些事件可以消除推斷的需要。這種方法的缺點是它很容易出錯,因為開發人員必須要記得釋出事件。這種方法的侷限性在於,由於其有限的事務和查詢功能,在使用某些 NoSQL 資料庫時,實現起來將是一大挑戰。

該方法通過讓應用程式使用本地事務更新狀態和釋出事件來消除對 2PC 的依賴。現在我們來看一下通過應用程式簡單地更新狀態來實現原子性的方法。

5.5、挖掘資料庫事務日誌

不依靠 2PC 來實現原子性的另一種方式,是用一個對資料庫的事務或者提交日誌進行挖掘的執行緒或程式來發布事件。當應用程式更新資料庫時,更改資訊被記錄到資料庫的事務日誌中。事務日誌挖掘器(Transaction Log Miner) 執行緒或程式讀取事務日誌並向 Message Broker 釋出事件。設計如圖 5-7 所示。

圖 5-7、Message Broker 可以公斷資料事務

一個使用此方法的示例是 LinkedIn Databus 開源專案。Databus 挖掘 Oracle 事務日誌併發布與更改相對應的事件。LinkedIn 使用 Databus 保持與系統的記錄一致的各種派生資料儲存。

另一個例子是 AWS DynamoDB 中的流機制,它是一個託管的 NoSQL 資料庫。DynamoDB 流包含了在過去 24 小時內對 DynamoDB 表中的項進行的更改(建立、更新和刪除操作),其按時間順序排列。應用程式可以從流中讀取這些更改,比如,將其作為事件釋出。

事務日誌挖掘有各種好處與壞處。一個好處是它能保證被髮布的事件每次更新都不依賴於 2PC。事務日誌挖掘還可以通過將事件釋出與應用程式的業務邏輯分離來簡化應用程式。一個主要的缺點是事務日誌的格式對於每個資料庫來說都是專有的,甚至在不同資料庫版本之間格式就發生了改變。而且,記錄於事務日誌中的低階別更新可能難以對高階業務事件進行逆向工程。

事務日誌挖掘消除了應用程式在做一件事時對 2PC 的依賴:更新資料庫。現在我們來看看另一種可以消除更新並僅依賴於事件的不同方式。

5.6、使用事件溯源

事件溯源通過使用不同於之前的、以事件為中心的方式來持久化業務實體,實現無 2PC 原子性。應用程式不儲存實體的當前狀態,而是儲存一系列狀態改變事件。應用程式通過回放事件來重建實體的當前狀態。無論業務實體的狀態何時發生變化,其都會將新事件追加到事件列表中。由於儲存事件是一個單一操作,因此具有原子性。

要了解事件溯源的工作原理,以 Order(訂單)實體為例。在傳統方式中,每個訂單都與 ORDER 表中的某行記錄相對映,也可以對映到例如 ORDER_LINE_ITEM 表中的記錄。

但當使用事件溯源時,Order Service 將以狀態更改事件的形式儲存 Order:Created(建立)、Approved(批准)、Shipped(發貨)、Cancelled(取消)。每個事件包含足夠的資料來重建 Order 的狀態。

圖 5-8、事件有完整的資料

事件被持久化在事件儲存中,事件儲存是一個事件的資料庫。該儲存有一個用於新增和檢索實體事件的 API。事件儲存還與我們之前描述的架構中的 Message Broker 類似。它提供了一個 API,使得服務能夠訂閱事件。事件儲存向所有感興趣的訂閱者派發所有事件。可以說事件儲存是事件驅動微服務架構的支柱。

事件溯源有幾個好處。它解決了實現事件驅動架構的關鍵問題之一,可以在狀態發生變化時可靠地釋出事件。因此,它解決了微服務架構中的資料一致性問題。此外,由於它持久化的是事件,而不是領域物件,所以它主要避免了物件關係阻抗失配問題。事件溯源還提供了對業務實體所做更改的 100% 可靠的審計日誌,可以實現在任何時間點對實體進行時間查詢以確定狀態。事件溯源的另一個主要好處是您的業務邏輯包括鬆耦合的交換事件業務實體,這使得從單體應用程式遷移到微服務架構將變得更加容易。

事件溯源同樣有缺點。這是一種不同而陌生的程式設計風格,因此存在學習曲線。事件儲存僅支援通過主鍵查詢業務實體。您必須使用命令查詢責任分離(CQRS)來實現查詢。因此,應用程式必須處理最終一致的資料。

5.7、總結

在微服務架構中,每個微服務都有私有的資料儲存。不同的微服務可能會使用不同的 SQL 或者 NoSQL 資料庫。雖然這種資料庫架構具有明顯的優勢,但它創造了一些分散式資料管理挑戰。第一個挑戰是如何實現維護多個服務間的業務事務一致性。第二個挑戰是如何實現從多個服務中檢索資料。

大部分應用使用的解決方案是事件驅動架構。實現事件驅動架構的一個挑戰是如何以原子的方式更新狀態以及如何釋出事件。有幾種方法可以實現這點,包括了將資料庫作為訊息佇列、事務日誌挖掘和事件溯源。

微服務實戰:NGINX 與儲存優化

by Floyd Smith

基於微服務的儲存方式涉及大數量和各種資料儲存,訪問和更新資料將變得更加複雜,DevOps 在維護資料一致性方面面臨著更大的挑戰。NGINX 為這種資料管理提供了重要支援,主要有三個方面:

  1. 資料快取與微快取(microcaching)

    使用 NGINX 快取靜態檔案和微快取應用程式生成的內容可減輕應用程式的負載、提高效能並減少問題的發生。

  2. 資料儲存的靈活性與可擴充套件性

    一旦將 NGINX 作為反向代理伺服器,您的應用程式在建立、調整大小、執行和調整資料儲存伺服器的大小時可獲得很大的靈活性,以滿足不斷變化的需求 — 每個服務都擁有自己的資料儲存是很重要的。

  3. 服務監控與管理,包括資料服務

    隨著資料伺服器數量的增加,支援複雜操作和具有監控和管理工具顯得非常重要。NGINX Plus 內建了這些工具和應用程式效能管理合作伙伴的介面,如 Data Dog、Dynatrace 和 New Relic。

微服務相關的資料管理示例可在 NGINX 微服務參考架構的三大模型中找到,其為您設計決策和實施提供了起點。

此係列全部譯文

github.com/oopsguy/mic…

相關文章