Chris Richardson微服務翻譯:微服務之事件驅動的數據管理

Chris Richardson 微服務系列翻譯全7篇連接:html

原文連接:Event-Driven Data Management for Microservices
nginx


微服務與分佈式數據管理問題

單體應用通常只有一個關係型數據庫,這樣的好處是能夠實現 ACID 保證:git

  • 原子性(Atomicity):原子粒度的更改
  • 一致性(Consistency)數據庫的狀態始終保持一致
  • 隔離性(Isolation):併發的事務表現的像是串行執行,事務之間不會互相影響
  • 持久性(Durable):一旦事務提交就不會撤銷

所以,應用能夠簡單的開始事務,更改(增刪改)多行數據,而後提交事務。github

使用關係型數據庫另外一好處是支持 SQL。SQL 是一種豐富的、聲明式的標準查詢語言,用戶能簡易的關聯查詢多個表中的數據,而後RDBMS 查詢調度器會執行最優的查詢方式,用戶沒必要關係底層的細節。全部的數據在一個數據庫中也方便查詢。數據庫

然而微服務架構中數據訪問變的複雜,由於每一個微服務都擁有獨立的數據庫,僅能經過 API 來訪問。數據封裝保證了微服務的鬆耦合,各個服務能夠獨立其餘服務演進。而若是多個服務訪問一樣的數據,架構更新會更耗費時間,也須要更多的服務協調。編程

不一樣服務可能使用不一樣類型的數據庫,現代應用存儲和處理各類各樣的數據,關係數據庫並不老是最好的選擇。一些場景下,一種特殊的 NoSQL 能提供更方便的數據模型、更好的性能和可擴展性。例如:使用 ElasticSearch 這樣的搜索引擎來進行文本的存儲和查詢;使用 Neo4j 這樣的圖譜數據庫來存儲社交圖譜數據。所以,微服務應用會混合使用 SQL 和 NoSQL 數據庫,即多態型數據持久方案。這也帶來了一些挑戰:架構

1)如何跨多個服務實現事務,維護數據的一致性。咱們以 B2B 商店爲例:客戶服務維護用戶信用額度等相關的信息,訂單服務管理訂單並確保新訂單沒有超過用戶的信用額度。單體應用中,訂單服務可使用 ACID 事務來覈對用戶信用額度並新建訂單。而在微服務架構中, order 和 customer 表是各個服務私有的:併發

訂單服務沒法直接訪問 customer 表,只能經過客戶服務的 API。訂單服務可能使用分佈式事務,也被稱爲兩階段提交(2PC),然而 2PC 在現代應用一般不是很好的選擇。CAP 定理須要用戶在可用性和 ACID 的一致性中二選一,一般可用性是更好的選擇。此外,不少NoSQL 並不支持 2PC,維護服務和數據庫中數據的一致性是很重要的,所以咱們能夠選擇另外一種方案。分佈式

2)另外一個挑戰是如何檢索多個服務中的數據,例如應用須要顯示一位客戶和他最近的訂單,若是訂單服務提供了用戶訂單的查詢 API,那麼能夠在應用端獲取該數據,應用端經過客戶服務檢索客戶,再經過訂單服務檢索該客戶的訂單。假設訂單服務只支持經過主鍵來查詢訂單,此時就沒有合適的方法來檢索所需數據了。ide

事件驅動的架構

對於許多應用,解決方案就是事件驅動的架構:服務在業務發生時(例如更新一條記錄信息)會發佈一個事件,其餘微服務訂閱該事件,當某一微服務接收到事件就會更新本身的業務記錄,而後其餘更多的事件會被髮布。下圖展現瞭如何使用事件驅動的方式在建立訂單時檢查可用信用,微服務間經過 MQ 來交換事件:

1)訂單服務建立狀態爲 NEW 的訂單,而後發佈『訂單建立』的事件

2)客戶服務獲取『訂單建立』事件,爲此訂單保留信用,發佈『信用保留』事件

3)訂單服務獲取『信用保留』事件,將訂單狀態修改成 OPEN

更爲複雜的場景會涉及更多的步驟,好比覈對用戶信用時預留庫存。

基於(a)每一個服務原子性的更新數據庫併發布事件;(b)MQ 確保事件至少交付一次,那麼就能夠實現跨服務的業務邏輯了。這並不是 ACID 事務,他提供更弱一點的最終一致性。這種事務模型可稱爲 BASE model

也可使用事件維護關聯多個微服務的物化視圖。維護此視圖的服務訂閱相關事件並更新視圖,例如:用戶訂單視圖經過訂閱訂單事件和用戶事件來進行更新:

當用戶訂單服務收到用戶服務或訂單服務的事件時,會更新視圖。可使用相似 MongoDB 的文檔數據庫爲每一個用戶存儲一份用戶訂單的文檔。

事件驅動架構的優勢:

  1. 他使得事務能跨多個服務並提供最終一致性;
  2. 使得應用能夠維護物化視圖。

不足之處:

  1. 編程模型比 ACID 事務更加複雜,爲了從應用級別的錯誤中恢復,須要完成補償事務,例如:信用檢查不成功則必須取消訂單;
  2. 臨時事務會致使不一致的數據。另外應用從物化視圖中讀取的數據未能及時更新,也會產生不一致的問題;
  3. 必須檢測並忽略重複事件

實現原子化

事件驅動架構還存在一個問題:以原子粒度更新 DB 與發佈事件。例如:訂單服務在訂單表中 insert 一行記錄,而後發佈『訂單建立』的事件,這兩個操做須要是原子性的,不然,更新 DB 後,發佈事件前服務崩潰了,系統將存在不一致。確保原子性的方法是使用 分佈式事務 調用 DB 和 MQ。然而因爲 CAP 理論,咱們是想避免這麼作。

使用本地事務發佈事件

應用發佈事件並保證原子性的方法之一就是:多步驟本地事務方法。技巧是 DB 中有一張 EVENT 表(模擬消息隊列),存儲業務數據的狀態。首先啓動一個本地數據庫事務,更新業務數據記錄並往 EVENT 表中插入一條數據,最後提交事務。一個單獨的線程會輪詢 EVENT 表,將查詢結果往 MQ 中發送事件消息,而後使用本地事務標註事件狀態爲已發佈。以下圖所示:

訂單服務首先往 ORDER 表中 insert 一行記錄,而後在 EVENT 表插入類型爲 Order Created 的事件(狀態爲 NEW )。事件發佈線程或進程輪詢 EVENT 表中未發佈的事件,發佈事件而後更新 EVENT 表事件狀態爲已發佈。

這種方法的優勢:

  1. 保證每次更新都有對應的事件發佈,不使用兩階段提交(2PC);
  2. 應用發佈了業務級別的事件,消除推斷事件類型的麻煩。

不足:

  1. 易出錯,由於要求開發者必須記得更新後去發佈事件;
  2. 當使用 NoSQL 時,由於 NoSQL 的事務和查詢能力有限,實現起來較麻煩。

挖掘數據庫事務日誌

另外一種不須要兩階段提交就能實現原子性的方法是:分析數據庫事務日誌或提交日誌來發布事件。應用更新 DB時,DB的事務日誌會記錄這些變動,事務日誌挖掘線程或進程讀取這些日誌,並將事件發佈到 MQ,以下圖所示:

範例之一就是開源的 LinkedIn Databus 項目,Databus 挖掘 Oracle等數據庫的事務日誌併發布相應的事件,LinkedIn 使用 Databus 來保持各類派生數據存儲和記錄系統的一致。

另外一範例就是 streams mechanism in AWS DynamoDB,AWS DynamoDB 流包括 DynamoDB 表在過去 24 小時內的時序變化,包括新建、更新和刪除操做。應用能讀取這些變動,將其做爲事件發佈。

事務日誌挖掘的優勢:

  1. 能保證無需使用兩階段提交就能對每一個更新發布事件;
  2. 簡化應用,將事件發佈與主業務邏輯分離。

不足:

  1. 每一個 DB 或 同一 DB 的不一樣版本的事務日誌格式不一樣;
  2. 很難從低級別事務日誌的更新記錄中反推高級別的業務事件。

使用事件源

事件源經過採用一種大相徑庭的、以事件爲中心的方法來保存業務實體,而不須要 2PC 來實現原子性。這種方法存儲一系列狀態變更的事件,而不是實體的當前狀態。應用經過重放事件來構建實體的當前狀態,每當業務實體的狀態改變,就往事件列表中添加新的事件。因爲保存事件是惟一操做,本質上就是原子性的。

以訂單爲例:傳統方案中,每一個訂單爲 ORDER 表中的一行記錄。使用事件源時,訂單服務存儲致使訂單狀態變化的事件,包括建立、批准、配送、取消。每一個事件由充足的信息來從新構建訂單:

事件被存儲 DB 中,可以使用 API 添加或查找實體的事件。事件存儲相似上文說起的 MQ,提供 API 讓其餘服務訂閱事件,將事件傳達至感興趣的訂閱者。事件存儲是事件驅動的微服務架構的支柱。

事件源的優勢:

  1. 解決了事件驅動的微服務架構的關鍵問題,可以可靠的發佈事件;
  2. 解決了數據一致性問題,因爲存儲事件而不是領域對象,也避免了面向對象到關係數據庫的不匹配問題;
  3. 爲實體提供了100%可靠的審計日誌,使得獲取任什麼時候間點的實體狀態成爲可能;
  4. 業務邏輯與事件交互的業務實體是鬆耦合的,這使得單體服務遷移到微服務更容易。

不足:

  1. 採用了陌生的編程風格,學習曲線陡峭;
  2. 事件數據庫只支持經過主鍵查詢業務實體,必須使用 CQRS(Command Query Responsibility Segregation)來完成查詢,所以應用程序必須採用最終一致性。

總結

微服務架構中,每一個微服務都有本身的數據存儲,不一樣的微服務可能使用不一樣的 SQL 和 NoSQL 數據庫。這些數據庫架構有不少優點,也帶來了分佈式數據管理的挑戰。第一個挑戰就是如何實現跨服務的業務事務,並保證一致性;第二個挑戰就是如何從多個服務中查詢數據。

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

相關文章
相關標籤/搜索