微服務架構下的分散式資料管理,架構資料管理
1.1 分散式資料管理之痛點
為了確保微服務之間松耦合,每個服務都有自己的資料庫, 有的是關係型資料庫(SQL),有的是非關係型資料庫(NoSQL)。
開發企業事務往往牽涉到多個服務,要想做到多個服務資料的一致性並非易事,同樣,在多個服務之間進行資料查詢也充滿挑戰。
我們以一個線上B2B商店為例,客戶服務 包括了客戶的各種資訊,例如可用信用等。
管理訂單,提供訂單服務,則需要驗證某個新訂單與客戶的信用限制沒有衝突。
在單體應用中,訂單服務只需要使用傳統事務交易就可以一次性檢查可用信用和建立訂單。
相反微服務架構下,訂單和客戶表分別是相應服務的私人表,如所示:
訂單服務不能直接存取客戶表,只能通過客戶服務發布的API來訪問或者使用分散式交易, 也就是眾所周知的兩階段交易認可 (2PC)來訪問客戶表,2PC意義圖如下所示:
這裡存在兩個挑戰,第一個挑戰是2PC除要求資料庫本身支援外,還要求服務的資料庫類型需要保持一致。
但是現在的微服務架構中,每個服務的資料庫類型可能是不一樣的,有的可能是MySQL資料庫,有的也可能是NoSQL資料庫;
第二個挑戰是如何?從多個服務中查詢資料。假設應用程式需要顯示一個客戶和他最近的訂單。如果訂單服務提供用於檢索客戶訂單的API,那麼應用程式端可以通過JOIN方式來檢索此資料,即應用程式首選從客戶服務檢索客戶,並從訂單服務檢索客戶的訂單。
然而,如果訂單服務僅支援通過其主鍵尋找訂單(也許它使用僅支援基於主鍵的檢索的NoSQL資料庫), 在這種情況下,就沒有方法來檢索查詢所需的資料。
為解決這兩大痛點,就需要我們使用到分步式資料管理了。
1.2 分散式資料管理之舉措
在介紹分散式資料管理(CRUD)解決方案之前,有必要介紹下CAP原理和最終一致性相關概念。
1.2.1 CAP原理和最終一致性
1.2.1.1 CAP原理(CAP Theorem)
在足球比賽裡,一個球員在一場比賽中進三個球,稱之為帽子戲法(Hat-trick)。在分布式資料系統中,也有一個帽子原理(CAP Theorem),不過此帽子非彼帽子。CAP原理中,有三個要素:
1)一致性(C onsistency)
2)可用性(A vailability)
3)分區容忍性(P artition tolerance)
CAP原理指的是,這三個要素最多隻能同時實現兩點,不可能三者兼顧。
因此在進行分布式架構設計時,必須做出取捨。而對於分布式資料系統,分區容忍性是基本要求 ,否則就失去了價值,因此設計分布式資料系統,就是在一致性和可用性之間取一個平衡。
對於大多數web應 用,其實並不需要強一致性,因此犧牲一致性而換取高可用性,是目前多數分散式資料庫產品的方向。
當然,犧牲一致性,並不是完全不管資料的一致性,否則資料是混亂的,那麼系統可用性再高分布式再好也沒有了價值。
犧牲一致性,只是不再要求關係型數 據庫中的強一致性,而是只要系統能達到最終一致性即可,考慮到客戶體驗,這個最終一致的時間視窗,要儘可能的對使用者透明,也就是需要保障“使用者感知到的一致性”。
通常是通過資料的多份非同步複製來實現系統的高可用和資料的最終一致性的,“使用者感知到的一致性”的時間視窗則 取決於資料複製到一致狀態的時間。
1.2.1.2 最終一致性(eventually consistent)
對於一致性,可以分為從用戶端和服務端兩個不同的視角。
從用戶端來看,一致性主要指的是多並發訪問時更新過的資料如何擷取的問題。
從服務端來看,則是更新如何複製分布到整個系統,以保證資料最終一致。
一致性是因為有並發讀寫才有的問題,因此在理解一致性的問題時,一定要注意結合考慮並發讀寫的情境。
從用戶端角度,多進程並發訪問時,更新過的資料在不同進程如何擷取的不同策略,決定了不同的一致性。
對於關係型資料庫,要求更新過的資料能被後續的 訪問都能看到,這是強一致性 ;如果能容忍後續的部分或者全部訪問不到,則是弱一致性 ; 如果經過一段時間後要求能訪問到更新後的資料,則是最終一致性。
從服務端角度,如何儘快將更新後的資料分布到整個系統,降低達到最終一致性的時間視窗,是提高系統的可用度和使用者體驗非常重要的方面。
那麼問題來了,如何?資料的最終一致性呢?答案就在事件驅動架構。
1.2.2 事件驅動架構簡介
Chris Richardson作為微服務架構設計領域的權威,給出了分散式資料管理的最佳解決方案。
對於大多數應用而言,要實現微服務的分散式資料管理,需要採用事件驅動架構(event-driven architecture)。
在事件驅動架構中,當某件重要事情發生時,微服務會發布一個事件,例如更新一個業務實體。
當訂閱這些事件的微服務接收此事件時,就可以更新自己的業務實體,也可能會引發更多的事件發布,讓其他相關服務進行資料更新,最終實現分布式資料最終一致性。
可以使用事件來實現跨多服務的業務交易。交易一般由一系列步驟構成,每一步驟都由一個更新業務實體的微服務和發布啟用下一步驟的事件構成。
1.2.2.1 事件驅動樣本1
展現如何使用事件驅動方法,在建立訂單時檢查信用可用度,微服務之間通過訊息代理(Messsage Broker)來交換事件。
1. 訂單服務建立一個帶有NEW狀態的Order (訂單),發布了一個“Order Created Event(建立訂單)”的事件。
2. 客戶服務 消費Order Created Event事件,為此訂單預留信用,發布“Credit Reserved Event(信用預留)”事件。
3. 訂單服務消費Credit Reserved Event,改變訂單的狀態為OPEN。
1.2.2.2 事件驅動樣本2
展現如何使用事件驅動方法,在建立訂單時觸發支付業務的資料更新,微服務之間通過訊息代理(Messsage Broker)來交換事件。
1. 訂單服務建立一個待支付的訂單,發布一個“建立訂單”的事件。
2. 支付服務消費“建立訂單”事件,支付完成後發布一個“支付完成”事件。
3. 訂單服務消費“支付完成”事件,訂單狀態更新為待出庫。
1.2.3 事件驅動架構之分布式資料更新
上節通過樣本概要介紹了通過事件驅動方式,實現了分布式資料最終一致性保證。縱觀微服務架構下的事件驅動業務處理邏輯,其核心要點在於,可靠的事件投遞和避免事件的重複消費。
可靠事件投遞有以下兩個特性:
1) 每個服務原子性的完成業務操作和發布事件;
2) 訊息代理確保事件投遞至少一次(at least once);
而避免事件重複消費則要求消費事件的服務實現等冪性,比如支付服務不能因為重複收到事件而多次支付。
BTW:當前流行的訊息佇列如Kafka等,都已經實現了事件的持久化和at least once的投遞模式,所以可靠事件投遞的第二條特性已經滿足,這裡就不展開。接下來章節講重點講述如何?可靠事件投遞的第一條特性和避免事件重複消費,即服務的業務操作和發布事件的原子性和避免消費者重複消費事件要求服務實現等冪性。
1.2.3.1 如何?事件投遞操作原子性?
事件驅動架構會碰到資料庫更新和發布事件原子性問題。例如,訂單服務必須向ORDER表插入一行,然後發布Order Created event,這兩個操作需要原子性。比如更新資料庫後,服務癱了(crashes)造成事件未能發布,系統變成不一致狀態。那麼如何?服務的業務操作和發布事件的原子性呢?
1.2.3.1.1 使用本地事務發布事件
獲得原子性的一個方法是將服務的業務操作和發布事件放在一個本機資料庫事務裡,也就是說,需要在本地建立一個EVENT表,此表在儲存業務實體資料庫中起到訊息列表功能。當應用發起一個(本地)資料庫交易,更新業務實體狀態時,會向EVENT表中插入一個事件,然後提交此次交易。另外一個獨立應用進程或者線程查詢此EVENT表,向訊息代理髮布事件,然後使用本地交易標誌此事件為發行,如所示:
訂單服務向ORDER表插入一行,然後向EVENT表中插入Order Created event,事件發布線程或者進程查詢EVENT表,請求未發布事件,發布他們,然後更新EVENT表標誌此事件為發行。
此方法也是優缺點都有。優點是可以確保事件發布不依賴於2PC,應用發布業務層級事件而不需要推斷他們發生了什麼;而缺點在於此方法由於開發人員必須牢記發布事件,因此有可能出現錯誤。
1.2.3.1.2 使用事件來源
Event sourcing (事件來源)通過使用以事件中心的資料存放區方式來保證業務實體的一致性。事件來源儲存了每個業務實體所有狀態變化的事件,而不是儲存實體當前的狀態。應用可以通過重放事件來重建實體現在的狀態。只要業務實體發生變化,新事件就會添加到事件表中。因為儲存事件是單一操作,因此肯定是原子性的。
為了理解事件來源工作方式,考慮以事件實體作為一個例子說明。傳統方式中,每個訂單映射為ORDER表中一行。但是對於事件來源方式,訂單服務以事件狀態改變方式儲存一個訂單:建立的,已獲批准的,已發貨的,取消的;每個事件包括足夠資訊來重建訂單的狀態。
事件來源方法有很多優點:解決了事件驅動架構關鍵問題,使得業務實體更新和事件發布原子化,但是也存在缺點,因為是持久化事件而不是對象,導致資料查詢時,必須使用 Command Query Responsibility Segregation (CQRS) 來完成查詢業務,從開發角度看,存在一定挑戰。
1.2.3.2 如何避免事件重複消費?
要避免事件重複消費,需要消費事件的服務實現服務等冪,因為存在重試和錯誤補償機制,不可避免的在系統中存在重複收到訊息的情境,服務等冪能提高資料的一致性。在編程中,一個等冪操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同,因此需要開發人員在功能設計實現時,需要特別注意服務的等冪性。
1.2.4 事件驅動架構之分布式資料查詢
微服務架構下,由於分散式資料庫的存在,導致在執行使用者業務資料查詢時,通常需要跨多個微服務資料庫進行資料查詢,也就是分布式資料查詢。那麼問題來了,由於每個微服務的資料都是私人化的,只能通過各自的REST介面擷取,如果負責業務查詢的功能模組,通過調用各個微服務的REST介面來分別擷取基礎資料,然後在記憶體中再進行業務資料拼裝後,再返回給使用者。該方法無論從程式設計或是查詢效能角度看,都不是一個很好的方法。那麼如何解決微服務架構下的分布式資料查詢問題呢? 在給出解決方案之前,需要讀者首先瞭解下物化視圖和命令查詢職責分離等相關概念。
1.2.4.1 什麼是物化視圖(merialized views)?
物化視圖是包括一個查詢結果的資料庫對像,它是遠端資料的的本機複本,或者用來產生基於資料表求和的匯總表。物化視圖儲存基於遠端資料表的資料,也可以稱為快照。這個基本上就說出了物化視圖的本質,它是一組查詢的結果,這樣勢必為將來再次需要這組資料時大大提高查詢效能。物化視圖有兩種重新整理模式ON DEMAND和ON COMMIT,使用者可根據實際情況進行設定。
物化視圖對於應用程式層是透明的,不需要有任何的改動,終端使用者甚至都感覺不到底層是用的物化視圖。總之,使用物化視圖的目的一個是提高查詢效能,另一個是由於物化視圖包含的資料是遠端資料庫的資料快照或拷貝,微服務可通過物化視圖和命令查詢職責分離(CQRS)技術(參見以下章節)實現分布式資料查詢。
1.2.4.2 什麼是命令查詢職責分離(CQRS)?
在常用的單體應用架構中,通常都是通過資料訪問層來修改或者查詢資料,一般修改和查詢使用的是相同的實體。在一些商務邏輯簡單的系統中可能沒有什麼問題,但是隨著系統邏輯變得複雜,使用者增多,這種設計就會出現一些效能問題;另外更重要的是,在微服務架構下,通常需要跨多個微服務資料庫來查詢資料,此時,我們可藉助命令查詢職責分離(CQRS)來有效解決這些問題。
CQRS使用分離的介面將資料查詢操作(Queries)和資料修改操作(Commands)分離開來,這也意味著在查詢和更新過程中使用的資料模型也是不一樣的。這樣讀和寫邏輯就隔離開來了。使用CQRS分離了讀寫職責之後,可以對資料進行讀寫分離操作來改進效能,同時提高可擴充性和安全。如:
主要資料庫處理CUD,從庫處理R,從庫的的結構可以和主庫的結構完全一樣,也可以不一樣,從庫主要用來進行唯讀查詢操作。在數量上從庫的個數也可以根據查詢的規模進行擴充,在商務邏輯上,也可以根據專題從主庫中劃分出不同的從庫。從庫也可以實現成ReportingDatabase,根據查詢的業務需求,從主庫中抽取一些必要的資料產生一系列查詢報表來儲存。
使用ReportingDatabase的一些優點通常可以使得查詢變得更加簡單高效:
· ReportingDatabase的結構和資料表會針對常用的查詢請求進行設計。
· ReportingDatabase資料庫通常會去正規化,儲存一些冗餘而減少必要的Join等聯集查詢操作,使得查詢簡化和高效,一些在主要資料庫中用不到的資料資訊,在ReportingDatabase可以不用儲存。
· 可以對ReportingDatabase重構最佳化,而不用去改變操作資料庫。
· 對ReportingDatabase資料庫的查詢不會給操作資料庫帶來任何壓力。
· 可以針對不同的查詢請求建立不同的ReportingDatabase庫。
1.2.4.3 如何?事件驅動架構下的資料查詢服務?
事件驅動不僅可以用於分布式資料一致性保證,還可以藉助物化視圖和命令查詢職責分離技術,使用事件來維護不同微服務擁有資料預串連(pre-join)的物化視圖,從而實現微服務架構下的分布式資料查詢。維護物化視圖的服務訂閱了相關事件並在事件發生時更新物化視圖。例如,客戶訂單視圖更新服務(維護客戶訂單視圖)會訂閱由客戶服務和訂單服務發布的事件(您還可以使用事件來維護由多個微服務擁有的資料群組成的物化視圖。 維護該視圖的服務訂閱了相關事件來觸發更新該物化視圖)。
例如中間的 “客戶訂單視圖更新”服務,主要負責客戶訂單視圖的更新。該服務訂閱了客戶服務和訂單服務發布的事件。當“客戶訂單視圖更新”服務收到了左側的客戶或者訂單更新事件,則會觸發更新客戶訂單物化視圖資料集。這裡可以使用文檔資料庫(例如MongoDB)來實現客戶訂單視圖,為每個使用者儲存一個文檔。而右側的客戶訂單視圖查詢服務負責響應對客戶以及最近訂單(通過查詢客戶訂單視圖資料集)的查詢。
總之,所示商務邏輯,用到了事件驅動、物化視圖和命令查詢職責分離等技術,有效解決了微服務架構下分布式資料查詢的問題。
1.2.5 事件驅動架構優缺點
事件驅動架構既有優點也有缺點,此架構可以實現跨多個服務的事務實現,且提供最終資料一致性,並且使得服務能夠自動維護查詢檢視;而缺點在於編程模式比傳統基於事務的交易模式更加複雜,必須實現補償事務以便從應用程式級故障中恢複,例如,如果信用檢查不成功則必須取消訂單;另外,應用必須應對不一致的資料,比如當應用讀取未更新的最終視圖時也會遇見資料不一致問題。另外一個缺點在於訂閱者必須檢測和忽略冗餘事件,避免事件重複消費。
1.3 總結
在微服務架構中,每個微服務都有自己私人的資料集。不同微服務可能使用不同的SQL或者NoSQL資料庫。儘管資料庫結構描述有很強的優勢,但是也面對資料分布式管理的挑戰。第一個挑戰就是如何在多服務之間維護業務資料一致性;第二個挑戰是如何從多服務環境中擷取一致性資料。
最佳解決辦法是採用事件驅動架構。其中碰到的一個挑戰是如何原子性的更新狀態和發布事件。有幾種方法可以解決此問題,包括將資料庫視為訊息佇列和事件來源等。
從目前技術應用範圍和成熟度等級看,推薦使用第一種方式(本地事務發布事件),來實現事件投遞原子化,即可靠事件投遞。
需要提醒:!!!資料一致性是微服務架構設計中唯恐避之不及卻又不得不考慮的話題。通過保證事件驅動實現最終資料的一致性,此方案的優劣,也不能簡單的一言而概之,而是應該根據情境定奪,適合的才是最好的。另外,我們在對微服務進行業務劃分的時候就儘可能的避免“可能會產生一致性問題”的設計。如果這種設計過多,也許是時候考慮改改設計了。
1.4 參考資料
https://www.nginx.com/blog/event-driven-data-management-microservices/
本文連結:https://mp.weixin.qq.com/s/WYNrcBe0h_o7whRGxqsqwg