當前位置:
首頁 > 最新 > 響應式微服務架構-分散式系統設計原則

響應式微服務架構-分散式系統設計原則

O』Reilly的電子書《Reactive Microservices Architecture》講述了微服務/分散式系統的一些設計原則,本文是筆者閱讀完此書後的理解。書籍地址:https://info.lightbend.com/COLL-20XX-Reactive-Microservices-Architecture-RES-LP.html。

微服務相比傳統的單體應用能夠帶來快速的響應,以小的系統產生大的影響。而隨著網路加速、磁碟成本降低、RAM成本降低、多核技術的發展、雲架構技術的爆發,微服務不再受這些客觀條件的限制,已經開始大規模的應用。

與SOA架構,微服務和它都具有相同的初衷:解耦、隔離、組合、集成、分散以及自主,但是SOA經常被誤解和誤用,尤其是使用ESB來支持對多個單體系統的協議(複雜、低效、不穩定)調用,使得系統變得非常複雜。而隨著這些年硬體以及軟體架構理念的發展,所有的系統基本都已經變成分散式架構,也帶來了很多新的挑戰。也就需要新的思路和理念來面對這些問題,其中本書所講述的響應式原則(Reactive principles)即一種解決分散式系統的思路。響應式原則也並非一個新的東西,Erlang中的Actor模型即一種響應式設計。微服務是響應式原則的一個架構設計,其借鑒了SOA架構中好的理念,並使用了現代的基礎服務設施(雲服務、自動化工具等)。


使用微服務架構最關鍵的一個原則就是將系統劃分成一個個相互隔離、無依賴的子系統,這些子系統通過定義良好的協議進行通信。其中隔離是實現彈性、可伸縮系統的前提,並且需要在服務間建立非同步通信邊界,因此要在以下兩方面進行解耦:

時間:允許並發。

空間:允許分散式和移動性,即服務能夠隨時移動。

此外,微服務還需要消除共享狀態從而最小化相互協作、聯結的成本,要盡量達到「不共享任何東西」。


隔離是微服務架構中最重要的特性。不僅僅是微服務帶來的很多優勢的基礎,也是對設計和架構影響最大的方面。如康威定律所說,它還對組織架構有非常大的影響。

系統的結構是對團隊組織架構的反映。

失敗隔離是一種與「艙壁」(船艙的隔板)相關的設計模式:隔離錯誤、失敗以防止其蔓延至所有服務,導致更大面積的失敗。

「艙壁」這種模式已經在輪船上使用了幾個世紀:創建一個個密封不漏水的空間以防止船的外殼破損或者其他泄漏。這些空間是完全互相隔離的,這樣即使一個隔離區充滿了水,也不會蔓延流到其他隔離空間中,從而使得船整體仍然能夠運作。

彈性(從失敗中恢復的能力)即依賴於這種艙壁和失敗隔離的設計,並且需要打破同步通信機制。由此,微服務一般是在邊界之間使用非同步消息傳輸,從而使得正常的業務邏輯避免對捕獲錯誤、錯誤處理的依賴。

進一步的,服務之間的隔離使得「持續交付」變得很容易,能夠隨時地部署服務而無需擔心影響正常的業務。而且隔離的單個服務很容易監控、調試、測試和部署,非常便於擴展。


上面所講的隔離是自主性的前提。只有當服務之間是完全隔離的,那麼才可能實現完全的自主,包括獨立的決策、獨立的行動以及與其他服務協調合作來解決問題。

一個自主的服務僅僅保證其對外公布的協議/API的正確性即可。如此,不僅能夠讓我們更好地了解協作的這些系統以及對他們的建模,也能夠在面對衝突、失敗狀況時,只在一個服務內進行排查、修復即可。

使用自主服務能夠給服務編排、工作流管理以及服務合作上帶來很大靈活性,同時也帶來可擴展性、可用性、運行時管理等優勢。但其付出的代價就是需要花心思在定義良好的可組合的API設計上,這個是有一定挑戰性的。


如Unix編程哲學所說:程序應該只做一件事,並且做好它。然後讓他們一起工作完成任務。這也類似於面向對象編程語言中軟體開發單一職責原則(SRP)的描述。

每一個服務都應該只有一個存在的原因,提供了一組相關的功能,業務和職責不會糅雜在一起。所有服務組織在一起整體上能夠便於擴展、具有彈性、易理解和維護。


微服務中有一個很關鍵的部分就是狀態(state),很多微服務也都是有狀態的實體,包括對狀態和行為的封裝。而在「無狀態」的設計理念下,很多服務都把自己的狀態下沉到一個大的共享資料庫中,這也是很多傳統的Web框架的做法。如此就造成了在擴展性、可用性以及數據集成上很難做好把控。而本質上,一個有著共享資料庫的微服務架構本質還是一個單體應用。

合理的方式是一個服務既然具有單一職責,那麼就應該擁有自己的狀態和持久化機制,建模成一個邊界上下文,有自己的域名和語言。這些也都是DDD(Domain-Drivern Design)裡面的技術。微服務受DDD影響很大,其中很多微服務的上下文的概念都來自於DDD。

當訪問一個服務時,也只能是客氣的請求其狀態而並不能強制其一定具有狀態。如此,每個服務都能夠通過事件溯源(Event Sourcing)和CQRS(Command Query Responsibility Segreation)自定義自己的狀態表示和實現(RDBMS、NoSQL、Time-Series、EventLog)。

去中心化的數據管理和持久化(多語言持久化)能夠帶來很多優勢。數據的存儲媒介可以根據服務自己的需要選擇,服務包括其數據都可以看做一個單獨的單元。同時並不允許一個服務直接去訪問另一個服務的資料庫,如果要訪問只能通過API(通過指定規範、策略和Code Review來保證)。

Event Log是一種消息的存儲方式。我們可以以消息進入服務的形式存儲(發送到服務的Commnds),即命令溯源(Command Sourcing)。我們也可以忽略命令,讓命令先執行對服務產生一些作用,如果觸發了狀態變更,那麼我們捕獲此次變動並用事件溯源(Event Sourcing)將此次Event存儲到EventLog中。

消息有序存儲,能夠提供服務所有的交互歷史。同時消息也保存了服務的事務,也就能夠對這些事務日誌進行查詢、審計、重放從而用於彈性伸縮、調試以及冗餘等。

命令溯源和事件溯源是不同的語義。重放命令意味著會重放其帶來的副作用。而重放事件則是執行狀態的改變。需要根據具體場景的不同選擇使用哪種溯源技術。

使用EventLog可以避免」對象關係不匹配」的問題(ORM中經常出現)。而由於其自身天然適合非同步消息傳輸,因此絕大多數情況下,Event Log是微服務中最佳的持久化模型。


微服務之間的通信的最佳機制就是消息傳輸。如上文所說,服務之間的非同步邊界能夠在時間和空間兩方面進行解耦,能夠提升整體系統的性能。

非同步非阻塞執行以及IO都是對資源的高效操作,能夠最小化訪問共享資源時的阻塞消耗(擴展性、低延遲以及高吞吐的最大障礙)。簡單的例子:如果要發起對10個服務的訪問,其中每一個請求需要耗時100ms,那麼如果使用同步模式,則完成所有請求則需要10*100=1000ms。而如果使用非同步模式,同時發起10個線程,則一共就需要100ms。

非同步消息傳輸還能夠讓我們注重網路編程的限制,而不是假裝這些限制不存在,尤其是在失敗場景下。還能夠讓我們更關注工作流以及服務間的數據流、協議、交互是怎樣進行的。

然而目前微服務的默認通信協議以REST為主,其本質是同步通信機制,比較適用於可控的服務調用或者緊耦合的服務調用上。

此外,使用非同步消息傳輸的另一個需求在於對消息的持續流處理(可能是無界的)。也是我們從「data at rest」到」data in motion」的理念的改變。之前的數據是離線被使用的,而現在的數據是被在線處理的。應用對數據變更的響應需要達到實時級別:當變動發生,需要實時進行持續的查詢、聚合併反饋給最終的應用。這個理念的形成經歷了三個主要階段:

1. 「data at rest」: 將大量數據存儲在HDFS類似的數據存儲媒介中,然後使用離線批處理技術去處理這些數據,一般會有數個小時的延遲。

2. 意識到了「data in motion」正變得越來越重要:在數秒內捕獲數據、處理數據並反饋給運行中的系統。Lambda即此時出現的一種架構: 加速層用來做實時在線計算;批處理層用來做複雜的離線處理。加速層實時處理的結果後續被批處理層的結果合併。這個模型解決了某些場景需要數據即時響應的問題,但其架構的複雜使得不容易維護。

3. 「data in motion」: 全面擁抱移動數據的概念。傳統的面向批處理的架構都在逐漸向純流處理的架構轉變。這種模型作為通信協議和持久化方案(通過Event Logging)也能夠給微服務帶來「data in motion」和流處理的能力。


如上述所講,非同步消息傳輸帶來了對時間和空間的解耦。其中,對於空間的解耦也被稱為「位置透明」:在多核或者多結點上的微服務在運行時無須改變結點即可以動態擴展的能力。這也決定了系統的彈性和移動性。要實現這些需要依賴雲計算帶來的一些特性和其「按需使用」模型。

而可定址則是說服務的地址需要是穩定的,從而可以無限地引用此服務,而無論服務目前是否可以被定位到。當服務在運行中、已經停止、被掛起、升級中、已經崩潰等等情形下,地址都應該是可用的,任意客戶端能夠隨時發送消息給一個地址。實際中,這些消息有可能進入隊列排隊、重提交、代理、日誌記錄或者進入死信隊列。此外,地址需要是虛擬的,可以代表一組實例提供的服務。

在無狀態的服務間做負載均衡:如果服務是無狀態的,那麼請求被哪一個服務實例處理都是沒任何問題的。也有很多種的路由演算法供使用,如:輪訓、廣播或者基於度量信息。

在有狀態的服務之間構建Active-Passive的冗餘設計:如果一個服務是有狀態的,那麼可以使用sticky路由演算法(同一個客戶端的請求都會發送給同一個服務實例)。冗餘一個passive實例是為了在主實例掛的時候接管上面的請求。因此,服務的每一個狀態變動都需要同步到passive實例上。

有狀態的服務的重定位:將一個服務實例從一個位置移動到另一個位置可以提高引用的本地性(讓數據和計算靠近)和資源利用率。

使用虛擬地址能夠讓服務消費方無須關心服務目前是如何配置操作的,只要知道地址即可。


一個微服務並非真正的「微服務」,一系列微服務通過通信、合作才能夠解決問題,才能組成一個完整的業務系統。實現一個服務是相對簡單的,困難的是其他基礎設施的實現:服務發現、協作、安全、冗餘、數據一致性、容錯、部署以及與其他系統的集成。


微服務架構帶來的一個很大優勢就在於它提供了一套工具,能夠利用現實,模模擬實的世界來創建系統,包括真實世界的限制和機會。

首先根據「康威定律」,微服務的部署是和現實中工程組織/部門如何工作是相適應的。此外,還需要注意的是現實不是一致的,任何事情都是相對的,即使是時間和「現在」這個概念。

信息的傳播速度不可能比光快,甚至大部分是很慢的,這也意味著信息通信是有延遲的。信息都是來自過去的,我們稍微思考一下可以知道信息承載的都是我們觀察到的東西。而我們觀察/學習到的事實至少都是很短時間之前發生的,也就是說我們總是在看過去,「現在」只是旁觀者的視角。

每一個微服務都可以看做一個安全的小島,提供了確定性和強一致性,上面的時間和「目前」都是絕對的。但是當離開一個微服務的邊界時,就進入了一片充滿非確定性的大海-分散式系統的世界。如很多人所說,構建分散式系統是困難的。但現實世界同時也提供了如何解決諸如彈性、可伸縮、隔離性等分散式問題的解決思路。因此,即使構建分散式系統是困難的,但是我們也不應該退化為單體應用,而是學習如何使用一系列的設計原則、抽象概念和工具來管理它。

正如Pat Helland在《Data on the Outside versus Data on the Inside.」》對」data on the inside」和「data on the outside」的對比所說:內部的數據就是我們本地的「目前」,而外部數據-事件即是來自過去的信息,服務之間的命令則是「對未來的希望」。


服務發現要解決的問題就是如何定位一系列的服務從而可以使用地址去調用。其中最簡單的手段就是將地址和埠信息硬編碼在所有服務中或者外置在服務的配置文件中。這種方式的問題在於其是一種靜態部署模型,與微服務的初衷是相矛盾的。

服務需要保持解耦和移動,而系統需要是彈性和動態的。因此可以通過使用控制反轉(Inversion of Control)模式引入一個間接層來解決此問題。也就是說每一個服務都上報自己的信息(位置、如何訪問)給一個統一的平台。這個平台被稱作「服務發現」,是微服務平台的一個基礎部分。這樣,一旦服務的信息被存儲了,服務就可以使用「服務註冊中心」來查找調用服務的信息,這種模式被稱作「Client-Side服務發現」。另一種策略是將信息存儲、維護在一個負載均衡器(AWS的ELB)或者直接維護在服務提供方的地址中-「Server-Side服務發現」。

可以選擇CP特性的資料庫作為服務信息的存儲,能夠保證信息的一致性。但是這種資料庫是犧牲了一定程度的可用性來達到強一致性的,並且依賴一些額外的基礎設施,而很多時候強一致性並非那麼需要。因此,更好的選擇是使用AP特性的點對點的技術來存儲,比如使用CRDTs(Conflict-Free Replicated Data Types )與Epidemic Gossip可以實現信息的最終一致性傳播,能夠有更好的彈性,也不需要額外的基礎設施。


API管理解決的問題在於如何將服務的協議和API統一管理起來,以方便服務的調用。包括協議和數據版本的升級和後退等。解決此問題可以通過引入一個負責序列化編碼、協議維護以及數據傳輸的層,甚至直接將服務版本化。這在DDD中被稱作」Anti-Corruption」層,可以加入到服務本身或者在API網關中實現。

假如一個客戶端需要調用10個服務(每一個都有不同的API)來完成一個任務,那麼對於這個客戶端來說是非常繁瑣的。相比起讓客戶端直接去調用服務,更好的方式是讓客戶端通過API網關服務來調用。API網關負責接受客戶端的請求,然後路由請求到相應的服務(如果有必要需要轉換協議),組裝響應並將其返回給客戶端。這樣,做為客戶端和服務之間的一層其就能夠簡化client-to-service協議。但這裡如果是中心化的則很難達到高可用和可擴展性,所以使用去中心化技術(比如服務發現)實現API網關則是更好的選擇。

但需要注意的是API網關,包括所有的核心出服務並不是一定要自建的,理想地它應該是底層平台的一部分。


在一個由數個微服務組成的系統中,使用點對點的通信就能完成服務間的通信工作。但是當服務數目越來越多,如果還是讓他們之間直接調用,那麼很快整個系統會變得混亂不堪。解決此問題需要一個機制能夠解耦發送者和接受者,並且能夠按照某種定義好的原則路由數據。

發布訂閱機制是一種解決方案:發布者發布信息到某個topic中,訂閱者監聽此topic以監聽消息。可以使用可擴展消息系統(Apache Kafka、Amazon Kinesis)或者NoSQL資料庫(AP特性資料庫,如Cassendra和Riak)來實現。

在SOA架構中,ESB承擔的即這種角色。微服務中我們肯定不會使用它來橋接單體應用,但是可以將它做為一個發布系統用來廣播任務和數據或者做為系統間的通信匯流排(通過Spark Streaming收集數據到Spark中)。

發布訂閱協議有時候也是有不足的。比如無法提供允許程序員自定義路由規則的高級路由特性或者數據的轉化、豐富、分隔以及合併等功能(可以使用Akka Streams或者Apache Camel)。


系統與外界或者系統之間的通信都是必需的。當與一個外部系統通信時,尤其當外部系統無法把控時,那麼就會有很大的失敗風險(系統超載、業務失敗)。因此即使協議協商得再好,也不能信賴外部服務,需要做好各種預防措施以保證自身服務的安全。

首先要達成一個良好的協議從而可以最小化一個系統突發超載造成服務不可用的風險,比如要避免發起的請求超過服務提供方的承載能力。也要盡量避免使用同步通信機制,否則就把自身服務的可用性放在了依賴的第三方服務的控制中。

避免級聯失敗需要服務足夠解耦和隔離。使用非同步通信機制是一個最佳的方案。此外,還需要通過背壓(back-pressure,接收方根據自己的接受狀況調節接受速率,通過反向的響應來控制發送方的發送速率)來達成數據流速度的一致性,以防止響應快速的系統壓垮其中較慢的部分。而越來越多的工具和庫都在開始擁抱「響應式流」(Reactive Streams)規範(Akka Stream、RxJava、Spark Streaming、Cassandra drivers),這些技術使用非同步背壓實時流來橋接系統,從而在總體上提高系統的可靠性、性能以及互操作性。

如何管理調用服務時候的失敗也是微服務中一個關鍵的問題。捕獲到錯誤後,先重試,而如果錯誤一直發生,那麼就隔離服務一段時間直到服務恢復-「斷路器」模式(Netflix和Akka中都有實現)。

面對可擴展性、高吞吐以及可用性的要求,系統集成的實現從傳統的依賴於中心化服務如RDBMS/ESB逐漸變為現在採用去中心化策略(HTTP REST、ZeroMQ)或者訂閱發布系統(Kakka、Amazon Kinesis)。而最近事件流平台(Event Streaming Platforms)正成為系統集成選型的趨勢,其理念來自於Fast Data和實時數據管理。

如上文所述,服務之間使用非同步通信機制能夠得到很多的好處。但是如果是客戶端(瀏覽器、APP)與服務之間的通信,使用REST經常是更好的選擇。但是並非所有的地方都非得使用同步通信機制,需要根據不同的場景做不同的評估。很多情況下,開發者出於習慣都會傾向於使用同步方案,而不是根據真正的需要作出能夠簡化操作、提升操作性的選擇。這裡給出幾個通常會使用同步方案建模但其本質是非同步行為的事例:

查詢一個商品是否有貨,如果此商品比較熱門被賣光了,用戶要得到通知。

如果一個餐館的特價菜單改動了,用戶要立刻知道。

用戶對於一個網站的評論需要實時對話。

廣告系統根據用戶在頁面上的行為輸出不同的響應。

對於上述實例,我們需要分別進行分析去理解怎樣才是符合客戶端和服務通信的最自然的方式。同時也經常需要根據數據的完整性約束來尋找可以弱化一致性保證(有序)的可能,目的就是找到最少的協調約束條件給用戶以直觀的語義:找到利用現實的最佳策略。


安全管理主要是對服務的認證授權管理,限制某些service只允許某些服務訪問。

TLS Client Certificates也被稱為相互驗證、雙路驗證。它給每一個service都分配一個單獨的私鑰和證書,從而能夠很好地保證服務間的認證訪問。不僅僅服務要驗證客戶端的身份,客戶端也要驗證服務的身份。因此,其不僅能防止數據被竊聽,而且即使在不安全的網路中也能防止對數據的攔截和轉發。基於SSL之上的通信不僅安全,其也是一個公開、易於理解的標準。但是其非常複雜,無法得到底層平台的足夠支持。同樣的,HTTPS Basic Authentication也是雙路驗證,但其對SSL證書的管理也很複雜,請求也不能被反向代理緩存。

Asymmetric Request Signing:每一個服務都需要使用自己的私鑰給自己發送的請求進行簽名,同時每一個服務的公鑰都要上報給「服務發現」服務。此方案的缺點在於一旦網路不可靠,那麼則很難防止數據竊聽或者請求重放攻擊。

Hash Message Authentication Code (HMAC) : 基於共享密鑰來對請求進行一定規則的簽名。這個方案比較簡單,但是由於每一對需要通信的服務都需要唯一的一個共享密鑰,整個系統則需要所有服務排列數目的共享密鑰數量,實現起來比較麻煩。


微服務架構中,完成一個任務需要多個服務的協同,因此最小化服務之間的狀態協作成本,有助於提升微服務系統的整體性能。

需要做的是要從業務的視角去分析數據以理解數據間的關係、擔保和完整性約束。然後對數據進行反範式設計並在系統內定義一致性邊界。如此,可以在邊界內部實現強一致性。接著,需要使用這些邊界來驅動微服務的設計和範圍。如果設計的服務之間有很多數據依賴和關係,那麼就需要去減少甚至是消除這些數據的耦合,從而避免對服務狀態的協同。


如上節所述,已經最小化數據耦合了,但仍然還是會有業務場景需要多個服務協作完成。這個的確是無法避免的,但到了目前這一步,需要做的是可以根據需要逐漸的添加協作,而不是一開始各種耦合再逐漸去消除(比較麻煩和困難)。

這裡提供幾種可擴展、彈性伸縮的方式來協同數據改變,以達到Composability(對數據的變動無須停止數據所在的服務,也無須等待某些條件)。

Apology-Oriented Programming: 基於請求原諒比請求許可權容易的想法。如果你不能響應協作,那麼就做出一個合理的猜測,賭一個條件已經滿足,後續如果錯了,那麼就道歉並做補償。這種做法和現實是非常相似的。比如航班的超售,如果起飛的時候沒有座位那麼就去做一些補償措施。

事件驅動架構(Event-Driven Architecture ):基於非同步消息傳輸和事件溯源。需要區分命令和事件,命令表示一個將要產生副作用的操作意圖(對未來的希望),而事件表示已經發生的事實。使用CQRS模式進行查詢,將寫入方(持久化事件日誌)與讀取方(將數據存入RDBMS或者NoSQL資料庫中)分離。這裡使用事件日誌做狀態管理和持久化具有很多好處:簡化審計、調試、冗餘、容錯,並且允許重放過去任意時間點的事件流。

ACID2.0:由Pat Helland創造,定義了一組原則,目的是實現可擴展、彈性的協議以及API設計。A是Associative,表示分組消息不會產生影響,可以批量處理。C是Commutative,表示消息的順序不重要。I是Idempotent,表示消息重複不會產生影響。D是Distributed,沒有實質的意義,猜測是為了湊ACID這個首字母縮寫。

CRDTs是一個囊括了上面這些東西的工具,可以實現最終一致性、據有豐富的數據結構(counters、sets、maps、graphs),並且不需要協作就可以收斂聚合。其更新操作的順序前後也並不影響最終的合併結果,能夠自動安全的進行合併。雖然CRDTs最近才出現在業界視野中,但其實它已經在生產環境使用了很多年。已經有一些生產級別的庫可以直接使用(Akka、Riak)。

然而,很多業務場景並不允許最終一致性。這時可以使用因果一致性(causal consistency)。因果關係很容易被大家理解。而且因果一致性模型能夠實現可擴展性和可用性。其一般使用邏輯時間,在很多NoSQL資料庫、事件日誌以及分散式事件流產品中都可用。

分散式事務是一個經常使用的方式用來協調分散式系統的變動,但其本質需要約束並發執行,保證同一時間只有一個用戶在操作。因此其成本非常昂貴,會使得系統變慢、無法擴展。Saga模式是分散式事務之外的一個能夠實現可擴展、彈性伸縮的選擇。它的理論基礎在於一個長時間運行的業務事務大多時候都是由多個事務步驟組成的,而事務步驟的總體一致性能夠通過將這些步驟分組成一個總體的分散式事務來實現。該技術將每一個階段的事務與一個可補償回滾的事務配對,如果一個階段的事務失敗了,那麼總體的分散式事務就可以回滾(反向順序)。


當設計一個響應式微服務時,需要堅持隔離、單一職責、自主、獨佔狀態、非同步消息傳輸和移動等特質。微服務需要協作才能形成一個系統去發揮作用。一個能夠提供基礎服務和響應式原則模式的複雜微服務平台是有必要的。

出處:http://www.rowkey.me/blog/2018/06/07/reactive-microservice/

架構文摘

互聯網應用架構丨架構技術丨大型網站丨大數據丨機器學習


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 架構文摘 的精彩文章:

NFS-Ganesha 核心架構解讀

TAG:架構文摘 |