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