cstring只获取到第一个数_一文讲透 Dubbo 负载均衡之最小活跃数算法
(給ImportNew加星標,提高Java技能)
作者:why技術(shù)(本文來自作者投稿)
本文是對于Dubbo負載均衡策略之一的最小活躍數(shù)算法的詳細分析。文中所示源碼,沒有特別標注的地方均為2.6.0版本。
為什么沒有用截止目前的最新的版本號2.7.4.1呢?因為2.6.0這個版本里面有兩個bug。從bug講起來,印象更加深刻。
最后會對2.6.0/2.6.5/2.7.4.1版本進行對比,通過對比學(xué)習(xí),加深印象。
本文目錄
第一節(jié):Demo準備。
本小節(jié)主要是為了演示方便,搭建了一個Demo服務(wù)。Demo中啟動三個服務(wù)端,負載均衡策略均是最小活躍數(shù),權(quán)重各不相同。
第二節(jié):斷點打在哪?
本小節(jié)主要是分享我看源碼的方式。以及我們看源碼時斷點如何設(shè)置,怎么避免在源碼里面"瞎逛"。
第三節(jié):模擬環(huán)境。
本小節(jié)主要是基于Demo的改造,模擬真實環(huán)境。在此過程中發(fā)現(xiàn)了問題,引申出下一小節(jié)。
第四節(jié):active為什么是0?
本小節(jié)主要介紹了RpcStatus類中的active字段在最小活躍數(shù)算法中所承擔(dān)的作用,以及其什么時候發(fā)生變化。讓讀者明白為什么需要在customer端配置ActiveLimitFilter攔截器。
第五節(jié):剖析源碼
本小節(jié)對于最小活躍數(shù)算法的實現(xiàn)類進行了逐行代碼的解讀,基本上在每一行代碼上加入了注釋。屬于全文重點部分。
第六節(jié):Bug在哪里?
逐行解讀完源碼后,引出了2.6.0版本最小活躍數(shù)算法的兩個Bug。并通過2.6.0/2.6.5/2.7.4.1三個版本的異同點進行交叉對比,加深讀者印象。
第七節(jié):意外收獲?
看官方文檔的時候發(fā)現(xiàn)了一處小小的筆誤,我對其進行了修改并被merged。主要是介紹給開源項目貢獻代碼的流程。
PS:前一到三節(jié)主要是分享我看源碼的一點思路和技巧,如果你不感興趣可以直接從第四節(jié)開始看起。本文的重點是第四到第六節(jié)。
另:閱讀本文需要對Dubbo有一定的了解。
一.Demo準備
我看源碼的習(xí)慣是先搞個Demo把調(diào)試環(huán)境搭起來。然后帶著疑問去抽絲剝繭的Debug,不放過在這個過程中在腦海里面一閃而過的任何疑問。
這篇文章分享的是Dubbo負載均衡策略之一最小活躍數(shù)(LeastActiveLoadBalance)。所以我先搭建一個Dubbo的項目,并啟動三個provider供consumer調(diào)用。
三個provider的loadbalance均配置的是leastactive。權(quán)重分別是默認權(quán)重、200、300。
默認權(quán)重是多少?后面看源碼的時候,源碼會告訴你。
三個不同的服務(wù)提供者會給調(diào)用方返回自己是什么權(quán)重的服務(wù)。
啟動三個實例。(注:上面的provider.xml和DemoServiceImpl其實只有一個,每次啟動的時候手動修改端口、權(quán)重即可。)
到zookeeper上檢查一下,服務(wù)提供者是否正常:
可以看到三個服務(wù)提供者分別在20880、20881、20882端口。(每個紅框的最后5個數(shù)字就是端口號)。
最后,我們再看服務(wù)消費者。消費者很簡單,配置consumer.xml
直接調(diào)用接口并打印返回值即可。
二.斷點打在哪?
相信很多朋友也很想看源碼,但是不知道從何處下手。處于一種在源碼里面"亂逛"的狀態(tài),一圈逛下來,收獲并不大。
這一小節(jié)我想分享一下我是怎么去看源碼。首先我會帶著問題去源碼里面尋找答案,即有針對性的看源碼。
如果是這種框架類的,正如上面寫的,我會先搭建一個簡單的Demo項目,然后Debug跟進去看。Debug的時候當然需要是設(shè)置斷點的,那么這個斷點如何設(shè)置呢?
第一個斷點,當然毋庸置疑,是打在調(diào)用方法的地方,比如本文中,第一個斷點是在這個地方:
接下里怎么辦?
你當然可以從第一個斷點處,一步一步的跟進去。但是在這個過程中,你發(fā)現(xiàn)了嗎?大多數(shù)情況你都是被源碼牽著鼻子走的。本來你就只帶著一個問題去看源碼的,有可能你Debug了十分鐘,還沒找到關(guān)鍵的代碼。也有可能你Debug了十分鐘,問題從一個變成了無數(shù)個。
那么我們怎么避免被源碼牽著四處亂逛呢?我們得找到一個突破口,還記得我在《很開心,在使用mybatis的過程中我踩到一個坑》這篇文章中提到的逆向排查的方法嗎?這次的文章,我再次展示一下該方法。
看源碼之前,我們得冷靜的分析。目標要十分明確,就是想要找到Dubbo最小活躍數(shù)算法的具體實現(xiàn)類以及實現(xiàn)類的具體邏輯是什么。根據(jù)我們的provider.xml里面的:
很明顯,我們知道loadbalance是關(guān)鍵字。所以我們拿著loadbalance全局搜索,可以看到dubbo包下面的LoadBalance。
這是一個SPI接口com.alibaba.dubbo.rpc.cluster.LoadBalance:
其實現(xiàn)類為:
com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance
AbstractLoadBalance是一個抽象類,該類里面有一個抽象方法doSelect。這個抽象方法其中的一個實現(xiàn)類就是我們要分析的最少活躍次數(shù)負載均衡的源碼。
同時,到這里。我們知道了LoadBalance是一個SPI接口,說明我們可以擴展自己的負載均衡策略。抽象方法doSelect有四個實現(xiàn)類。這個四個實現(xiàn)類,就是Dubbo官方提供的負載均衡策略,他們分別是:
ConsistentHashLoadBalance 一致性哈希算法?
LeastActiveLoadBalance 最小活躍數(shù)算法?
RandomLoadBalance? 加權(quán)隨機算法?
RoundRobinLoadBalance 加權(quán)輪詢算法
我們已經(jīng)找到了LeastActiveLoadBalance這個類了,那么我們的第二個斷點打在哪里已經(jīng)很明確了。
目前看來,兩個斷點就可以支撐我們的分析了。
有的朋友可能想問,那我想知道Dubbo是怎么識別出我們想要的是最少活躍次數(shù)算法,而不是其他的算法呢?其他的算法是怎么實現(xiàn)的呢?從第一個斷點到第二個斷點直接有著怎樣的調(diào)用鏈呢?
在沒有徹底搞清楚最少活躍數(shù)算法之前,這些統(tǒng)統(tǒng)先記錄在案但不予理睬。一定要明確目標,帶著一個問題進來,就先把帶來的問題解決了。之后再去解決在這個過程中碰到的其他問題。在這樣環(huán)環(huán)相扣解決問題的過程中,你就慢慢的把握了源碼的精髓。這是我個人的一點看源碼的心得。供諸君參考。
三.模擬環(huán)境
既然叫做最小活躍數(shù)策略。那我們得讓現(xiàn)有的三個消費者都有一些調(diào)用次數(shù)。所以我們得改造一下服務(wù)提供者和消費者。
服務(wù)提供者端的改造如下:
PS:這里以權(quán)重為300的服務(wù)端為例。另外的兩個服務(wù)端改造點相同。
客戶端的改造點如下(for循環(huán)里面的i應(yīng)該為<20):
一共發(fā)送21個請求:其中前20個先發(fā)到服務(wù)端讓其hold住(因為服務(wù)端有sleep),最后一個請求就是我們需要Debug跟蹤的請求。
運行一下,讓程序停在斷點的地方,然后看看控制臺的輸出:
權(quán)重為300的服務(wù)端共計收到9個請求
權(quán)重為200的服務(wù)端共計收到6個請求
默認權(quán)重的服務(wù)端共計收到5個請求
我們還有一個請求在Debug。直接進入到我們的第二個斷點的位置,并Debug到下圖所示的一行代碼(可以點看查看大圖):
正如上面這圖所說的:weight=100回答了一個問題,active=0提出的一個問題。
weight=100回答了什么問題呢?
默認權(quán)重是多少?是100。
我們服務(wù)端的活躍數(shù)分別應(yīng)該是下面這樣的
權(quán)重為300的服務(wù)端,active=9?
權(quán)重為200的服務(wù)端,active=6?
默認權(quán)重(100)的服務(wù)端,active=5
但是這里為什么active會等于0呢?這是一個問題。
繼續(xù)往下Debug你會發(fā)現(xiàn),每一個服務(wù)端的active都是0。所以相比之下沒有一個invoker有最小active。于是程序走到了根據(jù)權(quán)重選擇invoker的邏輯中。
四.active為什么是0?
active為0說明在dubbo調(diào)用的過程中active并沒有發(fā)生變化。那active為什么是0,其實就是在問active什么時候發(fā)生變化?
要回答這個問題我們得知道active是在哪里定義的,因為在其定義的地方,必有其修改的方法。
下面這圖說明了active是定義在RpcStatus類里面的一個類型為AtomicInteger的成員變量。
在RpcStatus類中,有三處()調(diào)用active值的方法,一個增加、一個減少、一個獲取:
很明顯,我們需要看的是第一個,在哪里增加。
所以我們找到了beginCount(URL,String)方法,該方法只有兩個Filter調(diào)用。ActiveLimitFilter,見名知意,這就是我們要找的東西。
com.alibaba.dubbo.rpc.filter.ActiveLimitFilter具體如下:
看到這里,我們就知道怎么去回答這個問題了:為什么active是0呢?因為在客戶端沒有配置ActiveLimitFilter。所以,ActiveLimitFilter沒有生效,導(dǎo)致active沒有發(fā)生變化。
怎么讓其生效呢?已經(jīng)呼之欲出了。
好了,再來試驗一次:
加上Filter之后,我們通過Debug可以看到,對應(yīng)權(quán)重的活躍數(shù)就和我們預(yù)期的是一致的了。
權(quán)重為300的活躍數(shù)為6?
權(quán)重為200的活躍數(shù)為11?
默認權(quán)重(100)的活躍數(shù)為3
根據(jù)活躍數(shù)我們可以分析出來,最后我們Debug住的這個請求,一定會選擇默認權(quán)重的invoker去執(zhí)行,因為他是當前活躍數(shù)最小的invoker。如下所示:
雖然到這里我們還沒開始進行源碼的分析,只是把流程梳理清楚了。但是把Demo完整的搭建了起來,而且知道了最少活躍數(shù)負載均衡算法必須配合ActiveLimitFilter使用,位于RpcStatus類的active字段才會起作用,否則,它就是一個基于權(quán)重的算法。
比起其他地方直接告訴你,要配置ActiveLimitFilter才行哦,我們自己實驗得出的結(jié)論,能讓我們的印象更加深刻。
我們再仔細看一下加上ActiveLimitFilter之后的各個服務(wù)的活躍數(shù)情況:
權(quán)重為300的活躍數(shù)為6??
權(quán)重為200的活躍數(shù)為11??
默認權(quán)重(100)的活躍數(shù)為3
你不覺得奇怪嗎,為什么權(quán)重為200的活躍數(shù)是最高的?
其在業(yè)務(wù)上的含義是:我們有三臺性能各異的服務(wù)器,A服務(wù)器性能最好,所以權(quán)重為300,B服務(wù)器性能中等,所以權(quán)重為200,C服務(wù)器性能最差,所以權(quán)重為100。
當我們選擇最小活躍次數(shù)的負載均衡算法時,我們期望的是性能最好的A服務(wù)器承擔(dān)更多的請求,而真實的情況是性能中等的B服務(wù)器承擔(dān)的請求更多。這與我們的設(shè)定相悖。
如果你說20個請求數(shù)據(jù)量太少,可能是巧合,不足以說明問題。說明你還沒被我?guī)?#xff0c;我們不能基于巧合編程。
所以為了驗證這個地方確實有問題,我把請求擴大到一萬個。
同時,記得擴大provider端的Dubbo線程池:
由于每個服務(wù)端運行的代碼都是一樣的,所以我們期望的結(jié)果應(yīng)該是權(quán)重最高的承擔(dān)更多的請求。但是最終的結(jié)果如圖所示:
各個服務(wù)器均攤了請求。這就是我文章最開始的時候說的Dubbo 2.6.0版本中最小活躍數(shù)負載均衡算法的Bug之一。
接下來,我們帶著這個問題,去分析源碼。
五.剖析源碼
com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance的源碼如下,我逐行進行了解讀。可以點開查看大圖,細細品讀,非常爽:
下圖中紅框框起來的部分就是一個基于權(quán)重選擇invoker的邏輯:
我給大家畫圖分析一下:
請仔細分析圖中給出的舉例說明。同時,上面這圖也是按照比例畫的,可以直觀的看到,對于某一個請求,區(qū)間(權(quán)重)越大的服務(wù)器,就越可能會承擔(dān)這個請求。所以,當請求足夠多的時候,各個服務(wù)器承擔(dān)的請求數(shù),應(yīng)該就是區(qū)間,即權(quán)重的比值。
其中第81行有調(diào)用getWeight方法,位于抽象類AbstractLoadBalance中,也需要進行重點解讀的代碼。
com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance的源碼如下,我也進行了大量的備注:
在AbstractLoadBalance類中提到了一個預(yù)熱的概念。官網(wǎng)中是這樣的介紹該功能的:
權(quán)重的計算過程主要用于保證當服務(wù)運行時長小于服務(wù)預(yù)熱時間時,對服務(wù)進行降權(quán),避免讓服務(wù)在啟動之初就處于高負載狀態(tài)。服務(wù)預(yù)熱是一個優(yōu)化手段,與此類似的還有 JVM 預(yù)熱。主要目的是讓服務(wù)啟動后“低功率”運行一段時間,使其效率慢慢提升至最佳狀態(tài)。
從上圖代碼里面的公式(演變后):計算后的權(quán)重=(uptime/warmup)*weight可以看出:隨著服務(wù)啟動時間的增加(uptime),計算后的權(quán)重會越來越接近weight。從實際場景的角度來看,隨著服務(wù)啟動時間的增加,服務(wù)承擔(dān)的流量會慢慢上升,沒有一個陡升的過程。所以這是一個優(yōu)化手段。同時Dubbo接口還支持延遲暴露。
在仔細的看完上面的源碼解析圖后,配合官網(wǎng)的總結(jié)加上我的靈魂畫作,相信你可以對最小活躍數(shù)負載均衡算法有一個比較深入的理解:
遍歷 invokers 列表,尋找活躍數(shù)最小的 Invoker
如果有多個 Invoker 具有相同的最小活躍數(shù),此時記錄下這些 Invoker 在 invokers 集合中的下標,并累加它們的權(quán)重,比較它們的權(quán)重值是否相等?
如果只有一個 Invoker 具有最小的活躍數(shù),此時直接返回該 Invoker 即可
如果有多個 Invoker 具有最小活躍數(shù),且它們的權(quán)重不相等,此時處理方式和 RandomLoadBalance 一致
如果有多個 Invoker 具有最小活躍數(shù),但它們的權(quán)重相等,此時隨機返回一個即可
所以我覺得最小活躍數(shù)負載均衡的全稱應(yīng)該叫做:有最小活躍數(shù)用最小活躍數(shù),沒有最小活躍數(shù)根據(jù)權(quán)重選擇,權(quán)重一樣則隨機返回的負載均衡算法。
六.BUG在哪里
Dubbo2.6.0最小活躍數(shù)算法Bug一
問題出在標號為①和②這兩行代碼中:
標號為①的代碼在url中取出的是沒有經(jīng)過getWeight方法降權(quán)處理的權(quán)重值,這個值會被累加到權(quán)重總和(totalWeight)中。
標號為②的代碼取的是經(jīng)過getWeight方法處理后的權(quán)重值。
取值的差異會導(dǎo)致一個問題,標號為②的代碼的左邊,offsetWeight是一個在[0,totalWeight)范圍內(nèi)的隨機數(shù),右邊是經(jīng)過getWeight方法降權(quán)后的權(quán)重。所以在經(jīng)過leastCount次的循環(huán)減法后,offsetWeight在服務(wù)啟動時間還沒到熱啟動設(shè)置(默認10分鐘)的這段時間內(nèi),極大可能仍然大于0。導(dǎo)致不會進入到標號為③的代碼中。直接到標號為④的代碼處,變成了隨機調(diào)用策略。這與設(shè)計不符,所以是個bug。
前面章節(jié)說的情況就是這個Bug導(dǎo)致的。
這個Bug對應(yīng)的issues地址和pull request分為:
https://github.com/apache/dubbo/issues/904
https://github.com/apache/dubbo/pull/2172
那怎么修復(fù)的呢?我們直接對比Dubbo 2.7.4.1(目前最新版本)的代碼:
可以看到獲取weight的方法變了:從url中直接獲取變成了通過getWeight方法獲取。獲取到的變量名稱也變了:從weight變成了afterWarmup,更加的見名知意。
還有一處變化是獲取隨機值的方法的變化,從Randmo變成了ThreadLoaclRandom,性能得到了提升。這處變化就不展開講了,有興趣的朋友可以去了解一下。
ThreadLocalRandomDubbo2.6.0最小活躍數(shù)算法Bug二
這個Bug我沒有遇到,但是我在官方文檔上看了其描述(官方文檔中的版本是2.6.4),引用如下:
官網(wǎng)上說這個問題在2.6.5版本進行修復(fù)。我對比了2.6.0/2.6.5/2.7.4.1三個版本,發(fā)現(xiàn)每個版本都略有不同。如下所示:
圖中標記為①的三處代碼:
2.6.0版本的是有Bug的代碼,原因在上面說過了。
2.6.5版本的修復(fù)方式是獲取隨機數(shù)的時候加一,所以取值范圍就從[0,totalWeight)變成了[0,totalWeight],這樣就可以避免這個問題。
2.7.4.1版本的取值范圍還是[0,totalWeight),但是它的修復(fù)方法體現(xiàn)在了標記為②的代碼處。2.6.0/2.6.5版本標記為②的地方都是if(offsetWeight<=0),而2.7.4.1版本變成了if(offsetWeight<0)。
你品一品,是不是效果是一樣的,但是更加優(yōu)雅了。
朋友們,魔鬼,都在細節(jié)里啊!
七.意外收獲
在看官網(wǎng)文檔負載均衡介紹的時候。發(fā)現(xiàn)了一處筆誤。所以我對其進行了修改并被merged。
可以看到,改動點也是一個非常小的地方。但是,我也為Dubbo社區(qū)貢獻了一份自己的力量。我是Dubbo文檔的committer,簡稱"Dubbo committer"。
本小節(jié)主要是簡單的介紹一下給開源項目提pr的流程。
首先,fork項目到自己的倉庫中。然后執(zhí)行以下命令,拉取項目并設(shè)置源:
git clone https://github.com/thisiswanghy/dubbo-website.git cd dubbo-website
git remote add upstream https://github.com/apache/dubbo-website.git git remote set-url --push upstream no_push創(chuàng)建本地分支:
git checkout -b xxxx開發(fā)完成后提交代碼:
git fetch upstream
git checkout master
git merge upstream/master git checkout -b xxxx git rebase master git push origin xxxx:xxxx然后到git上創(chuàng)建pull request后,靜候通知。
最后說一句
之前也寫過Dubbo的文章《Dubbo 2.7新特性之異步化改造》,通過對比Dubbo2.6.0/2.7.0/2.7.3版本的源碼,分析Dubbo2.7 異步化的改造的細節(jié),可以看看哦。
推薦閱讀
(點擊標題可跳轉(zhuǎn)閱讀)
一文聊透 Dubbo 優(yōu)雅上線
一文詳細解讀 Dubbo 中的 http 協(xié)議
Java 調(diào)試技能之 dubbo 調(diào)試 — telnet
看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多人
關(guān)注「ImportNew」,提升Java技能
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的cstring只获取到第一个数_一文讲透 Dubbo 负载均衡之最小活跃数算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《爱,机器人和死亡》适合青少年看吗(看过
- 下一篇: 怀孕前检查项目有哪些