寫在前面
之前,很多小夥伴私信我:如何才能快速的掌握Dubbo的核心原理和原始碼。所以,我寫了一篇《我是如何在短期內快速掌握Dubbo的原理和原始碼的(純乾貨)?》。對於Dubbo的原始碼解析系列文章,我也在思考如何讓原始碼解析的文章變得更加簡單易懂,所以,我調整了寫Dubbo原始碼解析文章的策略,力求讓小夥伴們能夠以更簡單、易懂的方式徹底掌握Dubbo原始碼。今天,我們先說說Dubbo中的統一契約是如何實現的。
文章已收錄到:
https://github.com/sunshinelyz/technology-binghe
https://gitee.com/binghe001/technology-binghe
不得不說的URL
URL全稱為統一資源定位符,它能夠在網際網路中定位到唯一的一個網路地址。URL的格式如下所示。
protocol://username:password@host:port/path?key=value&key=value
其中,各個部分的簡要說明如下所示。
- protocol:URL的協議。最常見的協議就是HTTP和HTTPS,其他的還有FTP、WS、FILE、SMTP等。
- username:使用者名稱。
- password:密碼。
- host:主機,通常是域名或者IP地址。
- port:主機的埠號。
- path:請求的目標檔案的路徑。
- parameters:請求的具體引數資訊,這裡為key=value&key=value。
這就是我們網際網路中的URL的簡單說明。
那麼,在Dubbo內部,大量的方法接收的引數都是以URL進行封裝的,那麼,URL在Dubbo內部到底起到了什麼作用呢?我們繼續往下看。
Dubbo中的URL
總的來說,在Dubbo內部,服務提供者Provider會將自身的相關資訊封裝成URL註冊到Zookeeper或其他註冊中心中,從而對外暴露自己提供的服務。而服務消費者Consumer也會通過URL的形式向Zookeeper或其他註冊中心訂閱自己想要呼叫的服務。而在Dubbo的SPI實現中,URL又會參與擴充套件實現的邏輯處理。所以說,URL在Dubbo的實現中是非常重要的。也可以這麼說,Dubbo中的URL就是Dubbo的統一契約。
我們先來看一下Dubbo中的URL具體長什麼樣吧,通過除錯Dubbo自帶Provider的示例原始碼,我們可以看到在Dubbo中的URL如下所示。
dubbo://192.168.175.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=15012&release=&side=provider×tamp=1610857629484
這也是Provider註冊到Zookeeper或者其他註冊中心的資訊。各個部分的說明如下所示。
- dubbo:使用的是dubbo協議。
- host:主機的IP地址為192.168.175.1。
- port:埠號為20880。
- path:這裡的請求路徑為:org.apache.dubbo.demo.DemoService
- parameters:請求的引數資訊,這裡為:anyhost=true&application=dubbo-demo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=15012&release=&side=provider×tamp=1610857629484。
既然Dubbo是向Zookeeper或其他註冊中心註冊這些資訊的,那Dubbo內部是如何對URL進行封裝的呢。
在dubbo-common模組中,有一個URL類專門用於封裝URL,如下所示。
在URL類中,我們來看一個核心建構函式,如下所示。
public URL(String protocol,
String username,
String password,
String host,
int port,
String path,
Map<String, String> parameters,
Map<String, Map<String, String>> methodParameters) {
if (StringUtils.isEmpty(username)
&& StringUtils.isNotEmpty(password)) {
throw new IllegalArgumentException("Invalid url, password without username!");
}
this.protocol = protocol;
this.username = username;
this.password = password;
this.host = host;
this.port = Math.max(port, 0);
this.address = getAddress(this.host, this.port);
// trim the beginning "/"
while (path != null && path.startsWith("/")) {
path = path.substring(1);
}
this.path = path;
if (parameters == null) {
parameters = new HashMap<>();
} else {
parameters = new HashMap<>(parameters);
}
this.parameters = Collections.unmodifiableMap(parameters);
this.methodParameters = Collections.unmodifiableMap(methodParameters);
}
可以看到,Dubbo對於URL的核心封裝,基本與網際網路中的URL封裝是一致的。
在Dubbo的dubbo-common模組提供了處理URL的工具類:URLBuilder和URLStrParser。如下所示。
這兩個類的實現還是比較簡單的,小夥伴們可以自行閱讀Dubbo的原始碼。
接下來,我們一起來看看在Dubbo內部,URL是如何實現統一契約的?
Dubbo中URL的實際應用
這裡,我們主要通過三方面來簡單聊聊URL在Dubbo內部的實際應用:
- URL在SPI中的應用。
- URL在服務註冊中的應用。
- URL在服務發現中的應用。
URL在SPI中的應用
稍微瞭解過Dubbo的小夥伴都知道,Dubbo具有高度的可擴充套件性,而這種擴充套件性是基於Dubbo自身的SPI來實現的。在Dubbo實現的SPI中,URL又起到了非常重要的作用。
在Dubbo SPI的實現中,一個典型的場景就是被@Adaptive註解修飾的介面方法,例如,在dubbo-registry-api 模組中的RegistryFactory介面中的getRegistry()方法上被@Adaptive({"protocol"})註解修飾。如下所示。
說明RegistryFactory介面中的getRegistry()方法是一個介面卡方法,Dubbo在執行的過程中,會為getRegistry()方法動態生成RegistryFactory$Adaptive
型別。例如,生成的RegistryFactory$Adaptive
型別如下所示。
public class RegistryFactory$Adaptive
implements RegistryFactory {
public Registry getRegistry(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("");
org.apache.dubbo.common.URL url = arg0;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("");
RegistryFactory extension = (RegistryFactory) ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(extName);
return extension.getRegistry(arg0);
}
}
這段程式碼相對來說還是比較容易理解的,生成的RegistryFactory$Adaptive會自動實現getRegistry()方法,在getRegistry()方法中,會獲取URL中的protocol引數來確定URL的協議,如果獲取的protocol為空,則使用預設的dubbo協議,有了這個協議,就能夠通過SPI動態載入具體的擴充套件實現類。
我們在Dubbo的dubbo-registry-api模組中找到RegistryProtocol類,如下所示。
找到其中的getRegistry()方法並打上斷點,如下所示。
接下來,debug啟動Dubbo的Provider示例,如下所示。
可以看到,此時使用的protocol協議為zookeeper。有關Dubbo中SPI的實現,我們後面再詳細剖析,今天,小夥伴們有個大致的瞭解即可。
URL在服務註冊中的應用
在Dubbo中的服務註冊實現中,URL同樣起到了非常重要的作用。這裡,我使用的註冊中心是Zookeeper,所以,我們在dubbo-registry-zookeeper模組中找到ZookeeperRegistry類,如下所示。
找到其中的doRegister()方法,打上斷點,如下所示。
debug啟動Dubbo自帶的provider示例,如下所示。
可以看到,在註冊到Zookeeper中的URL中,包含了protocol協議、host主機名、port埠號、path請求路徑,parameters引數等資訊。
URL在服務發現中的應用
Dubbo中服務的消費者Consumer在啟動時,會向Zookeeper註冊中心訂閱自身需要呼叫的服務,那具體是如何通過URL訂閱的呢?我們同樣在dubbo-registry-zookeeper模組中的ZookeeperRegistry類中找到doSubscribe()方法。在doSubscribe()方法中打上斷點,如下所示。
啟動Dubbo自帶的Consumer示例,如下所示。
我們可以看到,Dubbo的Consumer會向Zookeeper傳入如下引數進行服務的訂閱操作。
consumer://192.168.175.1/org.apache.dubbo.demo.DemoService?application=dubbo-demo-annotation-consumer&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=15184&side=consumer&sticky=false×tamp=1610860963037
其中的protocol為consumer,表示訂閱協議。category表示要訂閱的分類,這裡是providers,configurators,routers三個分類。interface表示要訂閱的介面服務,這裡是org.apache.dubbo.demo.DemoService。methods表示要訂閱的方法,這裡是sayHello,sayHelloAsync。
還有一點需要注意的是:在服務註冊的過程中,Dubbo會將URL轉化為Zookeeper路徑將資訊註冊到Zookeeper中;在服務發現的過程中,Dubbo會將URL轉化為Zookeeper路徑,從而監聽Zookeeper目錄的變化來訂閱相關的服務。
總之,在Dubbo內部通過URL實現了統一的契約。你學會了嗎?
推薦閱讀
好了,今天就到這兒吧,我是冰河,大家有啥問題可以在下方留言,也可以加我微信:sun_shine_lyz,一起交流技術,一起進階,一起牛逼~~