微服務的正確打開方式--阿里巴巴微服務最佳實踐

1回顧:從單體到微服務到 Function

在過去幾年間,微服務架構成爲業界主流,很多公司開始採用微服務,並將原有的單體應用遷移到微服務架構。從架構上說,微服務和單體之間最大的變化在於微服務架構下應用的粒度被“拆小”:將所有業務邏輯都集中在一起的單體應用,按照領域模型拆分爲多個內聚而自治的“更小”應用。而 Function 則在拆分上更進一步,拆分粒度變成“單個操作”,基於 Function 逐漸演進出現了 FaaS 形態和 Serverless 架構。

在微服務和 Serverless 喧囂中,業界逐漸出現很多質疑和反對的聲音:越來越多的人發現,當他們興沖沖的遷移單體應用到微服務和 Serverless 架構後,收益卻並沒有期望中的那麼理想。而最近,也出現了一些對微服務的各種質疑、反思的聲音,甚至放棄微服務迴歸單體。舉例,我在 InfoQ 中國網站簡單搜索關鍵字“微服務”,前三頁中就出現瞭如下內容:

我們爲什麼停用微服務?

這些公司爲什麼放棄微服務?

https://www.infoq.cn/article/KSzctluch2ijbRbKYBgO

致傳統企業朋友:不夠痛就別微服務,有坑

https://www.infoq.cn/article/Nd0RofAUp0WtlvlQArbu

微服務帶來的心理陰影

Uber 團隊放棄微服務改用宏服務,網友評論炸鍋了

爲什麼 Segment 從微服務迴歸單體

無論是支持還是反對微服務的聲音,大多都是着眼於組織架構(康威定律,對應用和代碼的 ownership)、微服務拆分(粒度大小,如何識別領域模型和業務邊界)、分佈式事務(跨多個微服務調用時維持一致性),工具(自動化構建、部署、可測試性、監控、分佈式鏈路跟蹤、CI/CD),數據庫分離(避免多個微服務,尤其是領域模型外的微服務共享數據庫)等方面進行合理性分析和觀點闡述,相信大家都對這些問題都有了解。

而我今天的文章,將從另外一個角度來看待微服務(也包括 Serverless)實踐中存在的誤區——辛辛苦苦從單體走到微服務,卻最後淪爲分佈式單體。

2分佈式單體

“Distributed Monolith”,分佈式單體,這真是一個悲傷的技術術語。而這偏偏是企業採用微服務後通常最容易掉進去的一個“陷阱”。事實上,我看到的很多微服務落地最終都是以"分佈式單體"收場,無法獲得微服務的完整收益。

問題源於微服務實施的方式 —— 按照業務邏輯拆解單體,劃分爲多個微服務,定義 API 接口,然後通過 REST 或者 RPC 進行遠程調用,最終把這些微服務組合起來提供各種業務功能。簡單說,就是在業務拆分的基礎上,用進程間的遠程調用簡單替代原來進程內的方法調用。期間,對於原來使用的各種分佈式能力,繼續採用之前的方式。簡單說:方式不變,只是粒度變小。

從方法論說,這樣做無可厚非,這也是微服務採用過程中非常標準的做法。但問題在於,止步於此是不夠的 —— 至少存在兩個有待繼續努力改進的地方。

 分佈式單體起因之一:通過共享庫和網絡客戶端訪問分佈式能力

分佈式能力的共享庫和網絡客戶端是造成分佈式單體問題的原因之一,關於這一點,來自 verizon 的 Mohamad Byan 在他的名爲 Avoid the Distributed Monolith!! 的演講中有詳細闡述,我這裏援引他的圖片和觀點:

https://www.slideshare.net/DevOpsDaysDFW/avoid-the-distributed-monolith

上圖是微服務體系的邏輯架構,由兩部分組成:

  • 內層架構(圖上淺藍色部分),是每個微服務的實現架構;

  • 外層架構 (圖上黃色部分),是構建強大微服務架構所需要的各種能力。這裏通常有大家熟悉的各種分佈式能力。

特別提示:這裏說的“網絡客戶端”是各種分佈式能力的客戶端,如服務註冊發現 /MQ 中間件 /Redis 等 key-value 存儲 / 數據庫 / 監控日誌追蹤系統 / 安全體系等,不是服務間通訊如 RPC 的客戶端。

而內層的微服務是通過共享類庫和網絡客戶端來訪問外層架構提供的分佈式能力:

分佈式能力的共享類庫和網絡客戶端會迫使內層微服務和外層架構的各種分佈式能力之間產生強耦合,增加運維的複雜性(如升級困難造成版本碎片化),多語言受限於類庫和網絡客戶端支持的語言,各種組件(如消息中間件)往往使用自定義數據格式和通訊協議 —— 所有這些迫使內層微服務不得不實質性受限於外層架構的技術選型。

對於 Function,這個問題就更加明顯:Function 的粒度更小,更專注業務邏輯。某些簡短的 Function 可能只有幾百行代碼,但是,爲了讓這幾百行代碼運轉起來而需要引入的共享類庫和網絡客戶端可能相比之下就規模驚人了。援引一張網上圖片作爲示意:

 

 分佈式單體起因之二:簡單用遠程調用替代進程內方法調用

在微服務架構改造過程中,熟悉單體系統和架構的開發人員,習慣性的會將這些單體時代的知識和經驗重用到新的微服務架構之中。其中最典型的做法就是:在遵循領域模型將現有單體應用按照業務邊界拆分爲多個微服務時,往往選擇用 REST 或者 RPC 等遠程調用方式簡單替代原有的進程內方法調用。

當兩個邏輯上的業務模塊存在協作需求時:

從單體到微服務,直接方法調用被替換爲遠程調用(REST 或者 RPC),即使採用 Service Mesh 也只是在鏈路中多增加了 sidecar 節點,並未改變遠程調用的性質:

這導致了前面所說的 “分佈式單體”:

  • 在微服務之前:應用程序由多個耦合在一起的模塊組成,這些模塊通過內存空間進行方法調用…..

  • 在微服務之後:應用程序由多個耦合在一起的微服務組成,這些微服務通過網絡進行遠程調用…..

拋開調用方式的差異來看採用微服務前後的系統架構,會發現:兩者幾乎是完全一樣的!!

而微服務版本在某些情況下可能表現的更糟糕:因爲調用方式更脆弱,因爲網絡遠比內存不可靠。而我們將網絡當成 “膠水” 來使用,試圖把分散的業務邏輯模塊(已經拆分爲微服務)按照單體時代的同樣方式簡單粘在一起,這當然比單體在同一個進程內直接方法調用更加的不可靠。

關於這一點,在"The Eight Fallacies of Distributed Computing/ 分佈式計算的 8 個謬論"一文中有詳細闡述。

https://www.red-gate.com/simple-talk/blogs/the-eight-fallacies-of-distributed-computing/

類似的,在採用 Function 時,如果依然沿用上面的方式,以單體或微服務架構的思維方式和設計模式來創建 FaaS/Serverless 架構:

其本質不會發生變化 —— 不過是將微服務變成粒度更小的函數,導致系統中的遠程調用數量大爲增加:

系統內的耦合並沒有發生變化,Serverless 並不能改變微服務中存在的這個內部耦合問題:調用在哪裏,則耦合就在哪裏!只是把將組件的粒度從 “微服務“換成了 “Function/ 函數”。

耦合的存在是源於系統不同組件之間的通訊模式,而不是實現通訊的技術。

如果讓兩個組件通過“調用”(後面再展開講何爲調用)進行遠程通信,那麼不管調用是如何實現的,這兩個組件都是緊密耦合。因此,當系統從單體到微服務到 Serverless,如果止步於簡單的用遠程調用替代進程內方法調用,那麼系統依然是高度耦合的,從這個角度來說:

單體應用 ≈ 分佈式單體 ≈ Serverless 單體

 分佈式單體起因小結

上面,我們列出了微服務和 Serverless 實踐中容易形成“分佈式單體”的兩個主要原因:

  1. 通過共享庫和網絡客戶端訪問分佈式能力;

  2. 簡單用遠程調用替代進程內方法調用。

下面我們針對這兩個問題探討解決的思路和對策。

3引入非侵入式方案:物理隔離 + 邏輯抽象

前面談到分佈式單體產生的一個原因是“通過共享庫和網絡客戶端訪問分佈式能力”,造成微服務和 Lambda 函數和分佈式能力強耦合。以 Service Mesh 爲典型代表的非侵入式方案是解決這一問題的有效手段,其他類似方案有 RSocket / Multiple Runtime Architecture,以及數據庫和消息的 Mesh 化產品,其基本思路有兩點:

  1. 委託:通過 Sidecar 或者 Runtime 來進行對分佈式能力的訪問,避免應用和提供分佈式能力的組件直接通訊造成強綁定 —— 通過物理隔離進行解耦。

  2. 抽象:對內層微服務隱藏實現細節,只暴露網絡協議和數據契約,將外圍架構的各種分佈式能力以 API 的方式暴露出來,而屏蔽提供這些能力的具體實現 —— 通過邏輯抽象進行解耦。

以 Service Mesh 的 Sidecar 爲例,在植入 Sidecar 之後,業務應用需要直接對接的分佈式能力就大爲減少(物理隔離):

最近出現的 Multiple Runtime / Mecha 架構,以及遵循這一架構思想的微軟開源產品 Dapr ,則將這個做法推進到服務間通訊之外更多的分佈式能力。

此外在委託之外,還提供對分佈式能力的抽象。比如在 Dapr 中,業務應用只需要使用 Dapr 提供的標準 API,就可以使用這些分佈式能力而無法關注提供這些能力的具體產品(邏輯抽象):

以 pub-sub 模型中的發消息爲例,這是 Dapr 提供的 Java 客戶端 SDK API:

  •  
  •  
  •  
  •  
public interface DaprClient {    Mono<Void> publishEvent(String topic, Object event);   Mono<Void> publishEvent(String topic, Object event, Map<String, String> metadata);}

可見在發送事件時,Dapr 完全屏蔽了底層消息機制的具體實現,通過客戶端 SDK 爲應用提供發送消息的高層抽象,在 Dapr Runtime 中對接底層 MQ 實現——完全解耦應用和 MQ:

關於 Multiple Runtime / Mecha 架構的介紹不在這裏深入展開,有興趣的同學可以瀏覽我之前的博客文章——“Mecha:將 Mesh 進行到底”。

https://skyao.io/talk/202004-mecha-mesh-through-to-the-end/

稍後我會有一篇深度文章針對上面這個話題,詳細介紹在消息通訊領域和 EDA 架構下如何實現消息通訊和事件驅動的抽象和標準化,以避免業務應用和底層消息產品綁定和強耦合,敬請關注。

4引入 Event:解除不必要的強耦合

在解決了微服務 /Serverless 系統和外部分佈式能力之間緊耦合的問題後,我們繼續看微服務 /Serverless 系統內部緊耦合的問題。前面討論到,從單體到微服務到 Function/Serverless,如果只是簡單的將直接方法調用替換爲遠程調用(REST 或者 RPC),那麼兩個通訊的模塊之間會因爲這個緊密耦合的調用而形成依賴,而且依賴關係會伴隨調用鏈繼續傳遞,導致形成一個樹形的依賴關係網絡,表現爲系統間的高度耦合:

要解決這個問題,基本思路在於審視兩個組件之間通訊行爲的業務語義,然後據此決定兩者之間究竟是應該採用 Command/ 命令模式還是 Event/ 事件模式。

 溫故而知新:Event 和 Command

首先,我們來溫習一下 Event 和 Command 的概念和差別,借用一張圖片,總結的非常到位:

什麼是 Event?

Event: “A significant change in state” — K. Mani Chandy

Event 代表領域中已經發生的事情:通常意味着有行爲(Action)已經發生,有狀態(Status)已經改變。

因爲是已經發生的事情,因此:

  • Event 可以被理解爲是對已經發生的事實的客觀陳述;

  • 這意味着 Event 通常是不可變的:Event 的信息(代表着客觀事實)不能被篡改,Event 的產生不能逆轉;

  • 命名:Event 通常以動詞的完成時態命名,如 UserRegistredEvent。

產生 Event 的目標是爲了接下來的 Event 傳播:

  • 將已經發生的 Event 通知給對此感興趣的觀察者;

  • 收到 Event 的觀察者將根據 Event 的內容進行判斷和決策:可能會有接下來的動作(Action),有些動作可能需要和其他模塊通訊而觸發命令(Command),這些動作執行完畢可能會造成領域狀態的改變從而繼續觸發新的事件(Event)。

Event 傳播的方式:

  • Event 有明確的“源 /source”,即 Event 產生(或者說狀態改變)的發生地;

  • 但由於生產者並不知道(不願意 / 不關心)會有哪些觀察者對 Event 感興趣,因此 Event 中並不包含“目的地 /Destination”信息;

  • Event 通常是通過 MessageQueue 機制,以 pub-sub 的方式傳播;

  • Event 通常不需要回復( reply)或者應答(response);

  • Event 通常用 發佈(publish)。

什麼是 Command?

Command 用於傳遞一個要求執行某個動作(Action)的請求。

Command 代表將要發生的事情:

  • 通常意味着行爲(Action)還未發生但即將發生(如果請求被接受和執行);

  • 存在被拒絕的可能:不願意執行(參數校驗失敗,權限不足),不能執行(接收者故障或者資源無法訪問);

  • 命名:Command 通常以動詞的普通形態命名,如 UserRegisterCommand。

產生 Command 的目標是爲了接下來的 Command 執行:

  • 將 Command 發送給期望的執行者;

  • 收到 Command 的執行者將根據 Command 的要求進行執行:在執行的過程中內部可能有多個動作(Action),有些動作可能需要和其他模塊通訊而觸發命令(Command),這些動作執行完畢可能會造成領域狀態的改變從而繼續觸發新的事件(Event)。

Command 的傳播方式:

  • Command 有明確的源(Source),即 Command 的發起者;

  • Command 也有非常明確的執行者(而且通常是一個),因此命名通常包含“目的地 /Destination”信息;

  • Command 通常是通過 HTTP / RPC 這樣的點對點遠程通訊機制,通常是同步;

  • Command 通常需要應答(Response):迴應 Command 是否被執行(因爲可能被拒絕),執行結果(因爲可能執行失敗);

  • Command 通常用 發送(Send)

Command 和 Event 總結

總結 —— Command 和 Event 的本質區別在於他們的意圖:

  • Command 的意圖是告知希望發生的事情;

  • Event 的意圖是告知已經發生的事情。

意圖上的差異最終會在服務間依賴關係上有特別的體現:

  • Command 的發起者必須明確知曉 Command 的接收者並明確指示需要做什麼(所謂的命令、指示、操縱、編排),尤其當發起者連續發出多個 Command 時,通常這些 Command 會有非常明確的順序和邏輯關係,以組合爲特定的業務邏輯。

Command 的依賴關係簡單明確: 發起者 “顯式依賴” 接收者

  • Event 的發起者只需負責發佈 Event,而無需關注 Event 的接收者,包括接收者是誰(一個還是多個)以及會做什麼(所謂的通知、驅動、協調)。即使 Event 實際有多個接收者,這些接受者之間往往沒有明確的順序關係,其處理過程中的業務邏輯也往往是彼此獨立的。

Event 的依賴關係稍微複雜一些:發起者明確不依賴接收者,接收者則存在對發起者 “隱式的反向依賴” ——反向是指和 Command 的依賴關係相比方向調轉,是接受者反過來依賴發起者;隱式則是指這種依賴只體現於 “接受者依賴 Event,而 Event 是由發起者發佈” 的間接關係中,接受者和發起者之間並不存在直接依賴關係。

 從業務視角出發:關係模型決定通訊行爲

在溫習完 Command 和 Event 之後,我們再來看我們前面的問題:爲什麼簡單的將直接方法調用替換爲遠程調用(REST 或者 RPC)會出問題?主要原因是在這個替換過程中,所謂簡單是指不假思索直接選擇遠程調用,也就是選擇全程 Command 方式:

真實業務場景下各個組件(微服務或者 Function)的業務邏輯關係,通常不會像上圖這麼誇張,不應該全是 Command (後面會談到也不應該全是 Event) ,而應該是類似下圖描述的兩者結合,以微服務爲例(Function 類推):

業務輸入:圖上微服務 A 接收到業務請求的輸入(可能是 Command 方式,也可能是 Event 方式)

業務邏輯 “實現” 的執行過程:

  • 微服務 A 在執行 Command(或者被 Event 觸發)的過程中,會有很多動作(Action);

  • 有些是微服務 A 內部的動作,比如操作數據庫,操作 key-value 存儲,內存中的業務邏輯處理等;

  • 有些是和外部微服務進行通訊,如執行查詢或要求對方進行某些操作,這些通訊方式是以 Command 的形式,如圖上和微服務 B 的通訊;

  • 在這些內部和外部動作完成之後,執行過程完成;

  • 如果是 Command,則需要以應答的形式給回 Command 操作的結果。

業務狀態變更觸發的後續行爲:

  • 在上面的執行過程完成後,如果涉及到業務狀態的變更,則需要爲此發佈事件;

  • 事件通過 event bus 分發給對該事件感興趣的其他微服務:注意這個過程是解耦的,微服務 A 不清楚也不關心哪些微服務對此事件感興趣,事件也不需要應答。

上面微服務 A 的業務邏輯執行處理過程中,需要以 Command 或者 Event 方式和其他微服務通訊,如圖中的微服務 B/C/D/E。而對於這些微服務 B/C/D/E(視爲微服務 A 的下游服務),他們在接受到業務請求後的處理流程和微服務 A 的處理流程是類似的。

因此我們可以簡單推導一下,當業務處理邏輯從微服務 A 延展到微服務 A 的下游服務(圖中的微服務 B/C/D/E)時的場景:

將圖中涉及的微服務 A/B/C/D/E 在處理業務邏輯的行爲總結下來,通訊行爲大體是一樣的:

抽象起來,一個典型的微服務在業務處理流程中的通訊行爲可以概括爲以下四點:

  1. 輸入:以一個 Command 請求或者一個 Event 通知爲輸入,這是業務處理流程的起點。

  2. 內部 Action:微服務的內部邏輯,典型如數據庫操作,訪問 Redis 等 key-value 存儲(對應於 Multiple Runtime/Mecha 架構中的各種分佈式能力)。可選,通常爲 0-N 個。

  3. 外部訪問:以 Command 形式訪問外部的其他微服務。可選,通常爲 0-N 個。

  4. 通告變更:以 Event 形式對外發布事件,通告上述操作產生的業務狀態的變更。可選,通常爲 0-1 個。

在這個行爲模式中,2 和 3 是沒有順序的,而且可能交錯執行,而 4 通常都是在流程的最後:只有當各種內部 Action 和外部 Command 都完成,業務邏輯實現結束,狀態變更完成,“木已成舟”,才能以 Event 的方式對外發布:“操作已完成,狀態已變更,望周知”。

這裏我們回顧一下前面的總結 —— Event 和 Command 的本質區別在於他們的意圖:

  • Event 的意圖是告知已經發生的事情;

  • Command 的意圖是告知希望發生的事情。

從業務邏輯處理的角度來看,外部訪問的 Command 和內部操作的 Action 是業務邏輯的 “實現” 部分:這些操作組成了完整的業務邏輯——如果這些操作失敗,則業務處理將會直接影響(失敗或者部分失敗)。而發佈事件則是業務邏輯完成之後的後續 “通知” 部分:當業務邏輯處理完畢,狀態變更完成後,以事件的方式驅動後續的進一步處理。注意是驅動,而不是直接操縱。

從時間線的角度來看整個業務處理流程如下圖所示:

 全程 Command 帶來的問題:不必要的強耦合

全程 Command 的微服務系統,存在的問題就是在上述最後階段的“狀態變更通知”環節,沒有采用 Event 和 pub-sub 模型,而是繼續使用 Command 逐個調用下游相關的其他微服務:

Event 可以解耦生產者和消費者,因此圖中的微服務 A 和微服務 C/D/E 之間沒有強烈的依賴關係,彼此無需鎖定對方的存在。但是 Command 不同,在採用 Command 方式後微服務 A 和下游相關微服務 C/D/E 會形成強依賴,而且這種依賴關係會蔓延,最終導致形成一顆巨大而深層次的依賴樹,而 Function 由於粒度更細,問題往往更嚴重:

而如果在“狀態變更通知”環節引入 Event,則可以解耦微服務和下游被通知的微服務,從而將依賴關係解除,避免無限制的蔓延。如下圖所示,左邊圖形是使用 Event 代替 Command 來進行狀態變更通知之後的依賴關係,考慮到 Event 對生產者和消費者的解耦作用,我們“斬斷”綠色的 Event 箭頭,這樣就得到了右邊這樣一個被分解爲多個小範圍依賴樹的系統依賴關係圖:

對 Event 和 Command 使用的建議:

  • 在單體應用拆分爲微服務時,不應該簡單的將原有的方法調用替換爲 Command;

  • 應該審視每個調用在業務邏輯上的語義:是業務邏輯執行的組成部分?還是執行完成之後的狀態通知?

  • 然後據此決定採用 Command 還是 Event。

編排和協調

在 Command 和 Event 的使用上,還有兩個概念:編排和協調。

這裏強烈推薦一篇博客文章, Microservices Choreography vs Orchestration: The Benefits of Choreography,作者 Jonathan Schabowsky ,Solace 的 CTO。他在這邊博客中總結了讓微服務協同工作的兩種模式,並做了一個生動的比喻:

https://solace.com/blog/microservices-choreography-vs-orchestration/

編排(Orchestration):需要主動控制所有的元素和交互,就像指揮家指揮樂團的樂手一樣——對應 Command。

協調(Choreography):需要建立一個模式,微服務會跟隨音樂起舞,不需要監督和指令——對應 Event。

也曾看到很多持類似觀點的文章,其中有一張圖片印象深刻,我摘錄過來:

左邊是期望通過編排(Orchestration)方式得到的整齊劃一的理想目標,右邊是實際得到的大型翻車現場。

 全程 Event 帶來的問題:開發困難和業務邊界不清晰

在 Command 和 Event 的使用上,除了全程使用 Command 之外,還有一個極端是全程使用 Event,這一點在 Lambda(FaaS)中更常見一些:

這個方式首當其衝的問題就是在適用 Command 語義的地方採用了 Event 來替代,而由於 Command 和 Event 在使用語義上的差異,這個替代會顯得彆扭:

  • Command 是一對一的,替代它的 Event 也不得不從 “1:N” 退化爲 “1:1”,pub-sub 模型不再存在。

  • Command 是需要返回結果的,尤其是 Query 類的 Command 必須要有查詢結果,使用 Event 替代之後,就不得不實現 “支持 Response 的 Event”,典型如在消息機制中實現 Request-Reply 模型的。

  • 或者引入另外一個 Event 來反向通知結果,即用兩個異步 Event 來替代一個同步的 Command —— 這需要讓發起者進行額外的訂閱和處理,開發複雜性遠遠超過使用簡單的 Command。

  • 而且還引入了一個非常麻煩的狀態問題:即服務間通訊的上下文中通常是有狀態的,Reply Event 必須準確的發送給 Request Event 的發起者的實例,而不能任意選擇一個。這使得 Reply Event 不僅僅要 1:1 的綁定訂閱者服務,還必須綁定這個服務的特定實例 —— 這樣的 Reply Event 已經沒法稱爲 Event 了。

  • 繞開這個狀態問題的常見方案是選擇無狀態的場景,如果處理 Reply Event 時無需考慮狀態,那麼 Event Reply 才能簡單的發送給任意的實例。

對於粒度較大的微服務系統,通常很難實現無狀態,所以在微服務中全程採用 Event 通常會比較彆扭的,事實上也很少有人這樣做。而在粒度非常小的 Function/FaaS 系統中,全程採用 Event 方式比較常見。

關於全程使用 Event,我個人持保留態度,我傾向於即使是在 FaaS 中,也適當保留 Command 的用法:如果某個操作是“業務邏輯”執行中不可或缺的一部分,那麼 Command 方式的緊耦合反而更能體現出這個“業務邏輯”的存在:

如果完全採用 Event 方式,“徹底”解耦,則產生新的問題(且不論在編碼方面額外帶來的複雜度) —— 在海量細粒度的 Event 調用下,業務邏輯已經很難體現,領域模型(Domain Modeling)和 有界上下文(Bounded Context)則淹沒在這些 Event 調用下,難於識別:

備註:這個問題被稱爲“Lambda Pinball”,這裏不深入展開,後續計劃會有一篇文章單獨詳細探討“Lambda Pinball”的由來和解決的思路。

 Command 和 Event 的選擇:實事求是不偏不倚

總結一下 Command 和 Event 的選擇,我個人的建議是不要一刀切:全程 Command 方式的缺點容易理解,但簡單替換爲全程 Event 也未必合適。

我的個人觀點是傾向於從實際“業務邏輯”處理的語義出發,判斷:

  • 如果是業務邏輯的 “實現” 部分:傾向於選擇使用 Command;

  • 如果是業務邏輯完成之後的後續 “通知” 部分:強烈建議選擇使用 Event。

5總結與反思

 警惕:不要淪爲分佈式單體

上面我們列出了微服務和 Serverless 實踐中容易形成 “分佈式單體” 的兩個主要原因和對策:

  • 通過共享庫和網絡客戶端訪問分佈式能力:引入非侵入方案解耦應用和各種分佈式能力;

  • 簡單用遠程調用替代進程內方法調用:區分 Command 和 Event,引入 Event 來解除微服務間不必要的強耦合。

前者在技術上目前還不太成熟,典型如 Istio/Dapr 項目都還有待加強,暫時在落地上阻力比較大。但後者已經是業界多年的成熟實踐,甚至在微服務和 Serverless 興起之前就廣泛使用,因此建議可以立即着手改進。

關於如何更方便的將 Event 和 Event Driven Architecture 引入到微服務和 Serverless 中,同時又不與提供 Message Queue 分佈式能力的具體實現耦合,我將在稍後文章中詳細展開,敬請期待。

 反思:喧鬧和謾罵之外的冷靜思考

如果我們在微服務和 Serverless 實踐中,始終停留在“用遠程調用簡單替代進程內方法調用”的程度,並固守單體時代的習慣引入各種 SDK,那麼分佈式單體問題就必然不可避免。我們的微服務轉型、Serverless 實踐最後得到的往往是:

把單體變成…… 更糟糕的分佈式單體

當然,微服務可能成爲分佈式單體,但這並不意味着微服務架構是個謊言,也不意味着比單體架構更差。Serverless 可能同樣遭遇分佈式單體(還有後續要深入探討的 Lambda Pinball),但這也不意味着 Serverless 不可取 —— 微服務和 Serverless 都是解決特定問題的工具,和所有的工具一樣,在使用工具之前,我們需要先研究和了解它們,學習如何正確的使用它們:

  • 需要爲微服務創建正確的架構,和單體架構必然會有很大的不同:一定不是“原封不動”的將方法調替換爲遠程調用,最好不要用共享類庫和網絡客戶端的方式直接使用各種分佈式能力;

  • Serverless 更是需要我們對架構進行徹底的反思,需要改變思維方式,才能保證收益大於弊端。

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