URL 簡介
在闡述地址推送性能的具體優化之前,我們有必要先了解一下與之息息相關的內容 --- 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:[email protected]:8080/list?version=1.0.0
ftp://username:[email protected]: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<br>
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 zk
-
username/password:用戶名/密碼
-
host/port:主機/埠
-
path:介面名稱
-
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×tamp=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×tamp=1545721827784
描述一個消費者
可以說,任意的一個領域中的一個實作都可以認為是一類 URL,dubbo 使用 URL 來統一描述了元資料,配置資訊,貫穿在整個框架之中,
Dubbo 2.7
URL 結構
在 Dubbo 2.7 中,URL 的結構非常簡單,一個類就涵蓋了所有內容,如下圖所示,

地址推送模型
接下來我們再來看看 Dubbo 2.7 中的地址推送模型方案,主要性能問題由下列程序引起,

上圖中主要的流程為
1、用戶新增/洗掉DemoService的某個具體Provider實體(常見于擴容縮容、網路波動等原因)
2、ZooKeeper將DemoService下所有實體推送給Consumer端
3、Consumer端根據Zookeeper推送的資料重新全量生成URL
根據該方案可以看出在Provider實體數量較小時,Consumer端的影響比較小,但當某個介面有大量Provider實體時,便會有大量不必要的URL創建程序,
而Dubbo 3.0中則主要針對上述推送流程進行了一系列的優化,接下來我們便對其進行具體的講解,
Dubbo 3.0
URL 結構
當然,地址推送模型的優化依然離不開 URL 的優化,下圖是Dubbo 3.0中優化地址推送模型的程序中使用的新的URL結構,

根據上圖我們可以看出,在 Dubbo 2.7 的 URL 中的幾個重要屬性在 Dubbo 3.0 中已經不存在了,取而代之的是 URLAddress 和 URLParam 兩個類,原來的 parameters 屬性被移動到了 URLParam 中的 params,其他的屬性則移動到了 URLAddress 及其子類中,
再來介紹 URL 新增的 3 個子類,其中 InstanceAddressURL 屬于應用級介面地址,本篇章中不做介紹,
而 ServiceConfigURL 及 ServiceAddressURL 主要的差別就是,ServiceConfigURL 是程式讀取組態檔時生成的 URL,而 ServiceAddressURL 則是注冊中心推送一些資訊(如 providers)過來時生成的 URL,
在這里我們順便提一下為什么會有 DubboServiceAddressURL 這個子類,按照目前的結構來看,ServiceAddressURL 只有這一個子類,所以完全可以將他們兩個的屬性全都放到 ServiceAddressURL 中,那么為什么還要有這個子類呢?其實是 Dubbo 3.0 為了兼容 HSF 框架所設計的,抽象出了一個 ServiceAddressURL,而 HSF 框架則可以繼承這個類,使用 HSFServiceAddressURL,當然,這個類目前沒有體現出來,所以此處我們簡單一提,不過多講解,
那么,我們接下來就討論一下 Dubbo 3.0 為什么要改為此種資料結構,并且該結構和地址推送模型的優化有何關聯性吧!
地址推送模型的優化
URL 結構上的優化
我們在上小節中的類圖里看到雖然原來的屬性都被移到了 URLAddress 和 URLParam 里,但是 URL 的子類依然多了幾個屬性,這幾個屬性自然也是為了優化而新增的,那么這里就講講這幾個屬性的作用,
ServiceConfigURL:這個子類中新增了 attribute 這個屬性,這個屬性主要是針對 URLParam 的 params 做了冗余,僅僅只是將 value 的型別從 String 改為了 Object,減少了代碼中每次獲取 parameters 的格式轉換消耗,
ServiceAddressURL:這個子類及其對應的其他子類中則新增了 overrideURL 和 consumerURL 屬性,其中 consumerURL 是針對 consumer 端的配置資訊,overrideURL 則是在 Dubbo Admin 上進行動態配置時寫入的值,當我們呼叫 URL 的 getParameter() 方法時,優先級為 overrideURL > consumerURL > urlParam,在 Dubbo 2.7 時,動態配置屬性會替換 URL 中的屬性,及當你有大量 URL 時消耗也是不可忽視的,而此處的 overrideURL 則避免了這種消耗,因為所有 URL 都會共同使用同一個物件,
多級快取
快取是 Dubbo 3.0 在 URL 上做的優化的重點,同時這部分也是直接針對地址推送模型所做的優化,那么接下來我們就開始來介紹一下多級快取的具體實作,
首先,多級快取主要體現在 CacheableFailbackRegistry 這個類之中,它直接繼承于 FailbackRegistry,以 Zookeeper 為例,我們看看 Dubbo 2.7 和 Dubbo 3.0 繼承結構的區別,

可以看到在 CacheableFailbackRegistry 快取中,我們新增了 3 個快取屬性 stringAddress,stringParam 和 stringUrls,我們通過下圖來描述這 3 個快取的具體使用場景,

在該方案下,我們使用了 3 個緯度的快取資料(URL 字串快取、URL 地址快取、URL 引數快取),這樣一來,在大部分情況下都能有效利用到快取中的資料,減少了 Zookeeper 重復通知的消耗,
延遲通知
除了上面提到的優化之外,其實另外還有兩個小小的優化,
第一個是決議 URL 時可以直接使用編碼后的 URL 字串位元組進行決議,而在 Dubbo 2.7 中,所有編碼后的 URL 字串都需要經過解碼才可以正常決議為 URL 物件,該方式也直接減少了 URL 解碼程序的開銷,
第二個則是 URL 變更后的通知機制增加了延遲,下圖以Zookeeper為例講解了實作細節,

在該方案中,當 Consumer 接收 Zookeeper 的變更通知后會主動休眠一段時間,而這段時間內的變更在休眠結束后只會保留最后一次變更,Consumer 便會使用最后一次變更來進行監聽實體的更新,以此方法來減少大量 URL 的創建開銷,
字串重用
在舊版本實作中,不同的 URL 中屬性相同的字串會存盤在堆內不同的地址中,如 protocol、path 等,當有大量 provider 的情況下,Consumer 端的堆內會存在大量的重復字串,導致記憶體利用率低下,所以此處提供了另一個優化方式,即字串重用,
而它的實作方式也非常的簡單,讓我們來看看對應的代碼片段,
public class URLItemCache {
private static final Map<String, String> PATH_CACHE = new LRUCache<>(10000);
private static final Map<String, String> PROTOCOL_CACHE = new ConcurrentHashMap<>();
// 省略無關代碼片段
public static String checkProtocol(String _protocol) {
if (_protocol == null) {
return _protocol;
}
String cachedProtocol = PROTOCOL_CACHE.putIfAbsent(_protocol, _protocol);
if (cachedProtocol != null) {
return cachedProtocol;
}
return _protocol;
}
public static String checkPath(String _path) {
if (_path == null) {
return _path;
}
String cachedPath = PATH_CACHE.putIfAbsent(_path, _path);
if (cachedPath != null) {
return cachedPath;
}
return _path;
}
}
由如上代碼片段可以得知,字串重用即為簡單地使用了 Map 來存盤對應的快取值,當你使用了相同的字串時,便會從 Map 中獲取早已存在的物件回傳給呼叫方,由此便可以減少堆記憶體中重復的字串數以達到優化的效果,
優化結果
這里優化結果我參考了《Dubbo 3.0 前瞻:服務發現支持百萬集群,帶來可伸縮微服務架構》這篇文章中的兩副圖來說明,下圖模擬了在220萬個 Provider 介面的情況下,介面資料不斷變更導致的 Consumer 端的消耗,我們看到整個 Consumer 端幾乎被 Full GC 占滿了,嚴重影響了性能,

那么我們再來看看 Dubbo 3.0 中對 URL 進行優化后同一個環境下的壓測結果,如下圖所示,

我們明顯可以看到 Full GC 的頻率減少到了只有 3 次,大大提升了性能,當然,該文章中還有其他方面的對比,此處便不一一參考了,感興趣的讀者可以自行去閱讀該文章,
歡迎在 https://github.com/apache/dubbo 給 Dubbo Star,
搜索關注官方微信公眾號:Apache Dubbo,了解更多業界最新動態,掌握大廠面試必備 Dubbo 技能
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/541174.html
標籤:其他
