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