一文搞懂分布式服務(wù)發(fā)布和引用(Dubbo 案例解讀)
在分布式系統(tǒng)和微服務(wù)架構(gòu)中,系統(tǒng)的能力來自服務(wù)與服務(wù)之間的交互和集成。為了實(shí)現(xiàn)這些過程,就需要服務(wù)提供者對外暴露可以訪問的入口,而服務(wù)消費(fèi)者就基于這些入口對服務(wù)提供者發(fā)起遠(yuǎn)程調(diào)用。
我們來舉一個(gè)例子,如果我們想要發(fā)布一個(gè) DemoService,那么可以使用這樣的代碼。
DemoService service = new…;
RpcServer server = new…;
server.export(DemoService.class, demo, options);
而對 DemoService 服務(wù)進(jìn)行導(dǎo)入的實(shí)現(xiàn)過程,可以采用如下所示的代碼風(fēng)格。
RpcClient client =
DemoService service = client.refer(DemoService.class);
service.call(“how are you?”);
顯然,就這兩段代碼而言,看上去很簡單。但事實(shí)上,想要實(shí)現(xiàn)這樣的效果,開發(fā)人員要做的事情非常多。今天,我們就將從這個(gè)簡單的示例出發(fā),探討背后的服務(wù)發(fā)布和引用流程。
服務(wù)發(fā)布和引用
在當(dāng)前主流的分布式服務(wù)框架中,無論是 Dubbo 還是 Spring Cloud,都提供了類似前面介紹的服務(wù)發(fā)布和引用功能。通過對這些框架的實(shí)現(xiàn)機(jī)制進(jìn)行抽象和提煉,我們實(shí)際上可以梳理出一套統(tǒng)一的設(shè)計(jì)和開發(fā)流程。接下來,讓我們先來看服務(wù)的發(fā)布流程。
服務(wù)發(fā)布
先拋開具體的技術(shù)和框架,我們可以簡單抽象出如圖所示的服務(wù)發(fā)布整體流程。
圖片
上圖中包含了服務(wù)發(fā)布過程中的各個(gè)核心組件,包括發(fā)布啟動(dòng)器、動(dòng)態(tài)代理、發(fā)布管理器、協(xié)議服務(wù)器和注冊中心。我們先來一一展開這些核心組件。
- 發(fā)布啟動(dòng)器
發(fā)布啟動(dòng)器(Launcher)的核心作用有兩點(diǎn),一個(gè)是確定服務(wù)的發(fā)布形式,一個(gè)是啟動(dòng)服務(wù)發(fā)布過程。在目前主流的開發(fā)框架中,配置化、注解和 API 調(diào)用是最常見的三種發(fā)布形式。
圖片
以上三種方式各有利弊,在日常開發(fā)過程中,配置和注解比較常用,而 API 調(diào)用則主要完成服務(wù)與服務(wù)之間的集成。
講完發(fā)布形式,我們來討論如何啟動(dòng)服務(wù)發(fā)布過程。
圖片
可以看到,我們能夠使用 Spring 容器來完成基于配置化和注解形式下的服務(wù)啟動(dòng)過程。而對于 API 調(diào)用而言,由于不一定會借助容器,所以可以直接使用 main 函數(shù)來實(shí)現(xiàn)這一目標(biāo)。
- 動(dòng)態(tài)代理
動(dòng)態(tài)代理是遠(yuǎn)程過程調(diào)用中非常核心的一個(gè)技術(shù)組件,在服務(wù)發(fā)布和服務(wù)引用過程中都會用到,其主要作用就是為了簡化服務(wù)發(fā)布和引用的開發(fā)難度,以及確保能夠?qū)φ麄€(gè)過程進(jìn)行擴(kuò)展和定制。我們在后面介紹到服務(wù)引用時(shí)還會看到動(dòng)態(tài)代理。
- 發(fā)布管理器
服務(wù)發(fā)布過程需要使用專門的組件來進(jìn)行統(tǒng)一管理,這個(gè)組件就是發(fā)布管理器。該組件需要判斷本次發(fā)布是否成功,然后在服務(wù)發(fā)布成功之后,把服務(wù)的地址信息注冊到注冊中心。而這里的服務(wù)地址信息則來自協(xié)議服務(wù)器。
- 協(xié)議服務(wù)器
在服務(wù)發(fā)布過程中,在物理上真正建立網(wǎng)絡(luò)連接,并綁定或釋放網(wǎng)絡(luò)端口的組件是協(xié)議服務(wù)器。協(xié)議服務(wù)器還會對網(wǎng)絡(luò)連接進(jìn)行心跳檢測,以及在連接失敗之后進(jìn)行重連操作。
圖片
用于發(fā)布服務(wù)的常見協(xié)議包括 HTTP、RMI、Hessian 等。我們也可以自己定義這樣的協(xié)議,例如 Dubbo 框架就實(shí)現(xiàn)了一套定制化的 Dubbo 協(xié)議。
- 注冊中心
注冊中心的作用是存儲和管理服務(wù)定義的各類元數(shù)據(jù),并能感知到這些元數(shù)據(jù)的變化。注冊中心的核心機(jī)制就是服務(wù)注冊和發(fā)現(xiàn),業(yè)界也存在一批主流的注冊中心實(shí)現(xiàn)工具。
圖片
上述的服務(wù)發(fā)布流程有一定的共性,可以通過轉(zhuǎn)化映射到某個(gè)具體的框架上。事實(shí)上,基于 Dubbo 的服務(wù)發(fā)布流程與上述過程非常類似。我們在后面的內(nèi)容中會做進(jìn)一步的分析。
服務(wù)引用
相較服務(wù)發(fā)布,服務(wù)的引用是一個(gè)導(dǎo)入(Import)的過程,整體流程如下圖所示。
圖片
從圖中,我們可以看到服務(wù)引用流程與服務(wù)發(fā)布流程呈對稱結(jié)構(gòu),所包含的組件有:
- 調(diào)用啟動(dòng)器
調(diào)用啟動(dòng)器和發(fā)布啟動(dòng)器是對應(yīng)的,這里不再重復(fù)介紹。
- 動(dòng)態(tài)代理
在服務(wù)引用過程中,動(dòng)態(tài)代理的作用就是確保遠(yuǎn)程調(diào)用過程的透明化,即開發(fā)人員可以使用本地對象來完成對遠(yuǎn)程對象的處理。
圖片
- 調(diào)用管理器
和發(fā)布管理器相比,調(diào)用管理器的核心功能是提供了一種緩存機(jī)制,從而確保服務(wù)調(diào)用者可以根據(jù)保存在本地的遠(yuǎn)程服務(wù)地址信息來發(fā)起調(diào)用。
- 協(xié)議客戶端
和協(xié)議服務(wù)器相對應(yīng),協(xié)議客戶端會創(chuàng)建與服務(wù)端的網(wǎng)絡(luò)連接,發(fā)起請求并獲取結(jié)果。
- 注冊中心
注冊中心在這里的作用是提供查詢服務(wù)定義元數(shù)據(jù)的入口。
上述的服務(wù)引用流程同樣有一定的共性,可以通過轉(zhuǎn)化映射到某個(gè)具體的框架上。事實(shí)上,基于 Dubbo 的服務(wù)引用流程與上述過程也比較類似。
相比于服務(wù)發(fā)布,服務(wù)引用的實(shí)現(xiàn)過程通常會更加復(fù)雜一點(diǎn)。這點(diǎn)在 Dubbo 框架中體現(xiàn)的就比較明顯。接下來,我們就以 Dubbo 框架為例,分析它的服務(wù)發(fā)布和引用流程。
Dubbo 中的服務(wù)發(fā)布和引用
Dubbo 中的服務(wù)發(fā)布
Dubbo 中的服務(wù)發(fā)布基本上遵循了我們前面所抽象的服務(wù)發(fā)布流程,但也添加了一些優(yōu)化措施。體現(xiàn)在兩方面,一方面是發(fā)布的時(shí)效性,另一方面是發(fā)布的作用范圍。
我們先來討論 Dubbo 暴露服務(wù)的兩種時(shí)效,一種是延遲暴露,一種是正常暴露。
圖片
你可能會問,Dubbo 為什么要考慮發(fā)布時(shí)效這個(gè)問題呢?主要是為了提供平滑發(fā)布機(jī)制。如果 Dubbo 服務(wù)本身還沒有完全啟動(dòng)成功,那這時(shí)候?qū)ν獗┞斗?wù)是沒有意義的,我們可以通過使用 delay 參數(shù)來設(shè)置延遲時(shí)間,從而確保服務(wù)在發(fā)布的時(shí)間點(diǎn)上就是可用的。
另一方面,Dubbo 中提供了四種發(fā)布作用范圍的選項(xiàng)。
圖片
可以看到,如果我們把 scope 配置為 none,則意味著不會發(fā)布這個(gè) Dubbo 服務(wù);如果配置成 local,則說明該服務(wù)只會在當(dāng)前 JVM 中進(jìn)行暴露,從而可以提高服務(wù)調(diào)用的效率;如果配置 scope 為 remote,那么該服務(wù)就會進(jìn)行遠(yuǎn)程暴露;而如果不配置為以上任何一種情況,那么 Dubbo 既會暴露本地服務(wù)又會暴露遠(yuǎn)程服務(wù)。
Dubbo 中的服務(wù)引用
正如前面所提到的,與服務(wù)發(fā)布相比,Dubbo 等分布式服務(wù)框架中的服務(wù)引用整體過程會更加復(fù)雜一點(diǎn)。原因在于,在服務(wù)引用過程中,因?yàn)樗{(diào)用的服務(wù)一般都會部署成集群模式,勢必會涉及負(fù)載均衡。而如果調(diào)用超時(shí)或失敗,還會采用集群容錯(cuò)機(jī)制。
接下來,我們來看 Dubbo 中如何實(shí)現(xiàn)服務(wù)引用。我們在 ReferenceConfig 的 init 方法中找到了如下所示的 createProxy 方法。這個(gè) createProxy 方法是理解 Dubbo 服務(wù)引用的關(guān)鍵入口,我們梳理了它的代碼結(jié)構(gòu)。
private T createProxy(Map<String, String> map) {
if (isJvmRefer) {
//生成本地引用 URL,使用 injvm 協(xié)議進(jìn)行本地調(diào)用
} else {
//URL 不為空,執(zhí)行點(diǎn)對點(diǎn)調(diào)用
} else {
//加載注冊中心 URL
}
if (urls.size() == 1) {
//單個(gè)服務(wù)提供者,直接調(diào)用
} else {
//多個(gè)服務(wù)提供者,則構(gòu)建集群
if (registryURL != null) {
// 如果注冊中心鏈接不為空,則將通過注冊中心執(zhí)行集群調(diào)用 } else {
//反之,直接執(zhí)行集群調(diào)用
}
}
// 生成服務(wù)代理類
return (T) proxyFactory.getProxy(invoker);
}
在上述流程中,我們明確了 Dubbo 中服務(wù)引用的幾種不同場景,這些場景對調(diào)用管理器的功能做了擴(kuò)展,但整體流程是一致的。
總結(jié)
在遠(yuǎn)程過程調(diào)用的實(shí)現(xiàn)思路上,主要包括服務(wù)發(fā)布和服務(wù)引用兩大維度。今天我們圍繞遠(yuǎn)程服務(wù)的發(fā)布和引用流程展開了詳細(xì)的討論,這部分內(nèi)容是我們構(gòu)建分布式系統(tǒng)的基本前提。同時(shí),基于這套服務(wù)發(fā)布和引用流程,我們對 Dubbo 這款主流的分布式服務(wù)框架如何進(jìn)行遠(yuǎn)程/本地服務(wù)暴露、如何實(shí)現(xiàn)對遠(yuǎn)程服務(wù)的調(diào)用過程也進(jìn)行了分析。
圖片