《微服務架構設計模式》的一點總結

經常翻閱微服務材料的話,總會碰到 microservices.io 這個網站,總結了微服務方方面面的設計模式。網站的作者是 Chris Richardson。

這些相關的經驗在 2018 年成爲了《微服務架構設計模式》這本書,並且 2019 年引進國內。當時我第一時間購入了這本書,中間斷斷續續地一直沒看完。最近準備分享內容的時候又翻起這本書,這次完整地讀完了一遍。感覺應該是目前微服務領域最好的一本書了。另一本《Building Microservices》也不錯,不過內容還是稍微單薄了一些。

這本書爲我們提供了宏觀上俯瞰微服務整個生態的大圖,比如:

當然,18 年的時候,service mesh 之類的東西還沒有太火,所以後來在網站上有個更新的版本:

個人很喜歡這種大圖,不管什麼領域,我只要照着圖去一點一點填坑就行了,沒有這樣的圖,總覺得是在望不見頭的技術森林裏兜兜轉轉,找不到北。

下面簡單寫一寫我對這本書的總結,裏面 saga 和測試的部分我就先省略了。

單體服務的困境

在單體時代,大家在一個倉庫裏開發,代碼衝突解決起來很麻煩,上線的 CI/CD pipeline 也是等待到死。

拆分了以後,至少大家有各自的代碼庫,各自的上線流程,各自的線上服務。這樣上線不打架了,上線以後也可以自己玩自己的灰度流程,一般不會互相影響。

服務拆分

雖然說是拆了,不過拆分也是要講究方法的。

書裏提供了兩種思路,一種是按照業務 / 商業能力拆分,一種是按照 DDD 中的 sub domain 拆分。

最終大概會形成上面這些服務。

用 DDD 來做分析,其實我們得到的結果也差不多:

在拆分時,我們還應該用 SOLID 中的 SRP 原則和另外一個閉包原則 CCP(common closure principle) 來進行指導。

在拆分後,也要注意微服務的拆分會額外給我們帶來的問題:

服務集成

分佈式服務通信大概可以分爲 one-to-one 和 one-to-many:

RPC 很好理解,同步的 request/response。異步通信,一種是回調式的 request/response,一種是一對多的 pub/sub。

具體到 RPC 的話,可以使用多種協議和框架:

不過當 API 更新時,應該遵循 semver 的規範進行更新。社區裏 gRPC 很多次更新都沒有遵守 semver,給它的依賴方都造成了不小的麻煩。感興趣的同學應該可以搜到一些相關的事件。

不得不說 Google 的程序員也並不是事事靠譜。

RPC 進行服務集成的時候,要注意不要被某些不穩定的服務慢響應拖死,要注意設置超時,熔斷。

服務與服務之間要能找得到彼此,有兩種方式,一種是基於服務註冊中心的服務發現。

一種是基於 dns 的服務發現。基於 DNS 的現在應該不太多了。

除了 RPC 以外,還可以使用消息來進行服務間的集成。

使用 MQ 也可以模擬 RPC 的 request/response,不過這樣會使你的服務強依賴於 MQ,如果 MQ 故障,那整個系統隨之崩潰。

一般我們使用的是 broker-based mq 通信,但也有無 broker 的異步通信,書中這裏舉了個 ZeroMQ 的例子,之前個人不是很瞭解,需要再調研一下。

Event Sourcing

event sourcing 是一種特殊的設計模式,不記錄實體的終態,而是記錄所有狀態修改的事件。然後通過對事件進行計算來得到實體最終的狀態。

但這樣事件累積太多以後會有性能問題,所以可以對一部分歷史數據進行計算,得到一箇中間的快照,之後的計算在快照的基礎上再疊加。

看起來方案很酷,我們在實際工作中也確實在一些下游的計算邏輯中使用過這種設計模式,不過它也是有缺陷的:

在使用異步消息來做解耦的時候,我們也會遇到一些實際的業務問題:

這要求我們能有對上游的領域事件進行校驗的系統,這裏可以參考 Google 的 schema validation 這個項目,之前我在 《MQ 正在變成臭水溝》一文中有詳述。這裏就不提了。

查詢模式

很多查詢邏輯其實就是進行 API 的數據組合,這個涉及到需要組合數據的 API 組合器,和數據提供方:

雖然看起來挺簡單,寫代碼的時候,下面這些問題還是難處理:

CQRS

業務開發經常自嘲是 CRUD 工程師,在架構設計裏,CRUD 的 R 可以單獨拆出來,像下面這樣。

拆出來的好處?互聯網大多是寫少讀多的服務,將關注點分離之後,讀服務和寫服務的存儲可以做異構。

比如寫可以是 MySQL,而讀則可以是各種非常容易做橫向擴展的 NoSQL。碰到檢索需求,讀還可以是 Elasticsearch。

讀服務可以訂閱寫服務的 domain event,也可以是 MySQL 的 binlog。

在消費上游數據時,需要根據業務邏輯去判斷有些狀態機要怎麼做處理。這裏其實數據上是有耦合的,並不是放個 MQ 和 domain event 就能解耦乾淨了。

CQRS 的缺點也比較明顯:

外部 API 模式

現在的互聯網公司一般客戶端都是多端,web、移動、向第三方開放的 open API。

如果我們直接把之前用拆分方法拆出來的這些內部 API 開放出去,那未來內部的 API 想升級就會非常非常地麻煩。

在單體時代,客戶端走弱網 internet,只需要一次調用。微服務化以後,如果不做任何優化,那在 internet 這種慢速網絡上就需要有多次調用。

這就是我們爲什麼需要中間有一個 API Gateway 的原因。

有了 Gateway 之後,在 internet 依然還是一次調用,在內部 IDC 強網絡的狀態下多次網路調用相對沒有那麼糟糕。

API Gateway 涉及到這些不同端的 API Gateway 應該要誰來維護的問題。下面是一種理想的情況,Mobile 團隊負責維護他們在 Gateway 裏的 API(也可能是單獨的 Gateway),web 端團隊維護 web 的 Gateway,open API 團隊負責維護第三方應用的 Gateway API。

網關基礎設施團隊負責提供這三方都需要的基礎庫。

在研發 API Gateway 的時候,我們有多種可選項:

開源的 API Gateway 大多不支持 API 數據組合功能,所以公司內的 API Gateway 有時候有兩層,一層是 nginx 之類的負責簡單路由和鑑權的 gateway,後面還有一個業務的 BFF 來負責拼裝端上需要的數據。

如果我們都是自研,那就可以在一個模塊上把 API Gateway 需要的功能都實現。這裏經常討論的一個問題是,我們是要使用 REST 還是類似 GraphQL 的圖查詢。

Netflix 的工程師在 2012 年發表過一篇文章:

《爲什麼 REST 讓我半夜睡不着》,老哥還挺幽默。內容大概是講 Netflix 要面對成百上千的終端設備,Netflix 曾經希望能給所有終端提供大一統的方案,大家使用統一的 REST API,但後來發現這種統一的方式是放棄了對任一有自己特性的設備的優化,比如有些設備內存小,有些設備屏幕小,這些設備上你返回的很多字段數據對他們來說根本就用不上,純粹是浪費網絡帶寬。有些設備用流式響應比返回完整響應性能更好,這也應該是要考慮的優化點。

所以 Netflix 做了一個叫 falcor 的方案,其實和後來 Facebook 的 GraphQL 非常類似,是用 JSON Graph 來描述內部 API 能提供的數據,然後用 JS 來定製圖上的查詢。

現在大多數人比較熟悉的還是 GraphQL:

GraphQL 是個好東西,但之前我個人對使用 GraphQL 一直持懷疑態度,主要是因爲:

直到最近我發現國外某公司公開了他們的 GraphQL 限流方案,這個方案非常有意思,我會在下一篇文章進行分享。

這本《微服務架構模式》難能可貴的地方在於幾乎所有的技術方案,都很詳盡地給出了優劣,這在其它書裏是比較少見的。我們在某些人的文章和方案裏永遠只能看到優點,但在實踐中落地技術方案的時候,其實更關注缺陷。對缺陷有心理準備,纔不會在新方案出問題的時候心慌。

個人覺得這本書,無論是對微服務剛入門,或是工作幾年的人,都值得一讀。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/TLZR252J7EHcR8h_vEsXrA