微服務架構下的分佈式數據管理

1.1 分佈式數據管理之痛點nginx

 

爲了確保微服務之間鬆耦合,每一個服務都有本身的數據庫, 有的是關係型數據庫(SQL),有的是非關係型數據庫(NoSQL)。web

開發企業事務每每牽涉到多個服務,要想作到多個服務數據的一致性並不是易事,一樣,在多個服務之間進行數據查詢也充滿挑戰。數據庫

咱們以一個在線B2B商店爲例,客戶服務 包括了客戶的各類信息,例如可用信用等。編程

管理訂單,提供訂單服務,則須要驗證某個新訂單與客戶的信用限制沒有衝突。安全

在單體應用中,訂單服務只須要使用傳統事務交易就能夠一次性檢查可用信用和建立訂單。架構

相反微服務架構下,訂單和客戶表分別是相應服務的私有表,以下圖所示:併發

 

images/mP4yrAxFc654WPwJYBxEXrB5tJpTe5Wx.png

訂單服務不能直接訪問客戶表,只能經過客戶服務發佈的API來訪問或者使用分佈式事物, 也就是衆所周知的兩階段提交 (2PC)來訪問客戶表,2PC意義圖以下所示:異步

images/rKadQKjJybnz7rYRsGSWmjhXW4yy5Pfs.png

 

這裏存在兩個挑戰,第一個挑戰是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(建立訂單)」的事件。

 

images/DxsWcPaWSFWCssX8ibMeZrj6znerracj.png

2. 客戶服務 消費Order Created Event事件,爲此訂單預留信用,發佈「Credit Reserved Event(信用預留)」事件。

images/xYMAc2hbZYSSnAG7MwRyWEPnDC8RZ7RC.png

3. 訂單服務消費Credit Reserved Event,改變訂單的狀態爲OPEN。

images/E2StaRdj4ktW8HeCAbWy4w3NyDKaWeJz.png

 

1.2.2.2 事件驅動示例2

下圖展示如何使用事件驅動方法,在建立訂單時觸發支付業務的數據更新,微服務之間經過消息代理(Messsage Broker)來交換事件。

1. 訂單服務建立一個待支付的訂單,發佈一個「建立訂單」的事件。

images/GA8wGFX55ppR5YkGdDN3rx54h8pRm3z5.png

2. 支付服務消費「建立訂單」事件,支付完成後發佈一個「支付完成」事件。

images/r7dNcQBfB2rJR4wdSQZd6AGbw7rnyGMD.png

3.  訂單服務消費「支付完成」事件,訂單狀態更新爲待出庫。

images/2QRwP2rTaTPYzfrHk8dtt82fHnennycz.png

 

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表,向消息代理髮布事件,而後使用本地交易標誌此事件爲已發佈,以下圖所示:

images/anzF4kitAwF7XDTsY6p6GiHsb4zbYjWx.png

 

訂單服務向ORDER表插入一行,而後向EVENT表中插入Order Created event,事件發佈線程或者進程查詢EVENT表,請求未發佈事件,發佈他們,而後更新EVENT表標誌此事件爲已發佈。

此方法也是優缺點都有。優勢是能夠確保事件發佈不依賴於2PC,應用發佈業務層級事件而不須要推斷他們發生了什麼;而缺點在於此方法因爲開發人員必須牢記發佈事件,所以有可能出現錯誤。

 

1.2.3.1.2 使用事件源

Event sourcing (事件源)經過使用以事件中心的數據存儲方式來保證業務實體的一致性。事件源保存了每一個業務實體全部狀態變化的事件,而不是存儲實體當前的狀態。應用能夠經過重放事件來重建實體如今的狀態。只要業務實體發生變化,新事件就會添加到事件表中。由於保存事件是單一操做,所以確定是原子性的。

爲了理解事件源工做方式,考慮以事件實體做爲一個例子說明。傳統方式中,每一個訂單映射爲ORDER表中一行。可是對於事件源方式,訂單服務以事件狀態改變方式存儲一個訂單:建立的,已批准的,已發貨的,取消的;每一個事件包括足夠信息來重建訂單的狀態。

images/dDPKAJZn6rhJZEtB8fDzPE4R6nn87HQQ.png

 

事件源方法有不少優勢:解決了事件驅動架構關鍵問題,使得業務實體更新和事件發佈原子化,可是也存在缺點,由於是持久化事件而不是對象,致使數據查詢時,必須使用 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分離了讀寫職責以後,能夠對數據進行讀寫分離操做來改進性能,同時提升可擴展性和安全。以下圖:

images/EiHw8W3ZyYPjZdRQHH4bER5WnyeThShh.png

主數據庫處理CUD,從庫處理R,從庫的的結構能夠和主庫的結構徹底同樣,也能夠不同,從庫主要用來進行只讀的查詢操做。在數量上從庫的個數也能夠根據查詢的規模進行擴展,在業務邏輯上,也能夠根據專題從主庫中劃分出不一樣的從庫。從庫也能夠實現成ReportingDatabase,根據查詢的業務需求,從主庫中抽取一些必要的數據生成一系列查詢報表來存儲。

images/w2BPbj2pac5B6P6S7GRhd2RHwS6A6bAx.png

 

使用ReportingDatabase的一些優勢一般可使得查詢變得更加簡單高效:

·  ReportingDatabase的結構和數據表會針對經常使用的查詢請求進行設計。

·  ReportingDatabase數據庫一般會去正規化,存儲一些冗餘而減小必要的Join等聯合查詢操做,使得查詢簡化和高效,一些在主數據庫中用不到的數據信息,在ReportingDatabase能夠不用存儲。

·  能夠對ReportingDatabase重構優化,而不用去改變操做數據庫。

·  對ReportingDatabase數據庫的查詢不會給操做數據庫帶來任何壓力。

·  能夠針對不一樣的查詢請求創建不一樣的ReportingDatabase庫。

 

1.2.4.3 如何實現事件驅動架構下的數據查詢服務?

事件驅動不只能夠用於分佈式數據一致性保證,還能夠藉助物化視圖和命令查詢職責分離技術,使用事件來維護不一樣微服務擁有數據預鏈接(pre-join)的物化視圖,從而實現微服務架構下的分佈式數據查詢。維護物化視圖的服務訂閱了相關事件並在事件發生時更新物化視圖。例如,客戶訂單視圖更新服務(維護客戶訂單視圖)會訂閱由客戶服務和訂單服務發佈的事件(您還可使用事件來維護由多個微服務擁有的數據組成的物化視圖。 維護該視圖的服務訂閱了相關事件來觸發更新該物化視圖)。

images/28Ae5hPd8N737NpWxfJP8SBGAdeh6xXH.png

例如上圖中間的 「客戶訂單視圖更新」服務,主要負責客戶訂單視圖的更新。該服務訂閱了客戶服務和訂單服務發佈的事件。當「客戶訂單視圖更新」服務收到了上圖左側的客戶或者訂單更新事件,則會觸發更新客戶訂單物化視圖數據集。這裏可使用文檔數據庫(例如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

 

更多參考內容:http://www.roncoo.com/article/index?title=%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1

閱讀原文http://click.aliyun.com/m/40406/

相關文章
相關標籤/搜索