向下相容特性是軟體開發系統的一個重要指標,它是指一個新的系統或者軟體能夠與舊的系統或軟體相容並正常運行。這意味著舊系統或軟體可以在新系統或軟體中使用,而不會出現問題。向下相容對於提高軟體或系統的可用性非常重要,因為它允許用戶在不更換舊系統或軟體的情況下使用新系統或軟體。 我們知道MacOS系統從Mo ...
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 技能