Dubbo 中的 URL 統一模型

作者:徐靖峰
來源:https://www.cnkirito.moe/dubbo-url/

定義

在不談及 dubbo 時,我們大多數人對 URL 這個概念並不會感到陌生。統一資源定位器 (RFC1738――Uniform Resource Locators (URL))應該是最廣為人知的一個 RFC 規範,它的定義也非常簡單

因特網上的可用資源可以用簡單字符串來表示,該文檔就是描述了這種字符串的語法和語義。而這些字符串則被稱為:“統一資源定位器”(URL)

一個標準的 URL 格式至多可以包含如下的幾個部分

protocol://username:password@host:port/path?key=value&key=value

一些典型 URL

http://www.facebook.com/friends?param1=value1&param2=value2
https://username:password@10.20.130.230:8080/list?version=1.0.0
ftp://username:password@192.168.1.7:21/1/read.txt

當然,也有一些不太符合常規的 URL,也被歸類到了 URL 之中

192.168.1.3:20880
url protocol = null, url host = 192.168.1.3, port = 20880, url path = null
file:///home/user1/router.js?type=script
url protocol = file, url host = null, url path = home/user1/router.js
file://home/user1/router.js?type=script

url protocol = file, url host = home, url path = user1/router.js
file:///D:/1/router.js?type=script


url protocol = file, url host = null, url path = D:/1/router.js
file:/D:/1/router.js?type=script
同上 file:///D:/1/router.js?type=script
/home/user1/router.js?type=script
url protocol = null, url host = null, url path = home/user1/router.js
home/user1/router.js?type=script
url protocol = null, url host = home, url path = user1/router.js

Dubbo 中的 URL

在 dubbo 中,也使用了類似的 URL,主要用於在各個擴展點之間傳遞數據,組成此 URL 對象的具體參數如下:

protocol:一般是 dubbo 中的各種協議 如:dubbo thrift http zkusername/password:用戶名/密碼host/port:主機/端口path:接口名稱parameters:參數鍵值對

public URL(String protocol, String username, String password, String host, int port, String path, Map<string> parameters) {
if ((username == null || username.length() == 0)
&& password != null && password.length() > 0) {
throw new IllegalArgumentException("Invalid url, password without username!");
}
this.protocol = protocol;
this.username = username;
this.password = password;
this.host = host;
this.port = (port < 0 ? 0 : port);
this.path = path;
// trim the beginning "/"
while(path != null && path.startsWith("/")) {
path = path.substring(1);
}
if (parameters == null) {
parameters = new HashMap<string>();
} else {
parameters = new HashMap<string>(parameters);
}
this.parameters = Collections.unmodifiableMap(parameters);
}
/<string>/<string>/<string>

可以看出,dubbo 認為 protocol,username,passwored,host,port,path 是主要的 URL 參數,其他鍵值對村房子啊 parameters 之中。

一些典型的 Dubbo URL

dubbo://192.168.1.6:20880/moe.cnkirito.sample.HelloService?timeout=3000
描述一個 dubbo 協議的服務
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=1214&qos.port=33333&timestamp=1545721981946
描述一個 zookeeper 註冊中心
consumer://30.5.120.217/org.apache.dubbo.demo.DemoService?application=demo-consumer&category=consumers&check=false&dubbo=2.0.2&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=1209&qos.port=33333&side=consumer&timestamp=1545721827784
描述一個消費者

可以說,任意的一個領域中的一個實現都可以認為是一類 URL,dubbo 使用 URL 來統一描述了元數據,配置信息,貫穿在整個框架之中。

URL 相關的生命週期

解析服務

基於 dubbo.jar 內的 META-INF/spring.handlers 配置,Spring 在遇到 dubbo 名稱空間時,會回調 DubboNamespaceHandler。

所有 dubbo 的標籤,都統一用 DubboBeanDefinitionParser 進行解析,基於一對一屬性映射,將 XML 標籤解析為 Bean 對象。

在 ServiceConfig.export() 或 ReferenceConfig.get() 初始化時,將 Bean 對象轉換 URL 格式,所有 Bean 屬性轉成 URL 的參數。

然後將 URL 傳給協議擴展點,基於擴展點自適應機制,根據 URL 的協議頭,進行不同協議的服務暴露或引用。

暴露服務

1. 只暴露服務端口:

在沒有註冊中心,直接暴露提供者的情況下,ServiceConfig 解析出的 URL 的格式為:dubbo://service-host/com.foo.FooService?version=1.0.0。

基於擴展點自適應機制,通過 URL 的 dubbo:// 協議頭識別,直接調用 DubboProtocol的 export()方法,打開服務端口。

2. 向註冊中心暴露服務:

在有註冊中心,需要註冊提供者地址的情況下,ServiceConfig 解析出的 URL 的格式為: registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0"),

基於擴展點自適應機制,通過 URL 的 registry:// 協議頭識別,就會調用 RegistryProtocol 的 export() 方法,將 export 參數中的提供者 URL,先註冊到註冊中心。

再重新傳給 Protocol 擴展點進行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0,然後基於擴展點自適應機制,通過提供者 URL 的 dubbo:// 協議頭識別,就會調用 DubboProtocol 的 export() 方法,打開服務端口。

引用服務

1. 直連引用服務:

在沒有註冊中心,直連提供者的情況下,ReferenceConfig 解析出的 URL 的格式為:dubbo://service-host/com.foo.FooService?version=1.0.0。

基於擴展點自適應機制,通過 URL 的 dubbo:// 協議頭識別,直接調用 DubboProtocol 的 refer() 方法,返回提供者引用。

2. 從註冊中心發現引用服務:

在有註冊中心,通過註冊中心發現提供者地址的情況下,ReferenceConfig 解析出的 URL 的格式為:registry://registry-host/org.apache.dubbo.registry.RegistryService?refer=URL.encode("consumer://consumer-host/com.foo.FooService?version=1.0.0")。

基於擴展點自適應機制,通過 URL 的 registry:// 協議頭識別,就會調用 RegistryProtocol 的 refer() 方法,基於 refer 參數中的條件,查詢提供者 URL,如: dubbo://service-host/com.foo.FooService?version=1.0.0。

基於擴展點自適應機制,通過提供者 URL 的 dubbo:// 協議頭識別,就會調用 DubboProtocol 的 refer() 方法,得到提供者引用。

然後 RegistryProtocol 將多個提供者引用,通過 Cluster 擴展點,偽裝成單個提供者引用返回。

URL 統一模型的意義

對於 dubbo 中的 URL,有人理解為配置總線,有人理解為統一配置模型,說法雖然不同,但都是在表達一個意思,這樣的 URL 在 dubbo 中被當做是公共契約,所有擴展點參數都包含 URL 參數,URL 作為上下文信息貫穿整個擴展點設計體系。

在沒有 URL 之前,只能以字符串傳遞參數,不停的解析和拼裝,導致相同類型的接口,參數時而 Map, 時而 Parameters 類包裝:

export(String url)
createExporter(String host, int port, Parameters params)

使用 URL 一致性模型:

export(URL url)
createExporter(URL url)

在最新的 dubbo 代碼中,我們可以看到大量使用 URL 來進行上下文之間信息的傳遞,這樣的好處是顯而易見的:

使得代碼編寫者和閱讀者能夠將一系列的參數聯繫起來,進而形成規範,使得代碼易寫,易讀。可擴展性強,URL 相當於參數的集合(相當於一個 Map),他所表達的含義比單個參數更豐富,當我們在擴展代碼時,可以將新的參數追加到 URL 之中,而不需要改變入參,返參的結構。統一模型,它位於 org.apache.dubbo.common 包中,各個擴展模塊都可以使用它作為參數的表達形式,簡化了概念,降低了代碼的理解成本。

如果你能夠理解 final 契約和 restful 契約,那我相信你會很好地理解 URL 契約。契約的好處我還是囉嗦一句:大家都這麼做,就形成了默契,溝通是一件很麻煩的事,統一 URL 模型可以省去很多溝通成本,這邊是 URL 統一模型存在的意義。