【原創】夠強!一行程式碼就修復了我提的Dubbo的Bug。
這是 why 技術的第 28 篇原創文章
之前在《Dubbo 一致性雜湊負載均衡的原始碼和 Bug,瞭解一下?》中寫到了我發現了一個 Dubbo 一致性雜湊負載均衡演算法的 Bug。
對於解決方案我是這樣寫的:
特別簡單,把獲取identityHashCode的方法從System.identityHashCode(invokers)修改為invokers.hashCode()即可。此方案是我提的issue裡面的評論,這裡System.identityHashCode和 hashCode之間的聯絡和區別就不進行展開講述了,不清楚的大家可以自行了解一下。
我說:這裡 System.identityHashCode 和 hashCode 之間的聯絡和區別就不進行展開講述了,不清楚的大家可以自行了解一下。
但是有讀者在後臺問我詳細原因,我已經和他聊清楚了。
再加上這個BUG 已於近期修復了,且只用了一行程式碼就修復了,那我就寫一下解決方案,以及背後的原理。
即是對之前文章的一個補充,也是一個獨立的知識點。
所以本文主要是回答下面這三個問題:
1.什麼是 System.identityHashCode?
2.什麼是 hashCode?
3.為什麼一行程式碼就修復了這個 BUG?
注:本文 Dubbo 原始碼 2.7.4.1 版本。如果閱讀過《Dubbo 一致性雜湊負載均衡的原始碼和 Bug,瞭解一下?》可以更好的理解這篇文章。但是沒有讀過也不會影響閱讀。
前情回顧
先通過一個前情回顧,引出本文所要分享的內容。
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。
這是 why 技術的第 28 篇原創文章 之前在《Dubbo 一致性雜湊負載均衡的原始碼和 Bug,瞭解一下?》中寫到了我發現了一個 Dubbo 一致性雜湊負載均衡演算法的 Bug。 對於解決方案我是這樣寫的: 特別簡單,把獲取identityHashCode的方法從Syste
驚!女朋友用Python寫出幾行程式碼就監控了我的電腦,吃雞被發現了
今天帶給大家一個非常有意思的 python 程式,基於 itchat 實現微信控制電腦。你可以通過在微信傳送命令,來拍攝當前電腦的使用者,然後圖片會發送到你的微信上。甚至你可以傳送命令來遠端關閉電腦。 學習Python中有不明白推薦加入交流裙  
【原創】tarjan算法初步(強連通子圖縮點)
fin namespace 但是 申請 div 處理 sin point 沒有 【原創】tarjan算法初步(強連通子圖縮點) tarjan算法的思路不是一般的繞!!(不過既然是求強連通子圖這樣的回路也就可以稍微原諒了。。) 但是研究tarjan之前總得知道強連通分量是
【原創】IE11驚現無厘頭Crash BUG(三招搞死你的IE11,並提供可重現代碼)!
解決問題 html 窗口 前言 stat 錯誤 ont spa 環境 前言 很多人都知道我們在做FineUI控件庫,而且我們也做了超過 9 年的時間,在和瀏覽器無數次的交往中,也發現了多個瀏覽器自身的BUG,並公開出來方便大家查閱: 分享IE7一個神奇的BUG(不是
【原創】博客園重大Bug!管理員快來!!
bug 圖片 用戶 提示 服務 原創 博客 記錄 ref 事情的起因 今天在修改密碼時提示修改失敗(必須包含字母,數字,特殊字符),習慣性的查看下請求響應,如圖, 輕松獲取到改密碼的接口地址,以及請求方式。 查看POST請求參數 很顯然,應該是使用js把新密碼和舊密碼進
【原創】《矩陣的史詩級玩法》連載十一:逆矩陣的程式碼實現及其在45度地圖中的應用
從搜尋引擎過來這篇文章的朋友可能會有點失望,因為我沒在標題上說明是多少階矩陣的程式碼。不得不說,固定階數,並且還只是3階的求逆實在是太簡單了,上篇說初中生都能看懂。而任意高階數則需要藉助諸如克拉默法則一類的定理進行實現,並且可能還得嘗試用高斯消元法進行優化。然而這些我都沒去做
【原創】《矩陣的史詩級玩法》連載十三:基向量座標變換矩陣的程式碼實現
本篇我們把上篇最後提到的矩陣實現出來並替換之前45度地圖演示檔案的矩陣上。 var baseX = new Point(0.87, 0.5); //ex基向量 var baseY = new Point(-0.32, 0.94); //ey基向量 var matri
【原創】答《讀研or工作?對計算機類專業學習的看法》---如果再來一次,我不會讀研!
題記:謹以此文貢獻給所有本科非211,985,且立志在程式設計屆有所作為的人! 引言 這幾天,在園子裡看到一篇文章《讀研or工作?對計算機專業學習對看法》。坦白說,博主初看之下,就覺得略顯稚嫩,讀研和工作兩邊說好話。對此,博主有一些自己的見解,因此想談談。 觀點 我先說一下,自己的觀點。因為我自己是做java
【原創】大資料基礎之Spark(4)RDD原理及程式碼解析
一 簡介 spark核心是RDD,官方文件地址:https://spark.apache.org/docs/latest/rdd-programming-guide.html#resilient-distributed-datasets-rdds官方描述如下:重點是可容錯,可並行處理 Spark r
【原創】驚!史上最全的select加鎖分析(Mysql)
引言 大家在面試中有沒遇到面試官問你下面六句Sql的區別呢 select * from table where id = ? select * from table where id < ? select * from table where id = ? lock in share mode sele
【原創】大資料基礎之Spark(5)Shuffle實現原理及程式碼解析
一 簡介 Shuffle,簡而言之,就是對資料進行重新分割槽,其中會涉及大量的網路io和磁碟io,為什麼需要shuffle,以詞頻統計reduceByKey過程為例, serverA:partition1: (hello, 1), (word, 1)serverB:partition2: (hell
【原創】Spring-boot快速入門(一)HelloWord!--轉載請註明出處
Spring-boot快速入門(一)HelloWord! 一、Spring-boot簡介 1. Spring-boot介紹 Spring-boot是一款將Spring4.X版本Spring族群進行整合的一款框架,繼承了來自於Spring族群的絕大部分功能,在Spring4.
【原創】網頁全站下載器4.0黑色版,利用爬蟲獲取所有js、css、img!
此程式是作者原創,轉載請註明出處(csdn:pythoning183)!!!!!!!版本號:WebFileSpider4.0使用前,點個贊謝謝!此下載器可以下載任意網頁的原始碼和所有js、css、img檔案,包括隱藏網頁和js和css裡隱藏的檔案,實現了幾乎不遺漏的模仿建站,
【拉勾專場】拋棄簡歷!讓程式碼說話!
前些日子謝亮兄弟丟了一個連結在群裡,我當時看了下,覺得這種裝逼題目沒什麼意思,因為每種語言都有不同的實現方法,你怎麼能說你的方法一定比其他語言的好,所以要好的思路 + 好的語言特性運用才能讓程式碼昇華。 FizzBuzzWhizz 你是一名體育老師,在某次課距離下課還有五分鐘時,你決定搞一個遊
【原創】中文分詞系統 ICTCLAS2015 的JAVA封裝和多執行緒執行(附程式碼)
本文針對的問題是 ICTCLAS2015 的多執行緒分詞,為了實現多執行緒做了簡單的JAVA封裝。如果有需要可以自行進一步封裝其它介面。 首先ICTCLAS2015的傳送門(http://ictclas.nlpir.org/),其對中文分詞做的比較透徹,而且有一定的可調式性。但是應用到實際開發中的話
【原創】Leetcode -- Reverse Linked List II -- 程式碼隨筆(備忘)
題目:Reverse Linked List II 題意:Reverse a linked list from position m to n. Do it in-place and in one-pass. 下面這段程式碼,有兩個地方,一個是4、5行的dummy節點設定;另一個是11-14行,區域性視覺
【原創】用事實說話,Firefox 的效能是 Chrome 的 2 倍,Edge 的 4 倍,IE11 的 6 倍!
前言 每個瀏覽器新版本釋出,都號稱效能有顯著提升,並且市面有各種測試工具,測試結果也是大相徑庭,比如下面這篇文章: https://www.oschina.net/news/97924/browser-benchmark-battle 測試結果就很有意思,請看下如下兩幅截圖: 一言以蔽之:Go
【原創】原來你竟然是這樣的Chrome?!Firefox笑而不語
書接上文 上一篇文章《【原創】用事實說話,Firefox 的效能是 Chrome 的 2 倍,Edge 的 4 倍,IE11 的 6 倍!》,我們對比了不同瀏覽器下FineUIPro一個頁面的效能,發現Firefox的載入速度最快,而眾望所歸的Chrome卻表現的差強人意,載入速度僅僅是Firefox的一半
【原創】從零開始搭建Electron+Vue+Webpack專案框架,一套程式碼,同時構建客戶端、web端(二)
導航: (一)Electron跑起來(二)從零搭建Vue全家桶+webpack專案框架(三)Electron+Vue+Webpack,聯合除錯整個專案(未完待續)(四)Electron配置潤色(未完待續)(五)預載入及自動更新(未完待續)(六)構建、釋出整個專案(包括client和web)(未完待續) 摘要:
【原創】這道Java基礎題真的有坑!我也沒想到還有續集。
前情回顧 自從我上次發了《這道Java基礎題真的有坑!我求求你,認真思考後再回答。》這篇文章後。我通過這樣的一個行文結構: 解析了小馬哥出的這道題,讓大家明白了這題的坑在哪裡,這題背後隱藏的知識點是什麼。 但是我是萬萬沒想到啊,這篇文章居然還有續集。因為有很多讀者給我留言,問我為什麼?怎麼回事?啥情況