《微服務(wù)設(shè)計(jì)》,Building Microservices,作者Sam Newman,譯者崔力強(qiáng)、張駿,人民郵電出版社,2016年。
筆記中有些內(nèi)容直接引用原書(shū)。
================================================================
第四章 集成
1. 尋找理想的集成技術(shù)
避免破壞性修改。例如,響應(yīng)增加字段不影響服務(wù)方。
保證API技術(shù)的無(wú)關(guān)性。微服務(wù)之間通信方式的技術(shù)無(wú)關(guān)性很重要。
使你的服務(wù)易于消費(fèi)方使用。利于消費(fèi)方使用任何技術(shù)來(lái)用你的服務(wù)。
隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。暴露內(nèi)部實(shí)現(xiàn)會(huì)導(dǎo)致消費(fèi)方去耦合內(nèi)部實(shí)現(xiàn),因此當(dāng)修改內(nèi)部實(shí)現(xiàn)時(shí)造成消費(fèi)方不必要的修改。不要采用傾向于暴露內(nèi)部細(xì)節(jié)的技術(shù)。
2. 為用戶(hù)創(chuàng)建接口
3. 共享數(shù)據(jù)庫(kù)?;诒淼墓蚕恚瑫?huì)使得服務(wù)消費(fèi)者看到內(nèi)部實(shí)現(xiàn),無(wú)法實(shí)現(xiàn)隱藏內(nèi)部細(xì)節(jié)。另外,服務(wù)實(shí)現(xiàn)綁定了數(shù)據(jù)庫(kù)技術(shù),導(dǎo)致服務(wù)消費(fèi)者也必須要使用同樣的數(shù)據(jù)庫(kù),無(wú)法帶來(lái)技術(shù)開(kāi)放性。因此,避免使用共享數(shù)據(jù)庫(kù)。
4. 同步與異步
同步通信的協(xié)作風(fēng)格是請(qǐng)求/響應(yīng)式,異步通信的風(fēng)格是基于事件。適合用異步通信的場(chǎng)景:運(yùn)行時(shí)間長(zhǎng)的任務(wù)、低延時(shí)的任務(wù)、移動(dòng)網(wǎng)絡(luò)及設(shè)備?;谑录南到y(tǒng)依賴(lài)于接收事件的系統(tǒng)自己判斷該做什么,其協(xié)作邏輯是分布在不同的協(xié)作者中,因此耦合度低。
5. 編排與協(xié)同
編排,依賴(lài)于某個(gè)中心來(lái)驅(qū)動(dòng)流程。而協(xié)同依靠各個(gè)部分主動(dòng)協(xié)作共同完成。編排的好處在于系統(tǒng)實(shí)現(xiàn)簡(jiǎn)單,便于追蹤問(wèn)題。缺點(diǎn)是中心控制節(jié)點(diǎn)承擔(dān)太多職責(zé),其它服務(wù)淪為CRUD貧血服務(wù)。協(xié)同的優(yōu)點(diǎn)在于能消除耦合。缺點(diǎn)是需要額外工作來(lái)監(jiān)控流程,增加系統(tǒng)復(fù)雜度。另外,如果想用請(qǐng)求/相應(yīng)風(fēng)格的語(yǔ)義,又想避免耗時(shí)業(yè)務(wù)時(shí)的長(zhǎng)等待,可以采用異步請(qǐng)求加回調(diào)的方式。
6. 遠(yuǎn)程過(guò)程調(diào)用
遠(yuǎn)程過(guò)程調(diào)用(RPC)種類(lèi)很多,一些依賴(lài)于接口定義(SOAP、Thrift、protocol buffers等),容易生成客戶(hù)端和服務(wù)端的樁代碼,用戶(hù)可以快速編程。但其代價(jià)大于快速啟動(dòng)的好處。
技術(shù)的耦合。Java RMI,雙方必須使用Java。Thrift和protocol buffers支持不同語(yǔ)言,一定程度上減輕了該問(wèn)題。有時(shí)候RPC技術(shù)對(duì)于互操作性有一定的限制。
本地調(diào)用和遠(yuǎn)程調(diào)用并不相同。RPC面臨網(wǎng)絡(luò)的不確定性,還要進(jìn)行載荷消息的封裝和解封裝。用戶(hù)在使用時(shí)需要額外考慮網(wǎng)絡(luò)帶來(lái)的問(wèn)題,而當(dāng)作本地調(diào)用時(shí)又會(huì)帶來(lái)考慮不全的問(wèn)題。在進(jìn)行RPC調(diào)用的錯(cuò)誤處理時(shí),顯然要麻煩的多。
脆弱性。以Java RMI為例,接口修改就要導(dǎo)致樁代碼的修改。
RPC很糟糕嗎。盡量避免使用RMI,轉(zhuǎn)為使用現(xiàn)代的RPC如protocol buffers或者Thrift。使用RPC時(shí),不要對(duì)遠(yuǎn)程調(diào)用過(guò)度抽象,以至于網(wǎng)絡(luò)因素都被隱藏了;確??梢元?dú)立升級(jí)服務(wù)端接口而不用客戶(hù)端強(qiáng)制升級(jí);在客戶(hù)端中不要隱藏是在做網(wǎng)絡(luò)調(diào)用這個(gè)事實(shí)。
7. REST
REST使得資源在服務(wù)內(nèi)和在服務(wù)對(duì)外提供的形式可以不一樣,解耦了。建議看Richardson的成熟度模型(http://martinfowler.com/articles/richardsonMaturityModel.html)。 REST沒(méi)有規(guī)定底層協(xié)議,常用的是HTTP,也可以使用其它協(xié)議如串口或USB。HTTP上實(shí)現(xiàn)REST簡(jiǎn)單。
REST和HTTP。REST聲明了一組對(duì)資源的使用方法,HTTP中有方法能與其對(duì)應(yīng)。HTTP有很多支撐工具和技術(shù)。比如Varnish是HTTP緩存代理,mod_proxy是負(fù)載均衡器,還有大量HTTP監(jiān)控工具,還有安全認(rèn)證機(jī)制和工具。
超媒體作為程序狀態(tài)的引擎。超媒體是一塊包含了其它內(nèi)容鏈接的內(nèi)容??蛻?hù)端與服務(wù)端應(yīng)該通過(guò)超媒體進(jìn)行交互。通過(guò)超媒體可以隱藏服務(wù)端內(nèi)部的更改,將客戶(hù)端與服務(wù)端解耦。該方式的缺點(diǎn)是客戶(hù)端和服務(wù)端之間通信次數(shù)較多。但還是建議客戶(hù)端自行發(fā)現(xiàn)遍歷和發(fā)現(xiàn)API,因?yàn)檫@樣可以解耦,不要過(guò)早優(yōu)化。
JSON、XML和其他。JSON簡(jiǎn)單,內(nèi)容更緊湊,比XML流行。但XML中有超鏈接來(lái)進(jìn)行超媒體控制,JSON中沒(méi)有,于是JSON有不同的自定義方式,如HAL標(biāo)準(zhǔn)。XML工具有更好支撐,提取負(fù)載特定部分可以使用XPATH工具,挺多,CSS選擇器也可以用。JSON可以使用JSONPATH。
留心過(guò)多的約定。有些工具使用RESTFul Web服務(wù)框架把內(nèi)部存儲(chǔ)暴露給消費(fèi)者,并不好。
基于HTTP的REST的缺點(diǎn)。無(wú)法像RPC一樣幫助生成客戶(hù)端代碼。不要回到基于HTTP進(jìn)行RPC的老路去構(gòu)建共享庫(kù)。另外,性能上的問(wèn)題:基于HTTP的REST支持多種格式,如JSON或二進(jìn)制,比SOAP強(qiáng),但沒(méi)法和Thrift這樣的二進(jìn)制協(xié)議比。對(duì)于低延遲通信或較小尺寸的消息不是一個(gè)好選擇。不支持高級(jí)的序列化和反序列化。建議閱讀《REST實(shí)戰(zhàn)》這本書(shū)。
8. 實(shí)現(xiàn)基于事件的異步協(xié)作方式
技術(shù)選擇。需要考慮微服務(wù)發(fā)布事件機(jī)制和消費(fèi)者接收事件機(jī)制。RabbitMQ這樣的消息代理可以解決上述問(wèn)題,是個(gè)好選擇。但盡量讓這種消息中間件簡(jiǎn)單,邏輯放在自己的服務(wù)中,企業(yè)級(jí)服務(wù)總線(xiàn)是個(gè)不好的反例。在HTTP上,有ATOM這個(gè)符合REST規(guī)范的協(xié)議,可以用來(lái)提供資源聚合的發(fā)布服務(wù)。但是有消息中間件的話(huà),還是建議使用消息中間件。
異步架構(gòu)的復(fù)雜性。事件驅(qū)動(dòng)的異步系統(tǒng)耦合度低,伸縮性好,但需要程序員轉(zhuǎn)換思維模式,而且復(fù)雜性更高。要考慮各個(gè)流程有很好的監(jiān)督,并考慮使用關(guān)聯(lián)ID,它可以對(duì)跨進(jìn)程請(qǐng)求進(jìn)行追蹤。強(qiáng)烈推薦《企業(yè)集成模式》這本書(shū)。
9. 服務(wù)即狀態(tài)機(jī)。要把關(guān)鍵領(lǐng)域的生命周期顯式地用狀態(tài)機(jī)建模出來(lái),避免出現(xiàn)貧血服務(wù)。
10. 響應(yīng)式擴(kuò)展(Reactive extensions, Rx)。它提供了一種機(jī)制,可以把多個(gè)調(diào)用結(jié)果組裝起來(lái)并在此基礎(chǔ)上執(zhí)行操作。調(diào)用本身可以是阻塞或非阻塞的。當(dāng)需要做一些基于多個(gè)服務(wù)調(diào)用的操作時(shí),可以嘗試它,它讓代碼更加簡(jiǎn)單。
11. 微服務(wù)世界中的DRY和代碼重用的危險(xiǎn)。Don’t Repeat Yourself,DRY可以得到重用性比較好的代碼,可以創(chuàng)建一個(gè)共享庫(kù)。但這在微服務(wù)中會(huì)導(dǎo)致服務(wù)和消費(fèi)者之間過(guò)度耦合。在微服務(wù)內(nèi)部不要違反DRY,在跨服務(wù)的情況下可以適當(dāng)違反DRY。
客戶(hù)端庫(kù)。客戶(hù)端庫(kù)可以對(duì)服務(wù)開(kāi)發(fā)進(jìn)行一些封裝,提升開(kāi)發(fā)效率,避免重復(fù)的與服務(wù)交互的代碼。但當(dāng)開(kāi)發(fā)服務(wù)端API和客戶(hù)端API是同一撥人時(shí),存在將服務(wù)邏輯引入客戶(hù)端的問(wèn)題,帶來(lái)了耦合性的問(wèn)題。如果要使用客戶(hù)端,讓它只處理底層傳輸協(xié)議(服務(wù)發(fā)現(xiàn)、故障處理等),不要加入服務(wù)邏輯。另外,客戶(hù)端庫(kù)可能會(huì)限制不同技術(shù)的使用。
12. 按引用訪問(wèn)。如果對(duì)訪問(wèn)的資源有本地緩存,要考慮資源的過(guò)期失效問(wèn)題,確保同時(shí)有一個(gè)指向原始資源的引用。
13. 版本管理
盡可能推遲。避免過(guò)早將客戶(hù)端與服務(wù)端緊密綁定。客戶(hù)端要盡可能靈活消費(fèi)服務(wù)響應(yīng),這符合Postel法則(系統(tǒng)中的每個(gè)模塊都應(yīng)該“寬進(jìn)嚴(yán)出”)。例如客戶(hù)端可以使用XPath從服務(wù)響應(yīng)中提取需要的字段,即使字段位置改變也能正確讀取(容錯(cuò)性讀取器)。
及早發(fā)現(xiàn)破壞性修改。建議使用消費(fèi)者驅(qū)動(dòng)的契約來(lái)及早定位對(duì)消費(fèi)者產(chǎn)生的破壞性修改。
使用語(yǔ)義化的版本管理。語(yǔ)義化版本管理使得客戶(hù)端僅通過(guò)查看版本號(hào)就能知道是否能與之集成。版本好:MAJOR.MINOR.PATCH。MAJOR改變意味著包含向后不兼容的修改,客戶(hù)端就不能直接集成。MINOR變化意味著新功能增加,向后兼容,客戶(hù)端可以直接集成。PATCH變化意味著功能缺陷修復(fù)。
不同的接口共存。接口不可避免要修改時(shí),保留老接口,提供新接口,二者共存。給消費(fèi)者時(shí)間將老接口替換為新接口的使用,然后再刪除老接口。另外,可以通過(guò)將老接口的請(qǐng)求轉(zhuǎn)換為新接口的調(diào)用。不同版本共存時(shí),可以在請(qǐng)求信息中增加版本標(biāo)識(shí),也可以在URI中增加版本標(biāo)識(shí)。
同時(shí)使用多個(gè)版本的服務(wù)。為了支持老用戶(hù),有時(shí)候會(huì)使用多個(gè)版本的服務(wù)共存(注意,這里不是指服務(wù)接口,而是指服務(wù)本身)。短期內(nèi)合理,但更應(yīng)該考慮一個(gè)服務(wù)暴露兩套API,而不是兩個(gè)服務(wù)共存。
14. 用戶(hù)界面
走向數(shù)字化。通過(guò)微服務(wù)的不同組合為桌面應(yīng)用、移動(dòng)端設(shè)備、可穿戴設(shè)備提供不同的體驗(yàn)。
約束。不同平臺(tái)(桌面端、移動(dòng)端)有不同的約束,屏幕解析度、通信方式、帶寬、電池電量、UI操作。
API組合。 不同平臺(tái)的API可以使用API入口(gateway),多個(gè)底層的調(diào)用會(huì)被聚合成一個(gè)調(diào)用。
UI片段的組合。相比UI主動(dòng)訪問(wèn)所有API,再同步狀態(tài)到UI控件上,更好的方法可能是服務(wù)直接暴露一部分UI,然后將這些組合到一起形成整體UI??墒褂梅?wù)端模板的技術(shù)將這些片段組裝起來(lái)。優(yōu)勢(shì)是修改服務(wù)的同時(shí)可以維護(hù)這些UI片段。
為前端服務(wù)的后端。服務(wù)端的聚合接口或API入口不要太厚重,要分成不同的后端,每個(gè)后端只為一個(gè)應(yīng)用或用戶(hù)界面服務(wù)(BFF, Backends for
Frontends)。
一種混合方式。片段組裝、BFF等可以權(quán)衡混合使用。
15. 與第三方軟件集成。
使用一些商業(yè)的第三方軟件會(huì)有如下問(wèn)題:
缺乏控制。只有軟件的廠家才能控制其發(fā)展和進(jìn)行技術(shù)決策。
定制化。定制化很昂貴。
意大利面式的集成。一團(tuán)亂麻的服務(wù)集成。
推薦的集成方式:
在自己可控的平臺(tái)進(jìn)行定制化。推薦這么做,可以使用自己的服務(wù)包住第三方的服務(wù)。
絞殺者模式(Strangler Application Pattern,?http://martinfowler.com/bliki/StranglerApplication.html),攔截對(duì)老系統(tǒng)的調(diào)用,把調(diào)用路由到現(xiàn)存的遺留代碼還是新寫(xiě)的代碼,然后逐步替換老系統(tǒng)。一般使用一系列的微服務(wù)來(lái)攔截,而不是單一的單塊應(yīng)用。