這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
【編者的話】本文是使用微服務建立應用系列的第五篇文章。第一篇文章介紹了微服務架構模式,並且討論了使用微服務的優缺點;第二和第三篇描述了微服務架構模組間通訊的不同方面;第四篇研究了服務發現中的問題。本篇中,我們從另外一個角度研究一下微服務架構帶來的分散式資料管理問題。
1.1 微服務和分散式資料管理問題
單體式應用一般都會有一個關係型資料庫,由此帶來的好處是應用可以使用 ACID transactions,可以帶來一些重要的操作特性:
- 原子性 – 任何改變都是原子性的
- 一致性 – 資料庫狀態一直是一致性的
- 隔離性 – 即使交易並發執行,看起來也是串列的
- Durable – 一旦交易提交了就不可復原
鑒於以上特性,應用可以簡化為:開始一個交易,改變(插入,刪除,更新)很多行,然後提交這些交易。
使用關係型資料庫帶來另外一個優勢在於提供SQL(功能強大,可聲明的,錶轉化的查詢語言)支援。使用者可以非常容易通過查詢將多個表的資料群組合起來,RDBMS查詢調度器決定最佳實現方式,使用者不需要擔心例如如何訪問資料庫等底層問題。另外,因為所有應用的資料都在一個資料庫中,很容易去查詢。
然而,對於微服務架構來說,資料訪問變得非常複雜,這是因為資料都是微服務私人的,唯一可訪問的方式就是通過API。這種打包資料訪問方式使得微服務之間松耦合,並且彼此之間獨立。如果多個服務訪問同一個資料,schema會更新訪問時間,並在所有服務之間進行協調。
更甚於,不同的微服務經常使用不同的資料庫。應用會產生各種不同資料,關係型資料庫並不一定是最佳選擇。某些情境,某個NoSQL資料庫可能提供更方便的資料模型,提供更加的效能和可擴充性。例如,某個產生和查詢字串的應用採用例如Elasticsearch的字元搜尋引擎。同樣的,某個產生社交圖片資料的應用可以採用圖片資料庫,例如,Neo4j;因此,基於微服務的應用一般都使用SQL和NoSQL結合的資料庫,也就是被稱為polyglot persistence的方法。
分區的,polyglot-persistent架構用於儲存資料有許多優勢,包括松耦合服務和更佳效能和可擴充性。然而,隨之而來的則是分散式資料管理帶來的挑戰。
第一個挑戰在於如何完成一筆交易的同時保持多個服務之間資料一致性。之所以會有這個問題,我們以一個線上B2B商店為例,客戶服務維護包括客戶的各種資訊,例如credit lines。訂單服務管理訂單,需要驗證某個新訂單與客戶的信用限制沒有衝突。在單一式應用中,訂單服務只需要使用ACID交易就可以檢查可用信用和建立訂單。
相反的,微服務架構下,訂單和客戶表分別是相對應服務的私人表,如所示:
訂單服務不能直接存取客戶表,只能通過客戶服務發布的API來訪問。訂單服務也可以使用 distributed transactions, 也就是周知的兩階段交易認可 (2PC)。然而,2PC在現在應用中不是可選性。根據CAP理論,必須在可用性(availability)和ACID一致性(consistency)之間做出選擇,availability一般是更好的選擇。但是,許多現代科技,例如許多NoSQL資料庫,並不支援2PC。在服務和資料庫之間維護資料一致性是非常根本的需求,因此我們需要找其他的方案。
第二個挑戰是如何完成從多個服務中搜尋資料。例如,設想應用需要顯示客戶和他的訂單。如果訂單服務提供API來接受使用者訂單資訊,那麼使用者可以使用類應用型的join操作接收資料。應用從使用者服務接受使用者資訊,從訂單服務接受此使用者訂單。假設,訂單服務只支援通過私人鍵(key)來查詢訂單(也許是在使用只支援基於主鍵接受的NoSQL資料庫),此時,沒有合適的方法來接收所需資料。
1.2 事件驅動架構
對許多應用來說,這個解決方案就是使用事件驅動架構(event-driven architecture)。在這種架構中,當某件重要事情發生時,微服務會發布一個事件,例如更新一個業務實體。當訂閱這些事件的微服務接收此事件時,就可以更新自己的業務實體,也可能會引發更多的時間發布。
可以使用事件來實現跨多服務的業務交易。交易一般由一系列步驟構成,每一步驟都由一個更新業務實體的微服務和發布啟用下一步驟的事件構成。展現如何使用事件驅動方法,在建立訂單時檢查信用可用度,微服務通過訊息代理(Messsage Broker)來交換事件。
- 訂單服務建立一個帶有NEW狀態的Order (訂單),發布了一個“Order Created Event(建立訂單)”的事件。
- 客戶服務消費Order Created Event事件,為此訂單預留信用,發布“Credit Reserved Event(信用預留)”事件
- 訂單服務消費Credit Reserved Event,改變訂單的狀態為OPEN
更複雜的情境可以引入更多步驟,例如在檢查使用者信用的同時預留庫存等。
考慮到(a)每個服務原子性更新資料庫和發布事件,然後,(b)訊息代理確保事件傳遞至少一次,然後可以跨多個服務完成業務交易(此交易不是ACID交易)。這種模式提供弱確定性,例如最終一致性 eventual consistency。這種交易類型被稱作 BASE model。
亦可以使用事件來維護不同微服務擁有資料預串連(pre-join)的實現視圖。維護此視圖的服務訂閱相關事件並且更新視圖。例如,客戶訂單視圖更新服務(維護客戶訂單視圖)會訂閱由客戶服務和訂單服務發布的事件。
當客戶訂單視圖更新服務收到客戶或者訂單事件,就會更新 客戶訂單視圖資料集。可以使用文檔資料庫(例如MongoDB)來實現客戶訂單視圖,為每個使用者儲存一個文檔。客戶訂單視圖查詢服務負責響應對客戶以及最近訂單(通過查詢客戶訂單視圖資料集)的查詢。
事件驅動架構也是既有優點也有缺點,此架構可以使得交易跨多個服務且提供最終一致性,並且可以使應用維護最終視圖;而缺點在於編程模式比ACID交易模式更加複雜:為了從應用程式層級失效中恢複,還需要完成補償性交易,例如,如果信用檢查不成功則必須取消訂單;另外,應用必須應對不一致的資料,這是因為臨時(in-flight)交易造成的改變是可見的,另外當應用讀取未更新的最終視圖時也會遇見資料不一致問題。另外一個缺點在於訂閱者必須檢測和忽略冗餘事件。
1.3 原子操作Achieving Atomicity
事件驅動架構還會碰到資料庫更新和發布事件原子性問題。例如,訂單服務必須向ORDER表插入一行,然後發布Order Created event,這兩個操作需要原子性。如果更新資料庫後,服務癱了(crashes)造成事件未能發布,系統變成不一致狀態。確保原子操作的標準方式是使用一個分布式交易,其中包括資料庫和訊息代理。然而,基於以上描述的CAP理論,這卻並不是我們想要的。
1.3.1 使用本地交易發布事件
獲得原子性的一個方法是對發布事件應用採用multi-step process involving only local transactions,技巧在於一個EVENT表,此表在儲存業務實體資料庫中起到訊息列表功能。應用發起一個(本地)資料庫交易,更新業務實體狀態,向EVENT表中插入一個事件,然後提交此次交易。另外一個獨立應用進程或者線程查詢此EVENT表,向訊息代理髮布事件,然後使用本地交易標誌此事件為發行,如所示:
訂單服務向ORDER表插入一行,然後向EVENT表中插入Order Created event,事件發布線程或者進程查詢EVENT表,請求未發布事件,發布他們,然後更新EVENT表標誌此事件為發行。
此方法也是優缺點都有。優點是可以確保事件發布不依賴於2PC,應用發布業務層級事件而不需要推斷他們發生了什麼;而缺點在於此方法由於開發人員必須牢記發布事件,因此有可能出現錯誤。另外此方法對於某些使用NoSQL資料庫的應用是個挑戰,因為NoSQL本身交易和查詢能力有限。
此方法因為應用採用了本地交易更新狀態和發布事件而不需要2PC,現在再看看另外一種應用簡單更新狀態獲得原子性的方法。
1.3.2 挖掘資料庫交易日誌
另外一種不需要2PC而獲得線程或者進程發布事件原子性的方式就是挖掘資料庫交易或者提交日誌。應用程式更新資料庫,在資料庫交易日誌中產生變化,交易日誌挖掘進程或者線程讀這些交易日誌,將日誌發布給訊息代理。如所見:
此方法的例子如LinkedIn Databus 項目,Databus 挖掘Oracle交易日誌,根據變化發布事件,LinkedIn使用Databus來保證系統內各記錄之間的一致性。
另外的例子如:AWS的 streams mechanism in AWS DynamoDB,是一個可管理的NoSQL資料庫,一個DynamoDB流是由過去24小時對資料庫表基於時序的變化(建立,更新和刪除操作),應用可以從流中讀取這些變化,然後以事件方式發布這些變化。
交易日誌挖掘也是優缺點並存。優點是確保每次更新發布事件不依賴於2PC。交易日誌挖掘可以通過將發布事件和應用商務邏輯分離開得到簡化;而主要缺點在於交易日誌對不同資料庫有不同格式,甚至不同資料庫版本也有不同格式;而且很難從底層交易日誌更新記錄轉換為高層業務事件。
交易日誌挖掘方法通過應用直接更新資料庫而不需要2PC介入。下面我們再看一種完全不同的方法:不需要更新只依賴事件的方法。
1.3.3 使用事件來源
Event sourcing (事件來源)通過使用根本不同的事件中心方式來獲得不需2PC的原子性,保證業務實體的一致性。 這種應用儲存業務實體一系列狀態改變事件,而不是儲存實體現在的狀態。應用可以通過重放事件來重建實體現在狀態。只要業務實體發生變化,新事件就會添加到時間表中。因為儲存事件是單一操作,因此肯定是原子性的。
為了理解事件來源工作方式,考慮事件實體作為一個例子。傳統方式中,每個訂單映射為ORDER表中一行,例如在ORDER_LINE_ITEM表中。但是對於事件來源方式,訂單服務以事件狀態改變方式儲存一個訂單:建立的,已獲批准的,已發貨的,取消的;每個事件包括足夠資料來重建訂單狀態。
事件是長期儲存在事件數目據庫中,提供API添加和擷取實體事件。事件儲存跟之前描述的訊息代理類似,提供API來訂閱事件。事件儲存將事件遞送到所有感興趣的訂閱者,事件儲存是事件驅動微服務架構的基幹。
事件來源方法有很多優點:解決了事件驅動架構關鍵問題,使得只要有狀態變化就可以可靠地發布事件,也就解決了微服務架構中資料一致性問題。另外,因為是持久化事件而不是對象,也就避免了object relational impedance mismatch problem。
資料來源方法提供了100%可靠的業務實體變化監控日誌,使得擷取任何時點實體狀態成為可能。另外,事件來源方法可以使得商務邏輯可以由事件交換的松耦合業務實體構成。這些優勢使得單體應用移植到微服務架構變的相對容易。
事件來源方法也有不少缺點,因為採用不同或者不太熟悉的變成模式,使得重新學習不太容易;事件儲存只支援主鍵查詢業務實體,必須使用 Command Query Responsibility Segregation (CQRS) 來完成查詢業務,因此,應用必須處理最終一致資料。
1.4 總結
在微服務架構中,每個微服務都有自己私人的資料集。不同微服務可能使用不同的SQL或者NoSQL資料庫。儘管資料庫結構描述有很強的優勢,但是也面對資料分布式管理的挑戰。第一個挑戰就是如何在多服務之間維護業務交易一致性;第二個挑戰是如何從多服務環境中擷取一致性資料。
最佳解決辦法是採用事件驅動架構。其中碰到的一個挑戰是如何原子性的更新狀態和發布事件。有幾種方法可以解決此問題,包括將資料庫視為訊息佇列、交易日誌挖掘和事件來源。
在未來的部落格中,將會跟深入探討微服務的其他方面。
本系列七篇文章中其它幾篇連結如下:
- Introduction to Microservices: http://dockone.io/article/394
- Building Microservices: Using an API Gateway: http://dockone.io/article/482
- Building Microservices: Inter-Process Communication in a Microservices Architecture: http://dockone.io/article/549
- Service Discovery in a Microservices Architecture: http://dockone.io/article/771
原文連結:Event-Driven Data Management for Microservices(翻譯:楊峰)