官术网_书友最值得收藏!

1.2 本書Demo詳解

1.2.1 Demo結構說明

首先講解本書使用的Demo的結構,Demo使用Maven聚合功能構建,里面有三個模塊,目錄如圖1.2所示。

圖1.2

其中:

· Consumer模塊為服務消費者相關模塊,本書中所有與消費端有關的Demo都在該模塊中,包含普通調用、各種異步調用、泛化調用、基于擴展接口實現的自定義負載均衡策略、集群容錯策略等。

· Provider模塊為服務提供者相關模塊,本書中所有與服務提供端有關的Demo都在該模塊中,包含服務接口的實現類、服務提供方的同步處理請求、各種異步處理請求的實現等。

· SDK模塊是一個二方包,用來存放服務接口,這是為了代碼復用,在服務提供者和消費者(泛化調用除外)的模塊里需要引入這個二方包。

在Demo根目錄下執行mvn clean install命令會將這些模塊的Jar安裝到本地倉庫,要想在其他模塊里引入這些模塊,必須先執行這個安裝步驟。

本書的Demo使用ZooKeeper(ZooKeeper 3.4.13)作為服務注冊中心,大家可以在其官網下載,并做相應的配置后啟動。另外,由于在編寫本書時Dubbo最新的版本為2.7.1,所以本書基于Dubbo 2.7.1版本做講解。

下面我們詳細講解Demo中的每個模塊,這里假設ZooKeeper已經啟動,并且地址為127.0.0.1:2181。

1.2.2 SDK模塊

考慮到代碼復用,SDK模塊主要用來存放服務接口的定義與POJO類,下面我們看看其中的內容。

· GreetingService接口類主要用來演示同步調用,如下代碼就定義了兩個方法:

· GrettingServiceAsync接口類主要用來演示基于定義CompletableFuture簽名的接口如何實現異步執行,如下代碼就定義了一個返回值類型為CompletableFuture的方法:

· GrettingServiceRpcContext接口類主要用來演示AsyncContext如何實現異步執行,如下代碼只有一個方法:

· PoJo類,一個簡單的POJO(Plain Ordinary Java Object,簡單Java對象)對象,用于演示泛化調用時的參數轉換:

· Result類,POJO的返回值類型,用于演示泛化調用時的參數轉換,定義如下:

1.2.3 同步發布與調用服務

首先我們看看服務提供端是如何使用Dubbo API發布服務的。在了解具體如何發布之前,先看看服務提供端針對SDK中com.books.dubbo.demo.api.GreetingService接口的實現類GreetingServiceImpl的代碼:

上面的代碼很簡單,sayHello()方法休眠1s后返回拼接的字符串。其中RpcContext.getContext().getAttachment("company")獲取調用方在上下文對象中附加的company變量的值,如果調用方在調用前沒進行設置,則返回null。testGeneric()方法則把傳入的poJo對象轉換為字符串后返回。

下面我們看看ApiProvider類是如何發布服務的:

代碼1創建了ServiceConfig實例,其泛型參數為GreetingService接口;代碼2配置應用程序屬性;代碼3設置服務注冊中心地址,可知服務注冊中心使用了ZooKeeper,并且ZooKeeper的地址為127.0.0.1,啟動端口為2181,服務提供方會將服務注冊到該中心。

代碼4將接口與實現類設置到ServiceConfig實例;代碼5設置服務分組與版本,在Dubbo中,“服務接口+服務分組+服務版本”唯一地確定一個服務,同一個服務接口可以有不同的版本以便服務升級等,另外每個服務接口可以屬于不同分組,所以當調用方消費服務時一定要設置正確的分組與版本。

代碼7導出服務,啟動NettyServer監聽鏈接請求,并將服務注冊到服務注冊中心;代碼8掛起線程,避免服務停止。

下面我們看看Consumer模塊是如果同步調用服務的,APiConsumer類的代碼如下:

代碼9創建服務引用對象實例,其中泛型參數為GreetingService;代碼10創建應用程序配置對象;代碼11設置服務注冊中心地址,可知服務注冊中心使用了ZooKeeper,并且ZooKeeper的地址為127.0.0.1,啟動端口為2181,服務消費端啟動后會從該中心獲取服務提供者地址列表。

代碼12設置服務接口和超時時間;代碼13設置自定義負載均衡策略與集群容錯策略,后面會具體講解;代碼14設置服務分組與版本,需要注意的是分組與版本要與服務提供者的分組與版本一致;代碼15引用服務;代碼16設置隱式參數,然后服務提供者就可以在服務實現類方法里獲取參數值;代碼17同步發起遠程調用,然后當前線程會被阻塞直到服務提供方把結果返回。

1.2.4 服務消費端異步調用服務

上一節我們講解的服務消費端是同步調用的,也就是說調用線程在服務提供方結果返回前需要被阻塞,異步調用則是說消費端發起調用后會馬上返回。本節我們將介紹兩種異步調用方式。

1.Dubbo 2.6.*版本提供的異步調用

首先我們看看第一種異步調用方式,也就是Consumer模塊中的APiAsyncConsumer類:

代碼1創建引用實例,并設置屬性;代碼2設置調用為異步;代碼3進行服務引用并且調用sayHello()方法,由于是異步調用,所以該方法馬上返回null;代碼4使用RpcContext.getContext().getFuture()獲取future對象,然后在需要獲取真實響應結果的地方調用future.get()來獲取響應結果(調用future.get()會阻塞調用線程直到結果返回)。

上面介紹的基于從返回的future對象調用get()方法實現異步的缺點是當業務線程調用get()方法后業務線程會被阻塞,這不是我們想要的,所以Dubbo提供了在future對象上設置回調函數的方式,讓我們實現真正的異步調用。下面是Consumer模塊的APiAsyncConsumerForCallBack類:

上面的代碼不同之處在于代碼4,在((FutureAdapter)RpcContext.getContext().getFuture()).getFuture()獲取的future對象上可以調用setCallback()方法設置一個回調函數,該回調函數有兩個方法,當遠端正常返回響應結果后,會回調done()方法,其參數response就是響應結果值;當發起遠程調用發生錯誤時會回調caught()方法以打印錯誤信息。

設置回調的這種方式不會阻塞業務調用線程,這是借助了Netty的異步通信機制,Netty底層的I/O線程會在接收到響應后自動回調注冊的回調函數,不需要業務線程干預。

2.Dubbo 2.7.*版本提供的異步調用

上面我們介紹了Dubbo 2.7.0版本前提供的異步調用方式,Future方式只支持阻塞式的get()接口獲取結果。雖然通過獲取內置的ResponseFuture接口可以設置回調,但獲取ResponseFuture的API使用起來不方便,并且無法滿足讓多個Future協同工作的場景,功能比較單一。下面我們講解Dubbo 2.7.0版本提供的基于CompletableFuture的異步調用。

下面是Consumer模塊的APiAsyncConsumerForCompletableFuture2類:

代碼4直接使用RpcContext.getContext().getCompletableFuture()獲取CompletableFuture類型的future,然后就可以基于CompletableFuture的能力做一系列操作,這里通過調用whenComplete()方法設置了回調函數,作用是當服務提供端產生響應結果后調用設置的回調函數,函數內判斷如果異常t不為null,則打印異常信息,否則打印響應結果。

1.2.5 服務提供端異步執行

1.基于定義CompletableFuture簽名的接口實現異步執行

在Provider模塊中,基于CompletableFuture簽名的接口實現異步執行的接口實現類為GrettingServiceAsyncImpl,其代碼如下:

通過上面的代碼可知,基于定義CompletableFuture簽名的接口實現異步執行需要接口方法的返回值為CompletableFuture,并且方法內部使用CompletableFuture.supplyAsync讓本該由Dubbo內部線程池中的線程處理的服務,轉換為由業務自定義線程池中的線程來處理,CompletableFuture.supplyAsync()方法會馬上返回一個CompletableFuture對象(所以Dubbo內部線程池中的線程會得到及時釋放),傳遞的業務函數則由業務線程池bizThreadpool執行。

需要注意的是,調用sayHello()方法的線程是Dubbo線程模型線程池中的線程,而業務處理是由bizThreadpool中的線程處理的,所以代碼2.1保存了RPC上下文對象(ThreadLocal變量),以便在業務處理線程中使用。

然后,ApiProviderForAsync類用來發布服務,其代碼如下:

上面的代碼比較簡單,這里就不再講解了。服務發布后我們看看服務調用端如何調用。我們看看Consumer模塊的APiConsumerForProviderAsync類:

上面代碼的不同之處在于代碼4,即調用greetingService.sayHello("world")直接返回了CompletableFuture對象,并在其上設置了回調函數。

2.使用AsyncContext實現異步執行

在Provider模塊中,GrettingServiceAsyncContextImpl使用了AsyncContext實現異步執行,具體代碼如下:

代碼1創建了一個自定義線程池用來執行業務處理;代碼2.1調用RpcContext.startAsync()方法開啟服務異步執行,返回一個asyncContext,然后把服務處理任務提交到業務線程池后sayHello()方法就直接返回了null;異步任務內首先執行代碼2.2切換任務的上下文,然后休眠500ms充當任務執行;最后,代碼2.3把任務執行結果寫入異步上下文。

這里由于具體執行業務處理的邏輯不在sayHello()方法所在的Dubbo內部線程池的線程里,所以Dubbo框架的線程不會被阻塞。

1.2.6 服務消費端泛化調用

前面我們講到,當基于Dubbo API搭建Dubbo服務時,服務消費端需要引入一個SDK二方包,其中存放著服務提供端提供的所有接口類。

泛化接口調用方式主要在服務消費端沒有API接口類及模型類元(比如入參和出參的POJO類)的情況下使用,其參數及返回值中沒有對應的POJO類,所以所有POJO參數均轉換為Map表示。使用泛化調用時,服務消費模塊不再需要引入SDK二方包。

在Dubbo中,根據序列化方式的不同,分為三種泛化調用,分別為true、bean和nativejava。

1.generic=true方式

在Consumer模塊的APiGenericConsumerForTrue類中演示了這種方式,其代碼如下:代碼1創建引用實例,這里需要注意的是,在泛型調用時,泛型參數固定為GenericService;代碼2設置泛化調用類型為true;代碼3獲取引用,注意泛化值類型固定為GenericService。

代碼4調用greetingService.$invoke()方法,其中第一個參數為sayHello,說明要調用sayHello()方法,第二個參數為sayHello的參數類型,第三個參數為sayHello參數的值。

代碼5和代碼6是對方法testGeneric()發起泛型調用,這里的第二個參數為testGeneric入參的POJO類型;第三個參數為入參的值,這里由于為POJO類,所以把POJO類的屬性轉換到了Map里,并且Map中固定有一個key為class的屬性,其對應的value為第二個參數的包名加類名,這是為了在服務提供端進行反射使用。

另外,由于這里的testGeneric()方法返回值是Result類型,是POJO類,所以返回值也會被轉換為Map。

2.generic=bean方式

在Consumer模塊的APiGenericConsumerForBean類中演示了這種方式,其代碼如下:

上面代碼的不同之處在于代碼2設置了泛型類型為bean,這意味著對參數使用JavaBean方式進行序列化,如代碼4使用JavaBeanSerializeUtil.serialize把參數進行序列化,然后把序列化結果作為第三個參數,最后執行泛化調用。

代碼5對響應結果使用JavaBeanSerializeUtil.deserialize進行反序列化就可以得到我們可以理解的響應結果(之所以進行反序列化,是因為服務提供方會對返回結果進行序列化)。

3.generic=nativejava方式

在Consumer模塊的APiGenericConsumerForNativeJava類中演示了這種方式,其代碼如下:

上面代碼的不同之處在于代碼2設置了泛型類型nativejava,這意味著對參數使用nativejava方式進行序列化,如代碼4使用nativejava對把參數進行序列化,然后把序列化結果作為第三個參數,最后執行泛化調用。

代碼5對響應結果使用nativejava進行反序列化就可以得到我們可以理解的響應結果(之所以進行反序列化,是因為服務提供方會對返回結果進行序列化)。

1.2.7 服務消費端本地服務mock與服務降級

1.本地服務mock

服務消費端本地服務mock主要用來做本地測試用,當服務提供端服務不可用時,使用本地mock服務可以模擬服務提供端來讓服務消費方測試自己的功能,而不需要發起遠程調用,在Demo的Consumer模塊中實現了mock功能。

要實現mock功能,首先需要消費端先實現服務接口的mock實現類,在Demo中我們是對接口com.books.dubbo.demo.api.GreetingService進行模擬(mock)的,其mock實現類為com.books.dubbo.demo.api.GreetingServiceMock。需要注意的是,mock實現類必須要符合“接口包名.類名Mock”格式,否則啟動時候會拋出“throw new IllegalStateException("No default constructor from mock class"+mockClass.getName(),e)”異常,這在后面的高級篇會做講解。

Demo中的GreetingServiceMock類的代碼如下:

上面代碼中的mock實現類比較簡單,對于sayHello()方法來說,mock返回mock value;對于testGeneric()方法來說,mock返回null。

創建完mock類后,我們看看在APiConsumerMock類中是如何開啟mock功能的:

在上面的代碼中,代碼5設置啟動時不檢查服務提供端是否可用,然后設置mock為true開啟mock功能,接著執行該類,并且保證GreetingServiceMock類的class文件位于classpath下,運行后會輸出mock value字符串,說明mock功能生效了。

需要注意的是,在執行mock服務實現類mock()方法前,會先發起遠程調用,當遠程調用失敗(比如服務不存在)時,才會降級執行mock功能。

2.服務降級

Dubbo提供了一些服務降級措施,當服務提供端某一個非關鍵的服務出錯時,可以手動對消費端的調用進行降級,這樣服務消費端就避免了再去調用出錯的服務,以避免加重服務提供端的負擔。

下面我們看看Dubbo提供的兩種服務降級策略如何使用。

· force:return策略:當服務調用方設置某個接口的降級策略為這種方式時,服務調用方在調用該接口服務時會直接在客戶端內返回設置的mock值,而不會在通過遠程調用方式調用服務提供者,比如配置URL為override://0.0.0.0/com.books.dubbo.demo.api.GreetingService?category=configurators&dynamic=false&application=first-dubbo-consumer&"+"mock="+type+":return+null&group=dubbo&version=1.0.0",其中mock=force:return+null表示服務調用方在調用該服務的方法時都直接返回mock的null值,而不發起遠程調用。需要注意的是,在URL里要指明是對哪個接口的哪個分組的哪個版本的服務進行降級,另外category必須為configurators,application為你的服務調用方的應用名稱,也就是ApplicationConfig的name值。override://0.0.0.0/標識該降級策略對所有的服務消費者生效。該URL會被保存到ZooKeeper中,持久化存放該設置,當消費端啟動時會獲取到該配置。

· fail:return策略:表示服務調用方調用服務提供方的服務失敗后再返回mock值,與force:return的區別是前者如果調用服務提供者成功,則返回正常的結果,如果調用失敗則返回mock的值。這個功能和上一節講解的本地服務mock功能一致。

在Demo的Consumer的APiConsumerMockResult類中演示了如何對一個服務進行降級設置:

代碼1通過增強SPI獲取服務注冊中心的工廠,這里獲取的為ZooKeeper工廠,然后代碼2獲取ZooKeeper客戶端,代碼3將服務降級方案注冊到ZooKeeper。其中參數type為降級方案(比如force或者fail),執行代碼后,配置信息會被保存到服務注冊中心,然后當消費端啟動時會獲取到該配置,并發返回null結果。當服務提供方的服務恢復后,可以執行上面代碼4以取消服務降級方案。

1.2.8 隱式參數傳遞

在Consumer模塊的APiConsumer類中使用隱式參數設置:

上面的代碼3在消費端發起遠程調用前,通過RpcContext.getContext().setAttachment設置了key為company、value為alibaba的鍵值對,這個鍵值對會隨著遠程調用通過網絡傳遞給服務提供端。下面我們看看Demo的Provider模塊的GreetingServiceImpl類:

上面的代碼在服務提供端服務實現類中通過RpcContext.getContext().getAttachment("company")可以獲取消費端傳遞的變量值。

1.2.9 本地服務暴露與引用

遠程服務暴露與引用,是指提供方與消費方通過網絡來進行通信,具體通信流程如圖1.3所示。

圖1.3

其實Dubbo還提供了一種本地服務暴露與引用的方式,這在同一個JVM進程中同時發布與調用同一個服務時顯得比較重要,因為如果當前JVM內要調用的服務在本JVM進程內有提供,則避免了一次遠程過程調用,而是直接在JVM內進行通信,如圖1.4所示。

圖1.4

在Demo的Provider模塊的APiConsumerInJvm類中演示了本地服務暴露與引用的實例:

在上面的代碼中,exportService()方法導出服務(包含本地服務與遠程服務),referService()方法進行服務引用,由于在JVM內存在要引用的服務,所以實際是進行了本地調用,大家可以在InjvmInvoker的invoke()方法內添加斷點以便驗證是否真的調用了本地服務。

主站蜘蛛池模板: 手机| 武强县| 博白县| 双辽市| 彝良县| 正蓝旗| 博白县| 许昌县| 长乐市| 修水县| 陇南市| 衢州市| 碌曲县| 翁源县| 柳江县| 岳阳市| 长沙市| 中阳县| 称多县| 十堰市| 大冶市| 卓尼县| 海城市| 宝应县| 松原市| 上虞市| 昆明市| 临江市| 十堰市| 防城港市| 昌吉市| 顺昌县| 南投市| 苍梧县| 咸阳市| 南宫市| 宁乡县| 扶绥县| 丹江口市| 鄯善县| 兴仁县|