單體應用一般使用單個關係型數據庫,由此帶來的好處在於應用可以使用 ACID 事務,後者提供了重要的操做特性:nginx
- 原子化:原子粒度的更改
- 一致性:數據庫的狀態始終保持一致
- 隔離:併發執行的事務顯示爲串行執行
- 持久:事務一旦提交就不會被撤銷
如此,應用可以簡單地開始事務、更改(插入、更新和刪除)多行、以及提交事務。數據庫
使用關係型數據庫的另外一大好處是它支持 SQL。SQL 是一門豐富、可聲明的和標準化的查詢預定。用戶可以輕鬆經過查詢將多個表中的數據組合起來,而後 RDBMS 查詢調度器決定執行查詢的最優方法。用戶沒必要關心底層細節,好比如何訪問數據庫。此外,因爲全部的應用數據在一個數據庫中,很容易查詢。編程
然而,微服務架構中的數據訪問變得複雜許多。每一個微服務擁有的數據專門用於該微服務,僅經過其 API 訪問。這種數據封裝保證了微服務鬆散耦合,而且能夠獨立更新。但若是多個服務訪問相同數據,架構更新會耗費時間、也須要全部服務的協調更新。架構
更糟糕的是,不一樣的微服務一般使用不一樣類型的數據庫。現代應用存儲和處理各類類型的數據,而關係型數據庫並不是老是好選擇。對於一些使用場景,特定的 NoSQL 數據庫能提供更方便的數據模型、更好的性能和可擴展性。譬如,服務使用 Elasticsearch 這樣的文本搜索引擎來存儲和查詢文本;一樣地,存儲社交圖譜數據的服務可能須要使用 Neo4j 這樣的圖譜數據庫。所以,基於微服務的應用一般會混合使用 SQL 和 NoSQL 數據庫,即多語言留存(polyglot persistence approach)。併發
分區的、多語言留存的架構對於數據存儲有不少好處,包括服務的鬆耦合、更好的性能和可擴展性。然而,它也確實給分佈式數據管理帶來了挑戰。app
第一個挑戰就是如何實現業務邏輯,保持多種服務的一致性。爲了說明爲什麼這是一個問題,咱們以在線 B2B 商店爲例。Customer Service(下文使用客戶服務)維護與用戶有關的信息,包括信用信息。Order Service(下文使用訂單服務)管理訂單,驗證新訂單沒有超出用戶的信用額度。在單體應用裏,訂單服務能夠簡單地使用 ACID 事務來覈對提供的信用信息和建立訂單。分佈式
相反,在微服務架構中,以下圖所示,訂單表和客戶表爲各自對應的服務私有。微服務
訂單服務沒法直接訪問客戶表,只能經過客戶服務提供的 API。訂購服務可能使用分佈式事務,也被稱爲兩步提交(2PC)。然而,2PC 一般不是現代應用的可行選項。CAP 定理須要用戶在可用性和 ACID 風格的一致性中二選一,一般可用性是更好的選擇。此外,許多現代技術,譬如大多數 NoSQL 數據庫並不支持 2PC。維護整個服務和數據庫中的數據一致性是相當重要的,所以咱們須要另外一種解決方案。性能
第二個挑戰就是如何實現檢索多個服務數據的查詢。假設應用須要顯示一位客戶和他的最近的訂單。若是訂單服務爲檢索客戶訂單提供了 API,那麼可使用應用端獲取該數據。應用經過客戶服務檢索該客戶,經過訂單服務檢索該顧客的訂單。可是假如訂單服務只支持經過訂單主鍵查詢訂單(可能使用僅支持鍵值檢索的 NoSQL 數據庫),這種狀況下,就沒有合適的方法來檢索所需數據。學習
對於許多應用,解決方案就是事件驅動的架構。在這一架構裏,當有顯著事件發生時,譬如更新業務實體,某個微服務會發布事件,其它微服務則訂閱這些事件。當某一微服務接收到事件就能夠更新本身的業務實體,實現更多事件被髮布。
用戶可以使用事件來實現跨多個服務的業務邏輯。事務由一系列步驟組成,每一步都有一個微服務更新業務實體,而後發佈觸發下一步的事件。下面的系列圖展現瞭如何使用事件驅動的方法在建立訂單時檢查可用信用。微服務經過消息代理來交換事件。
1. 訂單服務建立狀態爲 NEW 的訂單,併發布「訂單已建立」事件。
2. 客戶服務獲取「訂單已建立」事件,爲此訂單保留信用,發佈「信用保留」事件。
3. 訂單服務獲取「信用保留」事件,把訂單狀態修改成 OPEN。
更爲複雜的場景可能涉及更多的步驟,好比在覈對客戶信用的同時預留庫存。
基於(a)每一個服務自動更新數據庫和發佈事件,以及(b)消息代理確保事件傳遞至少一次,用戶可以跨多個服務完成業務邏輯。注意它們並不是 ACID 業務。這種模式提供弱肯定性,好比最終一致性。這種事務模型也被稱做 BASE 模型。
用戶也可使用事件來維護不一樣微服務擁有的預鏈接數據的物化視圖。維護此視圖的服務訂閱相關事件,並更新視圖。例如,維護客戶訂單視圖的客戶訂單視圖更新服務會訂閱由客戶服務和訂單服務發佈的事件。
當客戶訂單查看更新服務收到客戶或者訂單事件,就會更新客戶訂單查看的數據存儲。用戶可以使用相似 MongoDB 的文檔數據庫查看用戶訂單,併爲每位客戶存儲一個文檔。用戶訂單預覽查詢服務經過客戶訂單預覽數據存儲,處理來自客戶和最近訂單的請求。
事件驅動的架構有優勢也有缺點。它使得事務跨多個服務並提供最終一致性,也可讓應用維護物化視圖。缺點之一在於,它的編程模型要比使用 ACID 事務的更加複雜。爲了從應用級別的失效中恢復,還須要完成補償性事務,例如,若是信用檢查不成功則必須取消訂單。此外,因爲臨時事務形成的改變顯而易見,於是應用必須處理不一致的數據。此外,若是應用從物化視圖中讀取的數據沒有更新時,也會遇到不一致的問題。此架構的另外一缺點就是用戶必須檢測並忽略重複事件。
事件驅動的架構還存在以原子粒度更新數據庫併發布事件的問題。例如,訂單服務必須在訂單表中插入一行,而後發佈「訂單已建立」事件。這兩個操做須要原子化實現。若是服務在更新數據庫以後、發佈事件以前崩潰,系統變得不一致。確保原子化的標準作法是使用包含數據庫和消息代理的分佈式事務。然而,基於以上描述的 CAP 理論,這並不是咱們所想。
實現原子化的方法是使用多步驟進程來發布事件,該進程只包含本地事務。訣竅就是在存儲業務實體狀態的數據庫中,有一個事件表來充當消息隊列。應用啓動一個(本地)數據庫事務,更新業務實體的狀態,在事件表中插入一個事件,並提交該事務。獨立的應用線程或進程查詢事件表,將事件發不到消息代理,而後使用本地事務標註事件併發布。下圖展現了這一設計。
訂單服務在訂單表中插入一行,而後在事件表中插入「訂單已建立」的事件。時間發佈線程或進程在事件表中查詢未發佈的事件併發布,而後更新事件表,將該事件標記爲已發佈。
這種方法優缺點兼具。優勢之一是保證每一個更新都有對應的事件發佈,而且無需依賴 2PC。此外,應用發佈業務級別的事件,消除了推斷事件的須要。這種方法也有缺點。因爲開發者必須牢記發佈事件,所以有很大可能出錯。此外這一方法對於某些使用 NoSQL 數據庫的應用是個挑戰,由於 NoSQL 自己交易和查詢能力有限。
經過此方法,應用使用本地事務來更新狀態和發佈事件,排除了對 2PC 的須要。接下來,咱們瞭解使用應用更新狀態實現原子化的方法。
無需 2PC 實現原子化的另外一種方式是由線程或者進程經過挖掘數據庫事務或提交日誌來發布事件。應用更新數據庫,數據庫的事務日誌記錄這些變動。事務日誌挖掘線程或進程讀取這些日誌,並把事件發佈到消息代理。以下圖所示:
這一方法的範例是開源的 LinkedIn Databus 項目。Databus 挖掘 Oracle 事務日誌併發布與之對應的事件。LinkedIn 使用 Databus 維持各類來源的數據存儲與記錄系統一致。
另外一個範例則是 AWS DynamoDB 採用的流機制,AWS DynamoDB 是一個可管理的 NoSQL 數據庫。每一個 DynamoDB 流包括 DynamoDB 表在過去 24 小時以內的時序變化,包括建立、更新和刪除操做。應用可以讀取這些變動,將其做爲事件發佈。
事務日誌挖掘具備多個優勢。首先,它能保證無需使用 2PC 就能針對每一個更新發布事件。其次,經過將日誌發佈於應用的業務邏輯分離,事務日誌挖掘可以簡化應用。事務日誌挖掘也有缺點,主要缺點就是事務日誌的格式與每一個數據庫對應,甚至隨着數據庫版本而變化。此外,很難從底層事務日誌更新記錄中逆向工程這些業務事件。
經過讓應用更新數據庫,事務日誌挖掘消除了對 2PC 的須要。接下來咱們會討論另外一種方法——消除更新,只依賴事件。
經過採用一種大相徑庭的、以事件爲中心的方法來留存業務實體,事件源無需 2PC 實現了原子化。不一樣於存儲實體的當前狀態,應用存儲狀態改變的事件序列。應用經過重播事件來重構實體的當前狀態。每當業務實體的狀態改變,新事件就被附加到事件列表。鑑於保存事件是一個單一的操做,本質上也是原子化的。
要了解事件源如何運行,能夠以訂單實體爲例。在傳統的方法中,每一個訂單映射爲訂單表的一行,例如一個 ORDERLINEITEM 表。使用事件源的時候,訂單服務以狀態更改事件的方式存儲訂單,包括已建立、已批准、已發貨、已取消等。每一個事件都包含足夠的數據去重建訂單狀態。
事件長期保存在事件數據庫,使用 API 添加和檢索實體的事件。事件存儲相似上文說起的消息代理,經過 API 讓服務訂閱事件,將全部事件傳達到全部感興趣的訂閱者。事件存儲是事件驅動的微服務架構的支柱。
事件源有很多優勢。它解決了實施事件驅動的微服務架構時的一個關鍵問題,可以只要狀態改變就可靠地發佈事件。另外,它也解決了微服務架構中的數據一致性問題。因爲儲存事件而不是域對象,它也避免了對象關係抗阻不匹配的問題(object‑relational impedance mismatch problem)。事件源提供了 100% 可靠的業務實體變化的審計日誌,使得獲取任什麼時候間點的實體狀態成爲可能。事件源的另外一大優點在於業務邏輯由鬆耦合的、事件交換的業務實體構成,便於從單體應用向微服務架構遷移。
事件源也有缺點。因爲採用了不一樣或不熟悉的編程風格,會有學習曲線。事件存儲只直接支持經過主鍵查詢業務實體,用戶還須要使用 Command Query Responsibility Segregation (CQRS) 來完成查詢。所以,應用必須處理最終一致的數據。
在微服務架構中,每一個微服務都有其私有數據存儲,不一樣的微服務可能使用不一樣的 SQL 和 NoSQL 數據庫。這些數據庫架構帶來便利的同時,也給分佈式數據管理帶來挑戰。第一個挑戰就是如何實現業務事務,保持多個服務的一致性。第二個挑戰就是如何從多個服務中檢索數據,實現查詢。
對於許多應用,解決方案就是使用事件驅動的架構。事件驅動的架構帶來的挑戰是如何原子化地更新狀態和發佈事件。有幾個方法能夠作到這一點,包括把數據庫用做消息隊列、事務日誌挖掘和事件源。
下期:選擇微服務部署策略 ,敬請期待!