分佈式系統服務框架Zookeeper介紹與原理實現

分佈式數據管理之痛點

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

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

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

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

在單體應用中,訂單服務只需要使用傳統事務交易就可以一次性檢查可用信用和創建訂單。

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

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

這裏存在兩個挑戰,第一個挑戰是2PC除要求數據庫本身支持外,還要求服務的數據庫類型需要保持一致。

但是現在的微服務架構中,每個服務的數據庫類型可能是不一樣的,有的可能是MySQL數據庫,有的也可能是NoSQL數據庫;

第二個挑戰是如何實現從多個服務中查詢數據。假設應用程序需要顯示一個客戶和他最近的訂單。如果訂單服務提供用於檢索客戶訂單的API,那麼應用程序端可以通過JOIN方式來檢索此數據,即應用程序首選從客戶服務檢索客戶,並從訂單服務檢索客戶的訂單。

然而,如果訂單服務僅支持通過其主鍵查找訂單(也許它使用僅支持基於主鍵的檢索的NoSQL數據庫), 在這種情況下,就沒有方法來檢索查詢所需的數據。

爲解決這兩大痛點,就需要我們使用到分步式數據管理了。

分佈式數據管理之舉措

在介紹分佈式數據管理(CRUD)解決方案之前,有必要介紹下CAP原理和最終一致性相關概念。

CAP原理和最終一致性

CAP原理(CAP Theorem)

在足球比賽裏,一個球員在一場比賽中進三個球,稱之爲帽子戲法(Hat-trick)。在分佈式數據系統中,也有一個帽子原理(CAP Theorem),不過此帽子非彼帽子。CAP原理中,有三個要素:

1)一致性(C onsistency)

2)可用性(A vailability)

3)分區容忍性(P artition tolerance)

CAP原理指的是,這三個要素最多隻能同時實現兩點,不可能三者兼顧。

因此在進行分佈式架構設計時,必須做出取捨。而對於分佈式數據系統,分區容忍性是基本要求 ,否則就失去了價值,因此設計分佈式數據系統,就是在一致性和可用性之間取一個平衡。

對於大多數web應 用,其實並不需要強一致性,因此犧牲一致性而換取高可用性,是目前多數分佈式數據庫產品的方向。

當然,犧牲一致性,並不是完全不管數據的一致性,否則數據是混亂的,那麼系統可用性再高分佈式再好也沒有了價值。

犧牲一致性,只是不再要求關係型數 據庫中的強一致性,而是隻要系統能達到最終一致性即可,考慮到客戶體驗,這個最終一致的時間窗口,要儘可能的對用戶透明,也就是需要保障“用戶感知到的一致性”。

通常是通過數據的多份異步複製來實現系統的高可用和數據的最終一致性的,“用戶感知到的一致性”的時間窗口則 取決於數據複製到一致狀態的時間。

最終一致性(eventually consistent)

對於一致性,可以分爲從客戶端和服務端兩個不同的視角。

從客戶端來看,一致性主要指的是多併發訪問時更新過的數據如何獲取的問題。

從服務端來看,則是更新如何複製分佈到整個系統,以保證數據最終一致。

一致性是因爲有併發讀寫纔有的問題,因此在理解一致性的問題時,一定要注意結合考慮併發讀寫的場景。

從客戶端角度,多進程併發訪問時,更新過的數據在不同進程如何獲取的不同策略,決定了不同的一致性。

對於關係型數據庫,要求更新過的數據能被後續的 訪問都能看到,這是強一致性 ;如果能容忍後續的部分或者全部訪問不到,則是弱一致性 ; 如果經過一段時間後要求能訪問到更新後的數據,則是最終一致性。

從服務端角度,如何儘快將更新後的數據分佈到整個系統,降低達到最終一致性的時間窗口,是提高系統的可用度和用戶體驗非常重要的方面。

那麼問題來了,如何實現數據的最終一致性呢?答案就在事件驅動架構。

事件驅動架構簡介

Chris Richardson作爲微服務架構設計領域的權威,給出了分佈式數據管理的最佳解決方案。

對於大多數應用而言,要實現微服務的分佈式數據管理,需要採用事件驅動架構(event-driven architecture)。

在事件驅動架構中,當某件重要事情發生時,微服務會發佈一個事件,例如更新一個業務實體。

當訂閱這些事件的微服務接收此事件時,就可以更新自己的業務實體,也可能會引發更多的事件發佈,讓其他相關服務進行數據更新,最終實現分佈式數據最終一致性。

可以使用事件來實現跨多服務的業務交易。交易一般由一系列步驟構成,每一步驟都由一個更新業務實體的微服務和發佈激活下一步驟的事件構成。

事件驅動示例1

下圖展現如何使用事件驅動方法,在創建訂單時檢查信用可用度,微服務之間通過消息代理(Messsage Broker)來交換事件。

1. 訂單服務創建一個帶有NEW狀態的Order (訂單),發佈了一個“Order Created Event(創建訂單)”的事件。

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

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

 

事件驅動示例2

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

1. 訂單服務創建一個待支付的訂單,發佈一個“創建訂單”的事件。

2. 支付服務消費“創建訂單”事件,支付完成後發佈一個“支付完成”事件。

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

事件驅動架構之分佈式數據更新

上節通過示例概要介紹了通過事件驅動方式,實現了分佈式數據最終一致性保證。縱觀微服務架構下的事件驅動業務處理邏輯,其核心要點在於,可靠的事件投遞和避免事件的重複消費。

可靠事件投遞有以下兩個特性:

1) 每個服務原子性的完成業務操作和發佈事件;

2) 消息代理確保事件投遞至少一次(at least once);

而避免事件重複消費則要求消費事件的服務實現冪等性,比如支付服務不能因爲重複收到事件而多次支付。

BTW:當前流行的消息隊列如Kafka等,都已經實現了事件的持久化和at least once的投遞模式,所以可靠事件投遞的第二條特性已經滿足,這裏就不展開。接下來章節講重點講述如何實現可靠事件投遞的第一條特性和避免事件重複消費,即服務的業務操作和發佈事件的原子性和避免消費者重複消費事件要求服務實現冪等性。

如何實現事件投遞操作原子性?

事件驅動架構會碰到數據庫更新和發佈事件原子性問題。例如,訂單服務必須向ORDER表插入一行,然後發佈Order Created event,這兩個操作需要原子性。比如更新數據庫後,服務癱了(crashes)造成事件未能發佈,系統變成不一致狀態。那麼如何實現服務的業務操作和發佈事件的原子性呢?

1.2.3.1.1 使用本地事務發佈事件

獲得原子性的一個方法是將服務的業務操作和發佈事件放在一個本地數據庫事務裏,也就是說,需要在本地建立一個EVENT表,此表在存儲業務實體數據庫中起到消息列表功能。當應用發起一個(本地)數據庫交易,更新業務實體狀態時,會向EVENT表中插入一個事件,然後提交此次交易。另外一個獨立應用進程或者線程查詢此EVENT表,向消息代理髮布事件,然後使用本地交易標誌此事件爲已發佈,如下圖所示:

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

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

使用事件源

Event sourcing (事件源)通過使用以事件中心的數據存儲方式來保證業務實體的一致性。事件源保存了每個業務實體所有狀態變化的事件,而不是存儲實體當前的狀態。應用可以通過重放事件來重建實體現在的狀態。只要業務實體發生變化,新事件就會添加到事件表中。因爲保存事件是單一操作,因此肯定是原子性的。

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

事件源方法有很多優點:解決了事件驅動架構關鍵問題,使得業務實體更新和事件發佈原子化,但是也存在缺點,因爲是持久化事件而不是對象,導致數據查詢時,必須使用 Command Query Responsibility Segregation (CQRS) 來完成查詢業務,從開發角度看,存在一定挑戰。

如何避免事件重複消費?

要避免事件重複消費,需要消費事件的服務實現服務冪等,因爲存在重試和錯誤補償機制,不可避免的在系統中存在重複收到消息的場景,服務冪等能提高數據的一致性。在編程中,一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同,因此需要開發人員在功能設計實現時,需要特別注意服務的冪等性。

事件驅動架構之分佈式數據查詢

微服務架構下,由於分佈式數據庫的存在,導致在執行用戶業務數據查詢時,通常需要跨多個微服務數據庫進行數據查詢,也就是分佈式數據查詢。那麼問題來了,由於每個微服務的數據都是私有化的,只能通過各自的REST接口獲取,如果負責業務查詢的功能模塊,通過調用各個微服務的REST接口來分別獲取基礎數據,然後在內存中再進行業務數據拼裝後,再返回給用戶。該方法無論從程序設計或是查詢性能角度看,都不是一個很好的方法。那麼如何解決微服務架構下的分佈式數據查詢問題呢? 在給出解決方案之前,需要讀者首先了解下物化視圖和命令查詢職責分離等相關概念。


總結
想了解學習Java方面的技術以及學習進階哪方面的技術知識點等
可加羣:722040762 驗證碼:666(CSDN)歡迎大家的加入喲
微服務、分佈式、高併發、高可用,性能優化丶源碼分析等等一些技術等着你來探討學習!
歡迎關注CSDN:JAVA編程大飛哥
覺得收穫的話可以點個關注評論一波喔,謝謝大佬們支持!
最後,每一位讀到這裏的Java程序猿朋友們,感謝你們能耐心地看完。希望在成爲一名更優秀的Java程序猿的道路上,我們可以一起學習、一起進步!都能贏取白富美,走向架構師的人生巔峯!

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