【CHRIS RICHARDSON 微服務系列】事件驅動的數據管理-5

編者的話 |本文來自 Nginx 官方博客,是「Chris Richardson 微服務」系列的第五篇文章。第一篇文章介紹了微服務架構模式,並且討論了使用微服務的優缺點;第二和第三篇描述了微服務架構模塊間通訊的不同方面;第四篇研究了服務發現中的問題。本篇研究微服務架構帶來的分佈式數據管理問題

作者介紹:Chris Richardson,是世界著名的軟件大師,經典技術著作《POJOS IN ACTION》一書的作者,也是 cloudfoundry.com 最初的創始人,Chris Richardson 與 Martin Fowler、Sam Newman、Adrian Cockcroft 等並稱爲世界十大軟件架構師。

Chris Richardson 微服務系列全 7 篇:

1. 微服務架構概念解析

2. 構建微服務架構:使用 API Gateway

3. 深入微服務架構的進程間通信(本篇文章)

4. 服務發現的可行方案以及實踐案例

5. 微服務的事件驅動數據管理

6. 選擇微服務部署策略

7. 將單體應用改造爲微服務(本篇文章)

微服務以及分佈式數據管理中存在的問題

單體應用通常使用單個關係型數據庫,由此帶來的好處在於應用能夠使用 ACID 事務,後者提供了重要的操作特性:

  • 原子化:原子粒度的更改
  • 一致性:數據庫的狀態始終保持一致
  • 隔離:併發執行的事務顯示爲串行執行
  • 持久:事務一旦提交就不會被撤銷

如此,應用能夠簡單地開始事務、更改(插入、更新和刪除)多行、以及提交事務。

使用關係型數據庫的另一大好處是它支持 SQL。SQL 是一門豐富、可聲明的和標準化的查詢預約。用戶能夠輕鬆通過查詢將多個表中的數據組合起來,然後 RDBMS 查詢調度器決定執行查詢的最優方法。用戶不必關心底層細節,比如如何訪問數據庫。此外,由於所有的應用數據在一個數據庫中,很容易查詢。

然而,微服務架構中的數據訪問變得複雜許多。每個微服務擁有的數據專門用於該微服務,僅通過其 API 訪問。這種數據封裝保證了微服務鬆散耦合,並且可以獨立更新。但如果多個服務訪問相同數據,架構更新會耗費時間、也需要所有服務的協調更新。

更糟糕的是,不同的微服務通常使用不同類型的數據庫。現代應用存儲和處理各種類型的數據,而關係型數據庫並非總是好選擇。對於一些使用場景,特定的 NoSQL 數據庫能提供更方便的數據模型、更好的性能和可擴展性。譬如,服務使用 Elasticsearch 這樣的文本搜索引擎來存儲和查詢文本;同樣地,存儲社交圖譜數據的服務可能需要使用 Neo4j 這樣的圖譜數據庫。因此,基於微服務的應用通常會混合使用 SQL 和 NoSQL 數據庫,即多語言留存(polyglot persistence approach)。

分區的、多語言留存的架構對於數據存儲有很多好處,包括服務的鬆耦合、更好的性能和可擴展性。然而,它也確實給分佈式數據管理帶來了挑戰。

第一個挑戰就是如何實現業務邏輯,保持多種服務的一致性。爲了說明爲何這是一個問題,我們以在線 B2B 商店爲例。Customer Service(下文使用客戶服務)維護與用戶有關的信息,包括信用信息。Order Service(下文使用訂單服務)管理訂單,驗證新訂單沒有超出用戶的信用額度。在單體應用裏,訂單服務可以簡單地使用 ACID 事務來覈對提供的信用信息和創建訂單。

相反,在微服務架構中,如下圖所示,訂單表和客戶表爲各自對應的服務私有。

訂單服務無法直接訪問客戶表,只能通過客戶服務提供的 API。訂購服務可能使用分佈式事務,也被稱爲兩步提交(2PC)。然而,2PC 通常不是現代應用的可行選項。CAP 定理需要用戶在可用性和 ACID 風格的一致性中二選一,通常可用性是更好的選擇。此外,許多現代技術,譬如大多數 NoSQL 數據庫並不支持 2PC。維護整個服務和數據庫中的數據一致性是至關重要的,因此我們需要另一種解決方案。

第二個挑戰就是如何實現檢索多個服務數據的查詢。假設應用需要顯示一位客戶和他的最近的訂單。如果訂單服務爲檢索客戶訂單提供了 API,那麼可以使用應用端獲取該數據。應用通過客戶服務檢索該客戶,通過訂單服務檢索該顧客的訂單。但是假如訂單服務只支持通過訂單主鍵查詢訂單(可能使用僅支持鍵值檢索的 NoSQL 數據庫),這種情況下,就沒有合適的方法來檢索所需數據。

事件驅動的架構

對於許多應用,解決方案就是事件驅動的架構。在這一架構裏,當有顯著事件發生時,譬如更新業務實體,某個微服務會發布事件,其它微服務則訂閱這些事件。當某一微服務接收到事件就可以更新自己的業務實體,實現更多事件被髮布。

用戶能夠使用事件來實現跨多個服務的業務邏輯。事務由一系列步驟組成,每一步都有一個微服務更新業務實體,然後發佈觸發下一步的事件。下面的系列圖展示瞭如何使用事件驅動的方法在創建訂單時檢查可用信用。微服務通過消息代理來交換事件。

  1. 訂單服務創建狀態爲 NEW 的訂單,併發布“訂單已創建”事件。

  1. 客戶服務獲取“訂單已創建”事件,爲此訂單保留信用,發佈“信用保留”事件。

  1. 訂單服務獲取“信用保留”事件,把訂單狀態修改爲 OPEN。

更爲複雜的場景可能涉及更多的步驟,比如在覈對客戶信用的同時預留庫存。

基於(a)每個服務自動更新數據庫和發佈事件,以及(b)消息代理確保事件傳遞至少一次,用戶能夠跨多個服務完成業務邏輯。注意它們並非 ACID 業務。這種模式提供弱確定性,比如最終一致性。這種事務模型也被稱作 BASE 模型。

用戶也可以使用事件來維護不同微服務擁有的預連接數據的物化視圖。維護此視圖的服務訂閱相關事件,並更新視圖。例如,維護客戶訂單視圖的客戶訂單視圖更新服務會訂閱由客戶服務和訂單服務發佈的事件。

當客戶訂單查看更新服務收到客戶或者訂單事件,就會更新客戶訂單查看的數據存儲。用戶能夠使用類似 MongoDB 的文檔數據庫查看用戶訂單,併爲每位客戶存儲一個文檔。用戶訂單預覽查詢服務通過客戶訂單預覽數據存儲,處理來自客戶和最近訂單的請求。

事件驅動的架構有優點也有缺點。它使得事務跨多個服務並提供最終一致性,也可以讓應用維護物化視圖。缺點之一在於,它的編程模型要比使用 ACID 事務的更加複雜。爲了從應用級別的失效中恢復,還需要完成補償性事務,例如,如果信用檢查不成功則必須取消訂單。此外,由於臨時事務造成的改變顯而易見,因而應用必須處理不一致的數據。此外,如果應用從物化視圖中讀取的數據沒有更新時,也會遇到不一致的問題。此架構的另一缺點就是用戶必須檢測並忽略重複事件。

實現原子化

事件驅動的架構還存在以原子粒度更新數據庫併發布事件的問題。例如,訂單服務必須在訂單表中插入一行,然後發佈“訂單已創建”事件。這兩個操作需要原子化實現。如果服務在更新數據庫之後、發佈事件之前崩潰,系統變得不一致。確保原子化的標準做法是使用包含數據庫和消息代理的分佈式事務。然而,基於以上描述的 CAP 理論,這並非我們所想。

使用本地事務發佈事件

實現原子化的方法是使用多步驟進程來發布事件,該進程只包含本地事務。訣竅就是在存儲業務實體狀態的數據庫中,有一個事件表來充當消息隊列。應用啓動一個(本地)數據庫事務,更新業務實體的狀態,在事件表中插入一個事件,並提交該事務。獨立的應用線程或進程查詢事件表,將事件發不到消息代理,然後使用本地事務標註事件併發布。下圖展示了這一設計。

訂單服務在訂單表中插入一行,然後在事件表中插入“訂單已創建”的事件。時間發佈線程或進程在事件表中查詢未發佈的事件併發布,然後更新事件表,將該事件標記爲已發佈。

這種方法優缺點兼具。優點之一是保證每個更新都有對應的事件發佈,並且無需依賴 2PC。此外,應用發佈業務級別的事件,消除了推斷事件的需要。這種方法也有缺點。由於開發者必須牢記發佈事件,因此有很大可能出錯。此外這一方法對於某些使用 NoSQL 數據庫的應用是個挑戰,因爲 NoSQL 本身交易和查詢能力有限。

通過此方法,應用使用本地事務來更新狀態和發佈事件,排除了對 2PC 的需要。接下來,我們瞭解使用應用更新狀態實現原子化的方法。

挖掘數據庫事務日誌

無需 2PC 實現原子化的另一種方式是由線程或者進程通過挖掘數據庫事務或提交日誌來發布事件。應用更新數據庫,數據庫的事務日誌記錄這些變更。事務日誌挖掘線程或進程讀取這些日誌,並把事件發佈到消息代理。如下圖所示:

這一方法的範例是開源的 LinkedIn Databus 項目。Databus 挖掘 Oracle 事務日誌併發布與之對應的事件。LinkedIn 使用 Databus 維持各種來源的數據存儲與記錄系統一致。

另一個範例則是 AWS DynamoDB 採用的流機制,AWS DynamoDB 是一個可管理的 NoSQL 數據庫。每個 DynamoDB 流包括 DynamoDB 表在過去 24 小時之內的時序變化,包括創建、更新和刪除操作。應用能夠讀取這些變更,將其作爲事件發佈。

事務日誌挖掘具有多個優點。首先,它能保證無需使用 2PC 就能針對每個更新發布事件。其次,通過將日誌發佈於應用的業務邏輯分離,事務日誌挖掘能夠簡化應用。事務日誌挖掘也有缺點,主要缺點就是事務日誌的格式與每個數據庫對應,甚至隨着數據庫版本而變化。此外,很難從底層事務日誌更新記錄中逆向工程這些業務事件。

通過讓應用更新數據庫,事務日誌挖掘消除了對 2PC 的需要。接下來我們會討論另一種方法——消除更新,只依賴事件。

使用事件源

通過採用一種截然不同的、以事件爲中心的方法來留存業務實體,事件源無需 2PC 實現了原子化。不同於存儲實體的當前狀態,應用存儲狀態改變的事件序列。應用通過重播事件來重構實體的當前狀態。每當業務實體的狀態改變,新事件就被附加到事件列表。鑑於保存事件是一個單一的操作,本質上也是原子化的。

要了解事件源如何運行,可以以訂單實體爲例。在傳統的方法中,每個訂單映射爲訂單表的一行,例如一個 ORDERLINEITEM 表。使用事件源的時候,訂單服務以狀態更改事件的方式存儲訂單,包括已創建、已批准、已發貨、已取消等。每個事件都包含足夠的數據去重建訂單狀態。

事件長期保存在事件數據庫,使用 API 添加和檢索實體的事件。事件存儲類似上文提及的消息代理,通過 API 讓服務訂閱事件,將所有事件傳達到所有感興趣的訂閱者。事件存儲是事件驅動的微服務架構的支柱。

事件源有不少優點。它解決了實施事件驅動的微服務架構時的一個關鍵問題,能夠只要狀態改變就可靠地發佈事件。另外,它也解決了微服務架構中的數據一致性問題。由於儲存事件而不是域對象,它也避免了對象關係抗阻不匹配的問題(object‑relational impedance mismatch problem)。事件源提供了 100% 可靠的業務實體變化的審計日誌,使得獲取任何時間點的實體狀態成爲可能。事件源的另一大優勢在於業務邏輯由鬆耦合的、事件交換的業務實體構成,便於從單體應用向微服務架構遷移。

事件源也有缺點。由於採用了不同或不熟悉的編程風格,會有學習曲線。事件存儲只直接支持通過主鍵查詢業務實體,用戶還需要使用 Command Query Responsibility Segregation (CQRS) 來完成查詢。因此,應用必須處理最終一致的數據。

總結

在微服務架構中,每個微服務都有其私有數據存儲,不同的微服務可能使用不同的 SQL 和 NoSQL 數據庫。這些數據庫架構帶來便利的同時,也給分佈式數據管理帶來挑戰。第一個挑戰就是如何實現業務事務,保持多個服務的一致性。第二個挑戰就是如何從多個服務中檢索數據,實現查詢。

對於許多應用,解決方案就是使用事件驅動的架構。事件驅動的架構帶來的挑戰是如何原子化地更新狀態和發佈事件。有幾個方法可以做到這一點,包括把數據庫用作消息隊列、事務日誌挖掘和事件源。

文章轉載自:http://blog.daocloud.io/microservices-5/

查看英文原文

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章