《微服务架构设计模式》总结,文末送书
經(jīng)常翻閱微服務(wù)材料的話,總會(huì)碰到 microservices.io 這個(gè)網(wǎng)站,總結(jié)了微服務(wù)方方面面的設(shè)計(jì)模式。網(wǎng)站的作者是 Chris Richardson。
這些相關(guān)的經(jīng)驗(yàn)在 2018 年成為了《微服務(wù)架構(gòu)設(shè)計(jì)模式》這本書,并且 2019 年引進(jìn)國(guó)內(nèi)。當(dāng)時(shí)我第一時(shí)間購(gòu)入了這本書,中間斷斷續(xù)續(xù)地一直沒(méi)看完。最近準(zhǔn)備分享內(nèi)容的時(shí)候又翻起這本書,這次完整地讀完了一遍。感覺(jué)應(yīng)該是目前微服務(wù)領(lǐng)域最好的一本書了。另一本《Building Microservices》也不錯(cuò),不過(guò)內(nèi)容還是稍微單薄了一些。
這本書為我們提供了宏觀上俯瞰微服務(wù)整個(gè)生態(tài)的大圖,比如:
當(dāng)然,18 年的時(shí)候,service mesh 之類的東西還沒(méi)有太火,所以后來(lái)在網(wǎng)站上有個(gè)更新的版本:
個(gè)人很喜歡這種大圖,不管什么領(lǐng)域,我只要照著圖去一點(diǎn)一點(diǎn)填坑就行了,沒(méi)有這樣的圖,總覺(jué)得是在望不見(jiàn)頭的技術(shù)森林里兜兜轉(zhuǎn)轉(zhuǎn),找不到北。
下面簡(jiǎn)單寫一寫我對(duì)這本書的總結(jié),里面 saga 和測(cè)試的部分我就先省略了。
單體服務(wù)的困境
在單體時(shí)代,大家在一個(gè)倉(cāng)庫(kù)里開(kāi)發(fā),代碼沖突解決起來(lái)很麻煩,上線的 CI/CD pipeline 也是等待到死。
拆分了以后,至少大家有各自的代碼庫(kù),各自的上線流程,各自的線上服務(wù)。這樣上線不打架了,上線以后也可以自己玩自己的灰度流程,一般不會(huì)互相影響。
服務(wù)拆分
雖然說(shuō)是拆了,不過(guò)拆分也是要講究方法的。
書里提供了兩種思路,一種是按照業(yè)務(wù)/商業(yè)能力拆分,一種是按照 DDD 中的 sub domain 拆分。
供應(yīng)商管理
送餐員信息管理
餐館信息管理:管理餐館的訂單、營(yíng)業(yè)時(shí)間、營(yíng)業(yè)地點(diǎn)
消費(fèi)者管理
管理消費(fèi)者信息
訂單獲取和履行
消費(fèi)者訂單管理:讓消費(fèi)者可以創(chuàng)建、管理訂單
餐館訂單管理:讓餐館可以管理訂單的準(zhǔn)備過(guò)程
送餐管理
送餐員狀態(tài)管理:管理可以進(jìn)行接單操作的送餐員的實(shí)時(shí)狀態(tài)
配送管理:訂單配送追蹤
會(huì)計(jì)
消費(fèi)者記賬:管理消費(fèi)者的訂單記錄
餐館記賬:管理餐館的支付記錄
配送員記賬:管理配送員的收入信息
最終大概會(huì)形成上面這些服務(wù)。
用 DDD 來(lái)做分析,其實(shí)我們得到的結(jié)果也差不多:
在拆分時(shí),我們還應(yīng)該用 SOLID 中的 SRP 原則和另外一個(gè)閉包原則 CCP(common closure principle) 來(lái)進(jìn)行指導(dǎo)。
在拆分后,也要注意微服務(wù)的拆分會(huì)額外給我們帶來(lái)的問(wèn)題:
網(wǎng)絡(luò)延遲
服務(wù)間同步通信導(dǎo)致可用性降低
在服務(wù)間維持?jǐn)?shù)據(jù)一致性
獲取一致的數(shù)據(jù)視圖
阻塞拆分的上帝類
服務(wù)集成
分布式服務(wù)通信大概可以分為 one-to-one 和 one-to-many:
RPC 很好理解,同步的 request/response。異步通信,一種是回調(diào)式的 request/response,一種是一對(duì)多的 pub/sub。
具體到 RPC 的話,可以使用多種協(xié)議和框架:
不過(guò)當(dāng) API 更新時(shí),應(yīng)該遵循 semver 的規(guī)范進(jìn)行更新。社區(qū)里 gRPC 很多次更新都沒(méi)有遵守 semver,給它的依賴方都造成了不小的麻煩。感興趣的同學(xué)應(yīng)該可以搜到一些相關(guān)的事件。
不得不說(shuō) Google 的程序員也并不是事事靠譜。
RPC 進(jìn)行服務(wù)集成的時(shí)候,要注意不要被某些不穩(wěn)定的服務(wù)慢響應(yīng)拖死,要注意設(shè)置超時(shí),熔斷。
服務(wù)與服務(wù)之間要能找得到彼此,有兩種方式,一種是基于服務(wù)注冊(cè)中心的服務(wù)發(fā)現(xiàn)。
一種是基于 dns 的服務(wù)發(fā)現(xiàn)?;?DNS 的現(xiàn)在應(yīng)該不太多了。
除了 RPC 以外,還可以使用消息來(lái)進(jìn)行服務(wù)間的集成。
使用 MQ 也可以模擬 RPC 的 request/response,不過(guò)這樣會(huì)使你的服務(wù)強(qiáng)依賴于 MQ,如果 MQ 故障,那整個(gè)系統(tǒng)隨之崩潰。
一般我們使用的是 broker-based mq 通信,但也有無(wú) broker 的異步通信,書中這里舉了個(gè) ZeroMQ 的例子,之前個(gè)人不是很了解,需要再調(diào)研一下。
Event Sourcing
event sourcing 是一種特殊的設(shè)計(jì)模式,不記錄實(shí)體的終態(tài),而是記錄所有狀態(tài)修改的事件。然后通過(guò)對(duì)事件進(jìn)行計(jì)算來(lái)得到實(shí)體最終的狀態(tài)。
但這樣事件累積太多以后會(huì)有性能問(wèn)題,所以可以對(duì)一部分歷史數(shù)據(jù)進(jìn)行計(jì)算,得到一個(gè)中間的快照,之后的計(jì)算在快照的基礎(chǔ)上再疊加。
看起來(lái)方案很酷,我們?cè)趯?shí)際工作中也確實(shí)在一些下游的計(jì)算邏輯中使用過(guò)這種設(shè)計(jì)模式,不過(guò)它也是有缺陷的:
事件本身結(jié)構(gòu)變化時(shí),新老版本兼容比較難做
如果代碼中要同時(shí)處理新老版本數(shù)據(jù),那么升級(jí)幾次后會(huì)非常難維護(hù)
因?yàn)槿菀鬃匪?#xff0c;所以刪除數(shù)據(jù)變得非常麻煩,GDPR 類的法規(guī)要求用戶注銷時(shí)必須將歷史數(shù)據(jù)刪除干凈,這對(duì) Event Sourcing 是一個(gè)巨大的挑戰(zhàn)
在使用異步消息來(lái)做解耦的時(shí)候,我們也會(huì)遇到一些實(shí)際的業(yè)務(wù)問(wèn)題:
這個(gè)數(shù)據(jù)我需要,你能不能在消息里幫我透?jìng)饕幌?/p>
你重構(gòu)的時(shí)候怎么把這個(gè)字段刪了,我還要用呢
你們?cè)瓉?lái)的狀態(tài)機(jī)變更都有 event,本來(lái)有三個(gè),怎么現(xiàn)在變成兩個(gè)了?
你們 API 故障的時(shí)候,怎么消息順序亂了?
這要求我們能有對(duì)上游的領(lǐng)域事件進(jìn)行校驗(yàn)的系統(tǒng),這里可以參考 Google 的 schema validation 這個(gè)項(xiàng)目,之前我在 《MQ 正在變成臭水溝》一文中有詳述。這里就不提了。
查詢模式
很多查詢邏輯其實(shí)就是進(jìn)行 API 的數(shù)據(jù)組合,這個(gè)涉及到需要組合數(shù)據(jù)的 API 組合器,和數(shù)據(jù)提供方:
API 組合器:通過(guò)查詢數(shù)據(jù)提供方的服務(wù)實(shí)現(xiàn)查詢操作
數(shù)據(jù)提供方:提供數(shù)據(jù)
雖然看起來(lái)挺簡(jiǎn)單,寫代碼的時(shí)候,下面這些問(wèn)題還是難處理:
誰(shuí)來(lái)負(fù)責(zé)拼裝這些數(shù)據(jù)?有時(shí)是應(yīng)用,有時(shí)是外部的 API Gateway,難以定立統(tǒng)一的標(biāo)準(zhǔn),在公司里也經(jīng)常扯皮
增加額外的開(kāi)銷-一個(gè)請(qǐng)求要查詢很多接口
可用性降低-每個(gè)服務(wù)可用性 99.5%,實(shí)際接口可能是 99.5^5=97.5
事務(wù)數(shù)據(jù)一致性難保障-需要使用分布式事務(wù)框架/使用事務(wù)消息和冪等消費(fèi)
CQRS
業(yè)務(wù)開(kāi)發(fā)經(jīng)常自嘲是 CRUD 工程師,在架構(gòu)設(shè)計(jì)里,CRUD 的 R 可以單獨(dú)拆出來(lái),像下面這樣。
拆出來(lái)的好處?互聯(lián)網(wǎng)大多是寫少讀多的服務(wù),將關(guān)注點(diǎn)分離之后,讀服務(wù)和寫服務(wù)的存儲(chǔ)可以做異構(gòu)。
比如寫可以是 MySQL,而讀則可以是各種非常容易做橫向擴(kuò)展的 NoSQL。碰到檢索需求,讀還可以是 Elasticsearch。
讀服務(wù)可以訂閱寫服務(wù)的 domain event,也可以是 MySQL 的 binlog。
在消費(fèi)上游數(shù)據(jù)時(shí),需要根據(jù)業(yè)務(wù)邏輯去判斷有些狀態(tài)機(jī)要怎么做處理。這里其實(shí)數(shù)據(jù)上是有耦合的,并不是放個(gè) MQ 和 domain event 就能解耦干凈了。
CQRS 的缺點(diǎn)也比較明顯:
架構(gòu)復(fù)雜
數(shù)據(jù)復(fù)制延遲問(wèn)題
查詢一致性問(wèn)題
并發(fā)更新問(wèn)題處理
冪等問(wèn)題需要處理
外部 API 模式
現(xiàn)在的互聯(lián)網(wǎng)公司一般客戶端都是多端,web、移動(dòng)、向第三方開(kāi)放的 open API。
如果我們直接把之前用拆分方法拆出來(lái)的這些內(nèi)部 API 開(kāi)放出去,那未來(lái)內(nèi)部的 API 想升級(jí)就會(huì)非常非常地麻煩。
在單體時(shí)代,客戶端走弱網(wǎng) internet,只需要一次調(diào)用。微服務(wù)化以后,如果不做任何優(yōu)化,那在 internet 這種慢速網(wǎng)絡(luò)上就需要有多次調(diào)用。
這就是我們?yōu)槭裁葱枰虚g有一個(gè) API Gateway 的原因。
有了 Gateway 之后,在 internet 依然還是一次調(diào)用,在內(nèi)部 IDC 強(qiáng)網(wǎng)絡(luò)的狀態(tài)下多次網(wǎng)路調(diào)用相對(duì)沒(méi)有那么糟糕。
API Gateway 涉及到這些不同端的 API Gateway 應(yīng)該要誰(shuí)來(lái)維護(hù)的問(wèn)題。下面是一種理想的情況,Mobile 團(tuán)隊(duì)負(fù)責(zé)維護(hù)他們?cè)?Gateway 里的 API(也可能是單獨(dú)的 Gateway),web 端團(tuán)隊(duì)維護(hù) web 的 Gateway,open API 團(tuán)隊(duì)負(fù)責(zé)維護(hù)第三方應(yīng)用的 Gateway API。
網(wǎng)關(guān)基礎(chǔ)設(shè)施團(tuán)隊(duì)負(fù)責(zé)提供這三方都需要的基礎(chǔ)庫(kù)。
在研發(fā) API Gateway 的時(shí)候,我們有多種可選項(xiàng):
直接使用開(kāi)源產(chǎn)品
Kong
APISix
Traefik
自研
Zuul
Spring Cloud Gateway
RESTFul 自己做一個(gè)
GraphQL 自己做一個(gè)
開(kāi)源的 API Gateway 大多不支持 API 數(shù)據(jù)組合功能,所以公司內(nèi)的 API Gateway 有時(shí)候有兩層,一層是 nginx 之類的負(fù)責(zé)簡(jiǎn)單路由和鑒權(quán)的 gateway,后面還有一個(gè)業(yè)務(wù)的 BFF 來(lái)負(fù)責(zé)拼裝端上需要的數(shù)據(jù)。
如果我們都是自研,那就可以在一個(gè)模塊上把 API Gateway 需要的功能都實(shí)現(xiàn)。這里經(jīng)常討論的一個(gè)問(wèn)題是,我們是要使用 REST 還是類似 GraphQL 的圖查詢。
Netflix 的工程師在 2012 年發(fā)表過(guò)一篇文章:
《為什么 REST 讓我半夜睡不著》,老哥還挺幽默。內(nèi)容大概是講 Netflix 要面對(duì)成百上千的終端設(shè)備,Netflix 曾經(jīng)希望能給所有終端提供大一統(tǒng)的方案,大家使用統(tǒng)一的 REST API,但后來(lái)發(fā)現(xiàn)這種統(tǒng)一的方式是放棄了對(duì)任一有自己特性的設(shè)備的優(yōu)化,比如有些設(shè)備內(nèi)存小,有些設(shè)備屏幕小,這些設(shè)備上你返回的很多字段數(shù)據(jù)對(duì)他們來(lái)說(shuō)根本就用不上,純粹是浪費(fèi)網(wǎng)絡(luò)帶寬。有些設(shè)備用流式響應(yīng)比返回完整響應(yīng)性能更好,這也應(yīng)該是要考慮的優(yōu)化點(diǎn)。
所以 Netflix 做了一個(gè)叫 falcor 的方案,其實(shí)和后來(lái) Facebook 的 GraphQL 非常類似,是用 JSON Graph 來(lái)描述內(nèi)部 API 能提供的數(shù)據(jù),然后用 JS 來(lái)定制圖上的查詢。
現(xiàn)在大多數(shù)人比較熟悉的還是 GraphQL:
GraphQL 是個(gè)好東西,但之前我個(gè)人對(duì)使用 GraphQL 一直持懷疑態(tài)度,主要是因?yàn)?#xff1a;
網(wǎng)關(guān)容易被客戶端的修改直接帶崩
穩(wěn)定性非常難保障
中文互聯(lián)網(wǎng)上講 GraphQL 的基本都沒(méi)有提到限流,或者一筆帶過(guò),這是不太負(fù)責(zé)任的
直到最近我發(fā)現(xiàn)國(guó)外某公司公開(kāi)了他們的 GraphQL 限流方案,這個(gè)方案非常有意思,我會(huì)在下一篇文章進(jìn)行分享。
這本《微服務(wù)架構(gòu)模式》難能可貴的地方在于幾乎所有的技術(shù)方案,都很詳盡地給出了優(yōu)劣,這在其它書里是比較少見(jiàn)的。我們?cè)谀承┤说奈恼潞头桨咐镉肋h(yuǎn)只能看到優(yōu)點(diǎn),但在實(shí)踐中落地技術(shù)方案的時(shí)候,其實(shí)更關(guān)注缺陷。對(duì)缺陷有心理準(zhǔn)備,才不會(huì)在新方案出問(wèn)題的時(shí)候心慌。
個(gè)人覺(jué)得這本書,無(wú)論是對(duì)微服務(wù)剛?cè)腴T,或是工作幾年的人,都值得一讀。
在本文最后做個(gè)簡(jiǎn)單的抽獎(jiǎng)活動(dòng),本文回復(fù)的第一、第三、第十、第三十個(gè)人,都能夠得到一本《微服務(wù)架構(gòu)模式》,你需要寫寫自己對(duì)微服務(wù)的見(jiàn)解,或者簡(jiǎn)短地分享一下自己在公司中碰到的微服務(wù)的坑就可以。
100 字以內(nèi)就行,不用長(zhǎng)篇大論~
沒(méi)抽到的老哥就只能自己買了,[doge]。
超強(qiáng)干貨來(lái)襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的《微服务架构设计模式》总结,文末送书的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 曹大,欧神开新公众号了
- 下一篇: 设计模式:开篇