微服務(wù)的核心組件
微服務(wù)的架構(gòu)設(shè)計(jì)之前總結(jié)過,微服務(wù)的思想是分離,微服務(wù)模式下將應(yīng)用程序拆分為不同的微小服務(wù),通過使用或者組合不同的服務(wù)來完成不同的業(yè)務(wù)功能。那么一旦分離后再組合,就意味著服務(wù)之間一定會(huì)存在相互調(diào)用的過程,在前面微服務(wù)的定義中提到過,微服務(wù)之間都使用粗糙的通信機(jī)制,它一定是輕量級的,而且是可以支持跨語言調(diào)用的,包括微服務(wù)本身對客戶端提供服務(wù)也是采用這種機(jī)制的。因此,設(shè)計(jì)并實(shí)現(xiàn)適合的通信組件來提供遠(yuǎn)程調(diào)用的能力是微服務(wù)架構(gòu)的核心。下面來了解微服務(wù)的遠(yuǎn)程調(diào)用方式。
微服務(wù)的遠(yuǎn)程調(diào)用方式
微服務(wù)架構(gòu)中常用的調(diào)用方式有兩種:一種是通過異步的消息交換來通信,這里服務(wù)調(diào)用雙方一般使用一些中間件,如RabbitMQ、Kafka等;另一種是通過HTTP的資源接口,通過JSON作為信息載體的格式,即REST API的方式進(jìn)行遠(yuǎn)程通信。
1. 異步消息通信
先來看一下異步消息是如何通信的,這里以RabbitMQ的工作原理為例,如圖2.6所示。
首先消息由生產(chǎn)者發(fā)送到一個(gè)統(tǒng)一的交換中心(又稱交易所),交換中心根據(jù)一定的轉(zhuǎn)發(fā)規(guī)則(如直接、主題匹配、扇出等方式),將消息轉(zhuǎn)發(fā)到對應(yīng)的隊(duì)列中,然后通過消息隊(duì)列最終將消息傳遞給消費(fèi)者。消費(fèi)者可以選擇只訂閱自己關(guān)心的消息。
可以利用消息機(jī)制來做很多事情,如可以做一個(gè)簡單的任務(wù)隊(duì)列,也可以去訂閱和發(fā)布消息。當(dāng)然,訂閱和發(fā)布的方式有很多,最常見的就是圖2.7RabbitMQ遠(yuǎn)程調(diào)用工作原理中描述的定向轉(zhuǎn)發(fā)和Topic機(jī)制,這里對消息機(jī)制的原理不做闡述,利用這種發(fā)布和訂閱的工作方式,我們可以通過消息做到服務(wù)的遠(yuǎn)程調(diào)用。
但是,在微服務(wù)中這并不是常見的用法,因?yàn)楸旧硐C(jī)制的實(shí)現(xiàn)會(huì)依賴相關(guān)的中間件或框架,服務(wù)調(diào)用雙方都需要集成相同的消息服務(wù)或技術(shù)框架,這本身就會(huì)加重微服務(wù)架構(gòu)的通信方式,而且過度松散異步的操作也為代碼帶來了一定的復(fù)雜度。所以,在微服務(wù)中最常見的通信還是基于HTTP的REST API。
2. REST API
REST(Representational State Transfer,表述性狀態(tài)轉(zhuǎn)移)用來描述創(chuàng)建HTTP API的標(biāo)準(zhǔn)方法,REST API的核心概念是資源,對于資源有4種常見的行為:查看、創(chuàng)建、編輯和刪除,都可以直接映射到HTTP中已實(shí)現(xiàn)的GET、POST、PUT和DELETE方法。REST本身并沒有創(chuàng)造新的技術(shù)、組件或服務(wù),只是正確地使用Web的現(xiàn)有特征和能力,更好地使用現(xiàn)有Web標(biāo)準(zhǔn)中的一些準(zhǔn)則和約束。
如果一個(gè)架構(gòu)符合REST的約束條件和原則,就稱它為RESTful架構(gòu),當(dāng)然理論上REST架構(gòu)風(fēng)格并不是綁定在HTTP上的,只不過目前HTTP是唯一與REST相關(guān)的實(shí)現(xiàn),所以一般情況下REST API都是表示基于HTTP的RESTful接口。
既然是一種標(biāo)準(zhǔn)、一種架構(gòu)風(fēng)格,就必然會(huì)有它的相關(guān)概念和設(shè)計(jì)原則。表2.1所示為REST API中的一些重要術(shù)語。
同時(shí),REST API也有很多設(shè)計(jì)原則,如URL中永遠(yuǎn)不能包含動(dòng)詞,那么URL如何表示自己的行為呢?前面提到過4種常見的行為(查看、創(chuàng)建、編輯和刪除)映射到HTTP中已實(shí)現(xiàn)的方法中,具體如下。
(1)GET(查看):從服務(wù)器或資源列表中檢索特定資源。
(2)POST(創(chuàng)建):在服務(wù)器上創(chuàng)建一個(gè)新資源。
(3)PUT(編輯):更新服務(wù)器上的資源,提供整個(gè)資源。
(4)PATCH(編輯):更新服務(wù)器上的資源,僅提供已更改的屬性。
(5)DELETE(刪除):從服務(wù)器中刪除資源。
下面兩個(gè)不是很常用。
(1)HEAD(查看):檢索有關(guān)資源的元數(shù)據(jù),如數(shù)據(jù)的哈希值或上次更新的時(shí)間。
(2)OPTIONS(查看):檢索有關(guān)允許消費(fèi)者使用資源的信息。客戶端和服務(wù)端的交互是無狀態(tài)的,GET請求通常是可以被緩存的,資源使用復(fù)數(shù),URL中可以有表述版本的信息,舉例如下。
按照順序?qū)?yīng)的含義如下。
(1)使用2.0版本的接口,創(chuàng)建一個(gè)產(chǎn)品。
(2)使用2.0版本的接口,根據(jù)ID查看產(chǎn)品信息。
(3)使用1.0版本的接口,根據(jù)ID全量更新產(chǎn)品信息。
(4)使用1.0版本的接口,根據(jù)ID部分更新產(chǎn)品信息。
(5)使用1.0版本的接口,根據(jù)ID刪除產(chǎn)品。
RESTful的接口還有很多設(shè)計(jì)原則,這里不再贅述,感興趣的讀者可以查閱相關(guān)資料進(jìn)行學(xué)習(xí),在微服務(wù)中使用的HTTP通信協(xié)議就是采用的RESTful的接口設(shè)計(jì),所以熟練掌握REST API的設(shè)計(jì)用法在微服務(wù)架構(gòu)中是十分重要的。
HTTP通信方法
一般在項(xiàng)目中采用什么技術(shù)來完成HTTP通信?在一些早期的項(xiàng)目中,可以看到Apache HttpComponents的身影,它的功能也十分強(qiáng)大,但是在使用時(shí),需要編寫大量的基礎(chǔ)代碼,往往還需要進(jìn)行二次封裝,而在如今Spring Boot盛行的時(shí)代,大家更熱衷于現(xiàn)取現(xiàn)用,正所謂約定大于配置,所有的基礎(chǔ)工作都按照一定的約定交由框架來完成。下面以Spring為例,主要介紹RestTemplate和WebClient兩種方式進(jìn)行HTTP的通信。
1. RestTemplateRestTemplate是Spring Web中提供的用于在客戶端完成同步的HTTP請求的核心類,大大簡化了與HTTP服務(wù)器的通信,并實(shí)現(xiàn)了RESTful的設(shè)計(jì)原則。RestTemplate默認(rèn)采用JDK原生的HTTP連接工具實(shí)現(xiàn),當(dāng)然也可以切換庫,如Apache HttpComponents、Netty和OKHttp等第三方的HTTP庫。我們以默認(rèn)的實(shí)現(xiàn)為例,首先需要聲明一個(gè)RestTemplate,這里采用Spring Boot的注解式聲明方式,代碼如下。
當(dāng)然,還可以給它初始化一些公共屬性,如URL、用戶名密碼,或者一些連接超時(shí)等設(shè)置,代碼如下。
完成 RestTemplate 的聲明之后就可以使用它了 , 之前說過RestTemplate實(shí)現(xiàn)了RESTful的設(shè)計(jì)原則,所以RestTemplate提供了便捷的方法去實(shí)現(xiàn)HTTP的GET、POST、PUT、PATCH和DELETE方法。例如,getForEntity()就是以GET的方式發(fā)送HTTP請求,而postForEntity()則 是 以 POST 的 方 式 發(fā) 送 HTTP 請 求 。 當(dāng) 然 , 還 有 其 他 實(shí) 現(xiàn) , 如patchForObject()、put()、delete()和optionsForAllow()等方法,這里就不一一介紹了。下面以GET和POST兩種最常見的方式來介紹RestTemplate的用法,其他的大同小異。
(1)RestTemplate的GET方法。
RestTemplate提供了getForObject和getForEntity兩種方式發(fā)送GET的HTTP請求,其中g(shù)etForObject方法可以直接將響應(yīng)的Body轉(zhuǎn)換為指定的類型,方法定義如下。
其中,第一個(gè)方法比較常用,按照順序傳遞URL的參數(shù),通過在URL中定義{}來表示參數(shù)的站位,{}中可以寫具體含義的單詞,也可以寫數(shù)字,如{0},代碼如下。
此外,getForObject還提供了另外兩種重載方法,分別提供了通過Map傳遞參數(shù)和沒有參數(shù)兩種功能。沒有參數(shù)很好理解,不再贅述,下面的代碼展示了通過Map傳遞參數(shù)的方式。
不難看出,Map中的key對應(yīng)著URL中{}里的單詞,使用Map的不足之處是當(dāng)參數(shù)太多時(shí),順序容易弄錯(cuò),而且方法會(huì)寫得很長,不易讀,也不易維護(hù)。getForObject方法能滿足我們大部分的需求,但有時(shí)可能需要獲取除Body之外的信息,如響應(yīng)頭、響應(yīng)狀態(tài)碼等,這時(shí)就需要getForEntity了,getForEntity和getForObject一樣,提供了3種實(shí)現(xiàn),方法定義如下。
可以發(fā)現(xiàn),getForEntity的方法參數(shù)和getForObject的一樣,唯一的區(qū)別是getForEntity的返回類型是ResponseEntity。這里不再對每個(gè)方法進(jìn)行詳細(xì)介紹了,下面還是以第一個(gè)方法為例,代碼如下。
(2)RestTemplate的POST方法。
與GET的方式一樣,POST也提供了postForObject和postForEntity兩種方式來完成POST的HTTP請求。方法定義如下。
POST的6個(gè)方法定義與GET幾乎一樣,所以這里沒有把方法說明粘貼進(jìn)來,仔細(xì)觀察可以發(fā)現(xiàn),POST的方法多了一個(gè)request的參數(shù),這個(gè)參數(shù)會(huì)被放進(jìn)請求的Body中,當(dāng)沒有需要時(shí)也可以傳入null,舉例如下:
關(guān)于RestTemplate的其他方法就不再列舉了,感興趣的讀者可以查看Spring Web庫的源碼,或者訪問Spring的官網(wǎng)查閱相關(guān)教程。
2. WebClient
WebClient相比RestTemplate是一個(gè)較新的HTTP訪問方式,之前提到過,RestTemplate是一個(gè)同步的請求方式,當(dāng)請求發(fā)出后,當(dāng)前線程會(huì)等待,直到有響應(yīng)后才會(huì)繼續(xù)執(zhí)行后續(xù)代碼。其實(shí)遠(yuǎn)程調(diào)用是一個(gè)可以異步的過程,在等待請求響應(yīng)時(shí),我們完全可以做其他的事情,所以RestTemplate在一些性能要求比較高的地方使用就顯得不是那么合適了。
這時(shí)就需要使用可以異步完成請求的WebClient了,當(dāng)然,我們可以仍然使用RestTemplate,然后通過線程池或CompletableFuture等方式創(chuàng)建新的線程來執(zhí)行RestTemplate的請求而不阻塞當(dāng)前線程的執(zhí)行。不過這樣做不是特別優(yōu)雅,而且每次還需要自行維護(hù)關(guān)于創(chuàng)建不同線程的代碼。
隨著JAVAScript的Reactive設(shè)計(jì)理念越來越流行,不少語言和框架開始相繼模仿。Spring也采用了“No blocking”(無阻塞)的方式,推出了Spring WebFlux,關(guān)于WebFlux的功能有很多,這里主要來看一下WebFlux中WebClient的用法。
相比RestTemplate,WebClient最大的優(yōu)勢就是可以使用Reactive的方式執(zhí)行非阻塞的HTTP請求,即異步的請求服務(wù)端。WebClient同樣實(shí)現(xiàn)了RESTful的設(shè)計(jì)原則,支持GET、POST、PUT、PATCH和DELETE等操作,而且寫法更接近于流式,示例代碼如下。
這是一個(gè)GET方法,代碼很好理解,只不過是流式的寫法,先定義HTTP的方法,然后定義URI,最后定義返回類型。再來看一個(gè)POST的例子,代碼如下。
很顯然,相比GET方法多一個(gè)syncBody 方法,類似于RestTemplate的request參數(shù),這個(gè)方法會(huì)把該參數(shù)當(dāng)作請求的Body發(fā)送到服務(wù)端。仔細(xì)觀察可以發(fā)現(xiàn),與RestTemplate相比有一個(gè)最大的不同,就是方法的返回值變成了Mono類型,其實(shí)除了Mono類型,WebFlux還提供了Flux類型,代碼如下。
首先通過代碼可以發(fā)現(xiàn),Mono和Flux的區(qū)別在于前者是單個(gè)元素,后者是集合,當(dāng)然這不是最關(guān)鍵的,Mono中也可以是一個(gè)集合,如Mono<List<User>>、Mono和Flux之間可以相互轉(zhuǎn)換。關(guān)鍵在于WebClient是異步的請求,在調(diào)用它時(shí)得到的返回類型不可能是直接的期待返回的類型,如findAllUsers得到的不是List<User>類型,而是Flux<User>類型。
這就好比我們通過CompletableFuture創(chuàng)建了一個(gè)線程A,然后在執(zhí)行時(shí)會(huì)立刻返回一個(gè)CompletableFuture類型的對象,這時(shí)主線程就不會(huì)阻塞,而是繼續(xù)執(zhí)行。當(dāng)我們需要得到返回值時(shí),可以通過CompletableFuture的join方法,將線程A加入當(dāng)前線程,這時(shí)如果線程A已經(jīng)執(zhí)行完成,那么當(dāng)前線程就會(huì)立刻得到線程A的執(zhí)行結(jié)果,如果線程A還沒有執(zhí)行完,那么當(dāng)前線程就會(huì)等待,直到線程A執(zhí)行完成。這是一個(gè)典型的異步執(zhí)行方法的例子,而WebClient的Mono和Flux就和CompletableFuture具有相同的作用,而且更加強(qiáng)大。
例如,可以提前定義對返回?cái)?shù)據(jù)的操作,代碼如下。
再如,可以打包合并多個(gè)Mono或Flux,然后只執(zhí)行一次join,代碼如下。
可以看到,Mono提供了類似join的方法:block。如果你更喜歡使用 CompletableFuture , Mono 和 Flux 也可以輕松地轉(zhuǎn)換成CompletableFuture,代碼如下。






