這是 why 技術的第 28 篇原創文章git
以前在《Dubbo 一致性哈希負載均衡的源碼和 Bug,瞭解一下?》[1]中寫到了我發現了一個 Dubbo 一致性哈希負載均衡算法的 Bug。github
對於解決方案我是這樣寫的:web
特別簡單,把獲取identityHashCode的方法從System.identityHashCode(invokers)修改成invokers.hashCode()便可。此方案是我提的issue裏面的評論,這裏System.identityHashCode和 hashCode之間的聯繫和區別就不進行展開講述了,不清楚的你們能夠自行了解一下。
算法
我說:這裏 System.identityHashCode 和 hashCode 之間的聯繫和區別就不進行展開講述了,不清楚的你們能夠自行了解一下。apache
可是有讀者在後臺問我詳細緣由,我已經和他聊清楚了。服務器
再加上這個BUG 已於近期修復了,且只用了一行代碼就修復了,那我就寫一下解決方案,以及背後的原理。負載均衡
便是對以前文章的一個補充,也是一個獨立的知識點。編輯器
因此本文主要是回答下面這三個問題:ide
1.什麼是 System.identityHashCode?工具
2.什麼是 hashCode?
3.爲何一行代碼就修復了這個 BUG?
注:本文 Dubbo 源碼 2.7.4.1 版本。若是閱讀過《Dubbo 一致性哈希負載均衡的源碼和 Bug,瞭解一下?》[2]能夠更好的理解這篇文章。可是沒有讀過也不會影響閱讀。
先經過一個前情回顧,引出本文所要分享的內容。
Dubbo 一致性哈希負載均衡算法的設計初衷應該是若是沒有服務上下線的操做,後續請求根據已經映射好的哈希環進行處理,不須要從新映射。
然而我在研究其源碼時,我發現實際狀況是即便在服務端沒有上下線操做的時候,一致性哈希負載均衡算法每次都須要從新進行 hash 環的映射。
實際狀況與設計初衷不符。
因而給 Dubbo 提了一個 issue,地址以下:
https://github.com/apache/dubbo/issues/5429
如下內容是對該 issue 的詳細說明:
在 Dubbo 對應的源碼中,只須要一行代碼。就能夠判斷是否有服務上下線的操做:
就是下面這一行代碼:
int identityHashCode = System.identityHashCode(invokers);
經過判斷 invokers(服務提供方 List 集合)的 identityHashCode 是否發生了變化,從而判斷是否有服務上下線的操做。
可是這行代碼,在Dubbo2.7.0 版本以後就失效了。
問題出在 Dubbo2.7.0 版本引入的新特性之一:標籤路由。
其對應的源碼以下:
org.apache.dubbo.rpc.cluster.router.tag.TagRouter#filterInvoker
經過源碼能夠看出:在 TagRouter 中的 stream 操做,改變了 invokers,致使每次調用時其 System.identityHashCode(invokers)返回的值不同。
因此每次調用都會進行哈希環的映射操做,在服務節點多,虛擬節點多的狀況下必定會有性能問題。
該問題對應的 PR 連接以下:
https://github.com/apache/dubbo/pull/5440
修復方法也是特別簡單:把獲取 identityHashCode 的方法從 System.identityHashCode(invokers)修改成 invokers.hashCode()便可。以下圖所示:
爲何把獲取 identityHashCode 的方法從 System.identityHashCode(invokers)修改成 invokers.hashCode()就能夠了呢?
要回答這個問題,咱們首先得明白什麼是 identityHashCode?什麼是 hashCode?
**什麼是 identityHashCode?**咱們看看 API 裏面的註釋:
返回與默認方法 hashCode()返回的給定對象相同的哈希碼,不管給定對象的類是否覆蓋了 hashCode()。空引用的哈希碼爲零。
另外關於 identityHashCode 還有下面的三條規則:
1.因此若是兩個對象 A == B,那麼 A、B 的 System.identityHashCode() 一定相等;
2.若是兩個對象的 System.identityHashCode() 不相等,那他們一定不是同一個對象;
3.可是若是兩個對象的 System.identityHashCode()相等,並不保證 A==B,由於 identityHashCode 的底層實現是基於一個僞隨機數實現的。
什麼是 hashCode? 你們應該都比較熟了,仍是看 API 上的註釋:
再結合下面兩個示例代碼,深刻理解。
示例一:WhyHashCodeDto沒有重寫 hashCode()方法,因此 identityHashCode 和 hashCode 的值是同樣的:
示例二:以下所示,String 是重寫了 hashCode()的方法,因此在下面的例子中 identityHashCode 不等於 hashCode:
有了前面的知識鋪墊,咱們就能夠回到 Dubbo 的一致性哈希算法的場景中去了。
在 PR 中有一行註釋是這樣寫的:
using the hashcode of list to compute the hash only pay attention to the elements in the list
咱們應該只注意 list 裏面的元素就能夠了。 而這個 list 裏面的元素,就是一個個的服務提供方。
因此,在 Dubbo 的一致性哈希算法的場景中,咱們只須要關心 List 裏面的服務提供方是否有上下線的操做,而不關心這個 List 是否每次都是新的。
咱們再回到源碼中,結合源碼,而後簡化源碼:
把上面的源碼抽離一下,簡化一下,以下:
filterInvoker 方法是根據條件過濾 invokers,並返回一個 List。而我傳入的條件是,過濾出 invokers 中 invoker 大於 0 的數據:
filterInvoker(invokers, invoker -> invoker > 0);
執行結果以下:
能夠看到通過 filterInvoker 方法後,因爲集合中全部的元素都知足條件,因此過濾先後,集合中的元素並無發生變化,致使 hashCode 沒有變化。可是因爲裝元素的容器(集合)已經不是原來的容器了,因此 identityHashCode 發生了變化。
"由於集合中的元素沒有發生變化,致使 hashCode 沒有變化。"這句話的理由是什麼?
由於 List 重寫了 hashCode()方法,其算出的 hashCode 只和 list 中的元素相關:
通過 filterInvoker 方法後元素仍是【1,2,3】,與過濾以前同樣,因此 hashCode 沒有變。
"因爲裝元素的容器(集合)已經不是原來的容器了,因此 identityHashCode 發生了變化。"這句話的理由又是什麼?
能夠看到在源碼中,Collectors.toList()方法會 new List。因此都是新的,那麼每次的 identityHashCode 必不相同。
上面的示例代碼,模擬的是沒有服務上下線的操做。
接下來,咱們模擬一下服務下線的場景:
此次傳入的過濾條件爲,過濾出 invokers 中 invoker 大於 1 的數據:
filterInvoker(invokers, invoker -> invoker > 1);
輸出結果以下:
能夠看到,過濾後的集合中只有【2,3】了,因此 hashCode 發生了變化。
上面的示例在 Dubbo 的一致性哈希算法的場景中至關於 1 號服務器下線了,服務列表發生了變化,須要從新進行哈希環的映射。
對應源碼以下(PR 提交的源碼):
由於在標號爲 ① 處獲得的 invokersHashCode 和以前的不同了,因此在標號爲 ② 處判斷條件爲真,進入標號爲 ③ 的代碼處,從新進行 Hash 環的映射,並選擇某個虛擬節點執行該請求。
經過上面模擬的兩個示例,再結合下面的源碼:
也就回答了爲何把上圖中編號爲 ① 處的代碼替換爲標號爲 ② 的代碼,這一行代碼就能修復這個 Bug,核心思想就是隻關心 List 集合裏面的元素變化,而不關心 List 集合容器是否發生變化。
最開始找到這個 BUG 的時候,我本身也是有一套解決方案的。思路也是隻關心 List 裏面的元素,而不關心 List 這個容器,可是實現方式比較複雜,改動點較多,還須要寫一個工具類。
可是看到 issue 下面的這個評論,
我才一下回過神來,原來一行代碼就能代替我寫的工具類了啊。而對於這個知識點,我以前實際上是知道的。
我反思了一下本身爲何沒有想到這個方案。
其實就是對於已知道的知識點,掌握不夠深入致使的,沒有達到融會貫通的地步。知其然,也知其因此然,惋惜在須要使用的場景稍稍一變的狀況下,就想不起來了。
知道知識點,可是該用的時候卻記不起來,這種狀況其實挺常見的,那怎麼解決呢?
這篇文章就是個人解決方案,記錄下來嘛。就像高中的時候人手一本的錯題本,作錯的題,不會的題都抄下來嘛。沒事的時候翻一翻,總有下次碰到的時候。再次碰到時,就是"一雪前恥"的機會。
好了。
才疏學淺,不免會有紕漏,若是你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。
感謝您的閱讀,感謝您的關注。
以上。
歡迎關注公衆號【why 技術】,堅持輸出原創。願你我共同進步。
《Dubbo一致性哈希負載均衡的源碼和Bug,瞭解一下?》: https://juejin.im/post/5dedd263518825124b120298