5-事件驅動的分佈式數據管理

本文出自Nginx官網,是微服務介紹系列文章的第五篇。原文地址:https://www.nginx.com/blog/event-driven-data-management-microservices/

1.介紹

在第一篇文章中我們比較了微服務架構應用和單體應用的差異,討論了微服務架構的優點與缺點;第二篇文章和第三篇文章中討論了進程間通信相關的問題;第四篇文章討論服務發現問題;在本篇文章中我們討論分佈式數據管理相關的問題。

2.微服務下分佈式數據管理面臨的問題

單體應用一般有單一的關係型數據庫,使用關係型數據庫的主要好處是應用可以使用ACID事務,提供事務操作的原子性、一致性、隔離性和持久性等保證。應用使用關係型數據庫時先開啓事務,再進行多個表的增刪改查操作,最後提交事務,這樣就能保證數據一致性。

關係型數據庫的另外一個優點是它支持功能豐富、聲明式、標準化的SQL語言。使用SQL語言很容易實現多表查詢,數據庫管理系統會選擇最優執行路徑,開發人員不需要考慮如何訪問數據庫。

在微服務架構下,數據訪問變複雜了,因爲數據歸每一個微服務所有,只能通過服務接口訪問。通過服務接口對數據封裝能確保微服務之間的鬆散耦合和獨立進化,如果多個服務都訪問同一部分數據,對數據更新時就要花費大量時間協調多個服務的更新。

服務可能使用不同類型的數據庫,這讓問題更復雜。現代應用要處理各式各樣的數據,很多情況下,關係型數據庫並不是最優選擇。某些場景下,NoSQL數據庫可能有更簡潔的數據模型、更好的性能和伸縮性:存儲和查詢文本的服務使用像Elasticsearch這樣的文本搜索引擎更合適;存儲和處理社會關係的服務使用像Neo4j的圖數據庫更方便。微服務架構的應用經常混合使用SQL和NoSQL數據庫,這就是所謂的多語言持久化方法。

數據存儲使用分區的、多語言持久化架構有很多優點,能實現服務間鬆散耦合、具有更好的性能和伸縮性,但隨之而來的,也帶來了分佈式數據管理的挑戰。

第一個挑戰是如何實現跨服務的事務,下面以在線商店爲例具體討論。在線商店應用中,顧客服務擁有顧客信用卡限額的信息;訂單服務管理用戶訂單,在下訂單之前必須確保訂單金額不超過顧客信用卡限額。在單體應用中,訂單服務簡單使用ACID事務就能實現顧客信用卡限額檢查、下訂單。

微服務架構下,訂單表和顧客表分屬於不同的服務,是私有的,其他服務不能直接訪問,圖示如下:


訂單服務不能直接訪問顧客表,只能通過顧客服務的接口訪問。理論上訂單服務可以使用分佈式事務(兩階段提交)實現,實際上,在現代應用中,分佈式事務不是可選項。CAP定理要求你在可用性和ACID風格的一致性之間選擇,而可用性一般都是更明智的選擇;另外,很多現代技術(像NoSQL數據庫)不支持兩階段提交。既然如此,在多個服務和數據庫之間保持一致性又很關鍵,我們需要其他解決方案。

         第二個挑戰是如何實現從多個服務中獲取數據。假設應用需要展示顧客和最近訂單,如果訂單服務提供根據顧客查詢訂單的接口,則可以使用應用側聯合的方式實現:先從顧客服務中獲取顧客,再根據顧客從訂單服務中獲取訂單;如果訂單服務只支持按照主鍵獲取信息(假設使用NoSQL數據庫且只支持根據主鍵查詢),這就沒有簡單的方法能獲取到數據了。

3.事件驅動架構

對很多應用而言,事件驅動架構是一種解決方案。在這種架構下,有需要關注的事情發生時(比如服務更新了業務實體),就發佈一個事件;其他服務訂閱該服務的事件,在監測到事件後根據業務邏輯更新自己的實體,從而引發更多事件的發佈。

可以使用事件實現跨服務的事務,事務包括一系列步驟,每一步都包括更新業務實體、發佈事件,從而觸發下一步。下面系列圖示展示瞭如何使用事件驅動的方法實現信用卡額度校驗;在圖示中,服務之間通過消息中間件交換事件。

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


2.顧客服務消費訂單創建事件,發佈信用保留事件。


3.訂單服務消費信用保留事件,將訂單狀態變更爲“OPEN”。


假設:a.每個服務自動更新數據庫、發佈事件;b.消息中間件保證事件最少被分發一次;這就能實現跨越服務的事務。需要指出這不是ACID事務,它只能提供最終一致性這樣弱的保證,這種事務模型也叫基本模型(可參見:https://queue.acm.org/detail.cfm?id=1394128)。

還可以使用事件模型構建連結多個服務數據的物化視圖。維護視圖的服務訂閱相關事件,根據事件更新視圖。比如,可以實現一個顧客訂單更新服務來維護顧客訂單視圖,它訂閱顧客服務和訂單服務的事件,如下圖:


當顧客訂單更新服務接收到顧客服務和訂單服務的事件時,它會更新物化視圖的數據存儲;你可能使用類似MongoDB的文檔數據庫存儲視圖數據,每個顧客一個文檔。顧客訂單視圖查詢服務基於視圖數據提供顧客查詢和最近訂單查詢。

事件驅動架構的優點:能實現跨服務的事務,提供最終一致性保證;應用可以維護物化視圖;缺點:1.爲了從應用級故障中恢復,需要實現補償事務;比如,如果信用卡額度校驗失敗,需要主動撤銷訂單。2.應用可能讀到不一致數據(物化視圖更新前的數據)。3.訂閱者需要忽略重複消息。

4.實現原子操作

在事件驅動架構中,需要保證更新數據和發佈事件是原子操作。在示例中,訂單服務在訂單數據表中插入一行數據和發佈新訂單事件必須在一個事務中,如果系統在插入數據後崩潰,需要回滾插入數據的操作,不然就會造成不一致。保證原子操作的一般辦法是在數據庫和消息中間件之間構建分佈式事務,但是,就像之前描述的,基於CAP定理,不會使用分佈式事務實現。

使用本地事務發佈事件

實現原子操作的一個辦法是使用本地事務進行多步操作。這種方法需要創建一個事件表,它類似消息隊列,用於保存所有業務實體的狀態。應用更新業務表,同時更新事件表,這兩步操作放在一個事務中;另外創建進程查詢事件表,負責將事件發佈到消息中間件,再使用本地事務將事件表中的事件置爲已發佈。以下圖示展示了過程:


訂單服務向訂單表中插入一行數據,再向事件表中添加訂單創建事件;事件發佈進程查詢事件表中所有未發佈的事件,發佈事件,再使用本地事務將事件狀態修改爲已發佈。

         這種方法的好處是在不使用分佈式事務的前提下,保證了業務表的更新都會發布事件,發佈的事件是業務級的;缺點是需要開發人員編碼發佈事件,容易因爲遺忘而出錯,還有就是對某些事務能力和查詢能力較弱的NoSQL數據庫而言,不容易實現。

挖掘數據庫事務日誌

應用更新數據庫時,會導致數據庫事務日誌的變化,日誌挖掘進程通過讀取數據庫事務日誌的方式,實現事件發佈。下圖展示了此過程:


這種辦法的一個示例是開源的LinkedIn Databus項目,Databus挖掘oracle數據庫的事務日誌,根據日誌變化發佈事件。LinkedIn使用Databus保持多個衍生數據存儲和核心業務數據的一致性。

另外一個示例是AWS DynamoDB中的流機制。DynamoDB流包括按時間順序排列的過去24小時內DynamoDB表中所有數據項的變化(包括創建、更新和刪除操作),應用可以從流中讀取變化、發佈事件。

事務日誌挖掘方式的優點是將事件發佈從業務邏輯中隔離出來,簡化了應用開發;缺點是不同數據庫甚至同一數據庫的不同版本,日誌格式不一致,需要針對開發,另外一個問題是將底層數據庫的變化轉化到高層的業務事件可能比較困難。

使用事件源

事件源機制使用完全不同的,以事件爲中心的業務實體持久化方法。事件源機制不保存業務實體的當前狀態,它按時間順序保存狀態變化事件,應用可以重放事件來構建最新狀態。事件源機制在業務實體狀態變化時創建新的事件,附加到事件列表中;因爲保存事件是單一操作,它天生支持原子性。

以訂單實體爲例討論事件源機制的運行。在傳統的做法中,每個訂單對應數據庫訂單表中的一行數據;在事件源機制下,訂單是一組記錄業務實體狀態變化的事件(創建、審覈、取消等),每個事件包含重建訂單狀態的所有數據。過程見下圖:


每一個事件都持久化在事件存儲中,事件存儲提供添加和獲取事件的接口。事件存儲就像消息中間件,它提供接口支持其他服務訂閱事件,負責將事件分發給訂閱該事件的服務。事件存儲是事件驅動微服務架構的骨架。

事件源機制的好處:它保證事件驅動架構下業務實體狀態變化時,事件能可靠發佈;解決了微服務架構下的數據一致性問題;因爲它保存的是事件不是領域對象,不需要做對象和關係型數據庫的映射;它提供了100%可靠的數據庫審計,能查詢任意時間點的數據庫狀態;它能保證組成業務邏輯的服務間鬆散耦合。在單體應用中使用事件源機制可以簡化向微服務架構遷移的工作。

事件源機制的缺點:它不同於常見的編程模式,有一定的學習曲線;事件存儲直接支持的查詢只有一種:根據業務實體的主鍵查詢,需要使用CQRS(Command Query Responsibility Segregation)支持複雜查詢;應用需要操作持久化的事件數據(而不是業務實體數據)。

5.總結

在微服務架構下,每個微服務都有自己的私有數據存儲,不同的微服務可能使用SQL或者NoSQL數據庫,這帶來了分佈式數據管理的挑戰。第一個挑戰是如何實現跨服務的事務,第二個挑戰是如何跨服務的查詢。

對於大多數應用來說,解決方案是使用事件驅動架構。實現事件驅動架構需要考慮如何實現更新狀態和發佈事件的原子性,有幾種實現方式:包括將數據庫作爲消息隊列、事務日誌挖掘和事件源機制等。

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