當(dāng)我們找到了微服務(wù)邊界,將應(yīng)用分解成了多個(gè)微服務(wù),那么接下來(lái)一個(gè)重要的問題就是這些微服務(wù)的集成了。
一個(gè)健壯的微服務(wù)集成環(huán)境需要考慮多方面要素:
- 通信協(xié)議
- 接口協(xié)議
- 服務(wù)注冊(cè)、發(fā)現(xiàn)
- 服務(wù)版本控制
- 負(fù)載均衡
- 服務(wù)可用性
- 服務(wù)冪等性
- 服務(wù)擴(kuò)展性
- 服務(wù)安全性
- 服務(wù)彈性伸縮
- 服務(wù)降級(jí)、熔斷
- 調(diào)用鏈可追溯
頭大了?別愁,其實(shí)也不是一定要面面俱到,還是那句話:“架構(gòu)不是設(shè)計(jì)出來(lái)的,而是演進(jìn)出來(lái)的”,所以只要滿足當(dāng)下的需要就夠了,適合的就是最好的,過(guò)早優(yōu)化往往只會(huì)帶來(lái)更多痛苦。
微服務(wù)通信
此篇我想先談?wù)勗谕ㄐ欧矫嫖业囊恍┧伎迹驗(yàn)橥ㄐ攀羌傻幕A(chǔ),所以選擇合適的通信方案至關(guān)重要。
通信方案選型
可能有人會(huì)說(shuō)服務(wù)提供者完全可以提供多種通信方案,由服務(wù)的消費(fèi)者根據(jù)其偏好自行選擇。那么我想問問,你見過(guò)一部手機(jī)提供了多種數(shù)據(jù)線接口么?即便真的有過(guò)這種手機(jī),你覺得它更好用更合理么?可能這個(gè)比方并不很恰當(dāng),你明白我要表達(dá)的意思就好。
同時(shí)提供多種通信方案可能帶來(lái)哪些問題,我大概列了一下:
- 開發(fā)和維護(hù)成本增加
- 可能帶來(lái)潛在的性能問題
- 加大了系統(tǒng)耦合性
所以選擇合適的通信方案是要從多角度多層面來(lái)考量的,
- 首先,前篇說(shuō)過(guò)好的微服務(wù)是松耦合和高內(nèi)聚的,在選擇通信方案時(shí)也要圍繞這一核心
- 其次,服務(wù)的消費(fèi)者在調(diào)用服務(wù)時(shí)不應(yīng)該有大量工作要做,所以要選擇一個(gè)易于使用的通信方案
- 此外,微服務(wù)由于是高度自治的,允許技術(shù)異構(gòu)的,所以通信方案也不應(yīng)該受到具體技術(shù)的限制,即技術(shù)無(wú)關(guān)
所以,選用什么通信方案最好?答案是沒有。但是可以將上述幾點(diǎn)作為參考,結(jié)合自己的實(shí)際情況,比如業(yè)務(wù)特點(diǎn)、服務(wù)規(guī)模、性能壓力、團(tuán)隊(duì)結(jié)構(gòu)等,來(lái)找尋合適的方案。所以本文中只有思考過(guò)程,沒有現(xiàn)成的答案。
針對(duì)具體的通信方案,我們可能要進(jìn)一步考慮其細(xì)分的一些方面,包括通信協(xié)議、接口風(fēng)格、數(shù)據(jù)封裝、同步異步等,下面說(shuō)說(shuō)我的理解。
通信協(xié)議
通信協(xié)議有很多,常用的比如 HTTP、TCP、UDP等,此外還有如 WebSockets、XMPP、MQTT等,這些都是網(wǎng)絡(luò)通信,非網(wǎng)絡(luò)通信的還包括 串口通信、USB等等。
現(xiàn)今除非是極為特殊的環(huán)境,一般應(yīng)該都是使用網(wǎng)絡(luò)通信協(xié)議的,所以就不說(shuō)非網(wǎng)絡(luò)通信了。要說(shuō)網(wǎng)絡(luò)通信協(xié)議就離不開TCP/IP四層模型(現(xiàn)已基本淘汰OSI七層模型),總不能連 HTTP 和 TCP 有什么區(qū)別都不知道就做選擇吧。對(duì)四層模型我們一般只需要關(guān)注上面兩層:應(yīng)用層和傳輸層,應(yīng)用層協(xié)議(HTTP、XMPP等)是建立在傳輸層協(xié)議(TCP、UDP)上的。
- 應(yīng)用層協(xié)議:針對(duì)一些特定應(yīng)用采用特定的格式進(jìn)行數(shù)據(jù)傳輸,數(shù)據(jù)被編碼成標(biāo)準(zhǔn)協(xié)議,并傳送到下一層。該層的協(xié)議相比下層協(xié)議提供了更多特性,如QoS、安全性等都要比下層豐富,這些特性為我們提供了很多方便。
- 傳輸層協(xié)議:目前主要使用的就是TCP和UDP兩個(gè)協(xié)議。前者是一個(gè)較可靠的、面向連接的傳輸機(jī)制,它提供一種可靠的字節(jié)流保證數(shù)據(jù)完整、無(wú)損并且按順序到達(dá)。后者是一個(gè)無(wú)連接的數(shù)據(jù)報(bào)協(xié)議,它是一個(gè)“不可靠”協(xié)議,因?yàn)樗粰z查數(shù)據(jù)包是否已經(jīng)到達(dá)目的地,并且不保證它們按順序到達(dá)。相比應(yīng)用層協(xié)議,該層缺少了很多特性,因此需要一些特性的時(shí)候就只能自己實(shí)現(xiàn)。
所以通信協(xié)議的選擇最好是結(jié)合自己的應(yīng)用場(chǎng)景所需的特性,考慮是否有較為匹配的協(xié)議,如無(wú)匹配則考察是否有一些其他工具或技術(shù)可以提供相關(guān)特性,或者自己是否能夠輕松實(shí)現(xiàn)這些特性,總之要做到:
- 清楚了解自己在通信層面需要什么樣的特性
- 對(duì)各種主要的網(wǎng)絡(luò)通信協(xié)議的特點(diǎn)和特性有所掌握
接口風(fēng)格
應(yīng)用最廣泛的接口風(fēng)格當(dāng)屬 RPC(遠(yuǎn)程過(guò)程調(diào)用)了,從當(dāng)年的基于SOAP和WSDL的WebServices,到現(xiàn)在的Thrift、Dubbo等,RPC 技術(shù)和工具在 SOA 生態(tài)中一直扮演著重要的角色。RPC 技術(shù)和工具幫助我們解決了網(wǎng)絡(luò)通信和數(shù)據(jù)序列化/反序列化的問題,讓我們像調(diào)用本地方法一樣調(diào)用遠(yuǎn)程服務(wù),有些 RPC 框架會(huì)幫你生成服務(wù)端和客戶端樁代碼(Thrift等),讓你可以快速開發(fā),有些 RPC 框架甚至提供了服務(wù)注冊(cè)、發(fā)現(xiàn)能力(Dubbo等),便于你對(duì)服務(wù)進(jìn)行水平伸縮。但是 RPC 多多少少會(huì)增加一定的耦合性,比如有時(shí)服務(wù)端的修改也會(huì)引起客戶端的修改,而且修改后要求服務(wù)提供者和消費(fèi)者要同時(shí)發(fā)布。
隨著萬(wàn)維網(wǎng)技術(shù)的日新月異,REST 架構(gòu)風(fēng)格也越來(lái)越被推崇,但我發(fā)現(xiàn)實(shí)際上大部分人(包括我)都并沒有真正理解 REST 精髓并加以應(yīng)用。很多人以為使用 HTTP + JSON 并使用了 PUT、DELETE 等方法就是 RESTful 服務(wù)了,實(shí)際上這只是一種表面形式。首先 REST 并沒有說(shuō)一定是要用 HTTP 協(xié)議,也沒有說(shuō)要用 JSON 數(shù)據(jù)格式,只不過(guò) HTTP 的特性為 REST 提供了更好的支持,所以使用 HTTP 對(duì) REST 來(lái)說(shuō)是一種較好的實(shí)踐。REST 包含了非常多內(nèi)容,我也尚在學(xué)習(xí)中,所以要深入了解 REST 還是建議讀讀 Roy Fielding 博士論文 以及 Richardson 成熟度模型。不過(guò)在使用 REST + HTTP 風(fēng)格時(shí),相比一些 RPC 方案,自己可能要做更多的工作。
異步通信也是現(xiàn)在的一個(gè)主流技術(shù),因?yàn)樗沟孟到y(tǒng)資源利用率更高,并有助于降低耦合性,提高系統(tǒng)的擴(kuò)展性。一些 RPC 框架和 REST 框架也同時(shí)提供了同步和異步通信的能力,事件驅(qū)動(dòng)架構(gòu)(EDA)則能夠借助異步技術(shù)(如消息系統(tǒng))實(shí)現(xiàn)更加松耦合且易于擴(kuò)展的應(yīng)用系統(tǒng)。
在選擇接口風(fēng)格時(shí)我認(rèn)為需要考慮:
- 自己當(dāng)前最需要什么,如易用性、開發(fā)速度、熟悉程度、通信性能、系統(tǒng)擴(kuò)展性、水平伸縮性、同步異步、高可用等等。
- 盡可能多的了解各種接口風(fēng)格及相關(guān)技術(shù)和工具提供的能力,學(xué)習(xí)曲線,優(yōu)缺點(diǎn)等,篩選較為滿足自己需要的方案。
- 評(píng)估篩選出來(lái)的技術(shù)和工具如果應(yīng)用到自己的系統(tǒng)中,需要做些什么樣的工作,花費(fèi)多大成本,帶來(lái)的效果和好處是否值得,有必要的話做一些基準(zhǔn)測(cè)試,用數(shù)據(jù)說(shuō)話。
數(shù)據(jù)封裝
如果你是用的是 RPC,十有八九你都不需要考慮數(shù)據(jù)如何封裝,因?yàn)?RPC 框架基本都已經(jīng)實(shí)現(xiàn)了數(shù)據(jù)的序列化/反序列化,例如 Protocol buffers、Hession 等。不同的框架在不同場(chǎng)景下的性能表現(xiàn)也各不相同,相關(guān)的文章有很多,這里不多贅述。
若你需要自己來(lái)選取一種數(shù)據(jù)封裝方案,比如使用 REST + HTTP 時(shí),面對(duì) JSON、XML 或 二進(jìn)制 等格式該如何選擇?我認(rèn)為可以從以下幾方面思考:
- 技術(shù)無(wú)關(guān)性。JSON 和 XML 這類格式天生是技術(shù)無(wú)關(guān)的,如果使用 二進(jìn)制 格式則要避免使用特定技術(shù),比如Java自帶的序列化/反序列化。
- 數(shù)據(jù)大小。相比 JSON,XML 可能會(huì)略大一點(diǎn),如果傳輸?shù)臄?shù)據(jù)量較大,那么不同數(shù)據(jù)格式封裝的結(jié)果可能會(huì)相差很多。
- 性能。不同數(shù)據(jù)格式各自都有多種序列化/反序列化工具,它們的實(shí)現(xiàn)方式使得性能也存在很大差別,有些對(duì)較小的數(shù)據(jù)量處理很高效,對(duì)較大數(shù)據(jù)量則性能低下,有些則恰恰相反。
- 使用場(chǎng)景。如果有的客戶端可能不對(duì)數(shù)據(jù)反序列化,而是直接取用其中的某個(gè)特定部分?jǐn)?shù)據(jù),則使用 XML、JSON 這類格式會(huì)更方便,實(shí)際上消費(fèi)方如何使用數(shù)據(jù)是它自己的事情,服務(wù)提供者理應(yīng)提供這種便利。
同步異步
何時(shí)用同步何時(shí)用異步?我覺得這個(gè)問題現(xiàn)今已經(jīng)沒有多少意義了。因?yàn)橥侥茏龅降?,異步也都能做到,例如在異步調(diào)用中注冊(cè)一個(gè)回調(diào),或者使用 Future + Listener 這種模型,而相比異步,同步會(huì)產(chǎn)生大量阻塞,帶來(lái)延遲,對(duì)系統(tǒng)資源的利用率低下。
那么為什么很多系統(tǒng)仍然在使用同步?因?yàn)楹?jiǎn)單。同步的開發(fā)能夠很快上手,需要做的工作也相對(duì)較少,而異步技術(shù)對(duì)很多開發(fā)者尚存在一定的壁壘,如果完全自己來(lái)實(shí)現(xiàn)異步要做的工作也不少。
前篇提到了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD),我們可以使用其中的思想來(lái)將單塊應(yīng)用分解為多個(gè)微服務(wù),在 DDD 中較為推崇的一種協(xié)作方式是事件驅(qū)動(dòng)架構(gòu)(EDA),在微服務(wù)中這種架構(gòu)更是如魚得水。基于事件的協(xié)作能讓微服務(wù)更加松耦合,業(yè)務(wù)完全內(nèi)聚在各個(gè)微服務(wù)中,不需要有一個(gè)集中控制業(yè)務(wù)邏輯的中樞,從而大大提高系統(tǒng)的可擴(kuò)展性。
所以我的想法是,
- 如果開發(fā)團(tuán)隊(duì)了解EDA那么盡可能使用EDA。
- 如果開發(fā)團(tuán)隊(duì)不了解EDA但是熟悉異步,那么結(jié)合業(yè)務(wù)的實(shí)際情況合理使用異步技術(shù)。
- 如果開發(fā)團(tuán)隊(duì)即不了解EDA也不熟悉異步,那么可以用同步快速開發(fā),然后逐步建立異步的知識(shí)體系,隨著業(yè)務(wù)的開展針對(duì)系統(tǒng)中對(duì)低延遲、低耦合要求高的地方用異步一步步進(jìn)行重構(gòu)。
結(jié)語(yǔ)
本篇主要針對(duì)微服務(wù)集成中的基礎(chǔ)——通信——該如何選擇合適方案談了我的思路,指出了一些方面是我在做選擇時(shí)會(huì)去考量的,不是十分全面,更不敢說(shuō)都是正確的,有什么不妥之處歡迎和我交流。