吐血輸出:2萬字長文帶你細細盤點五種負載均衡策略。


                          

Dubbo的五種負載均衡策略html

2020 年 5 月 15 日,Dubbo 發佈 2.7.7 release 版本。其中有這麼一個 Features
git


新增一個負載均衡策略。github


熟悉個人老讀者確定是知道的,Dubbo 的負載均衡我都寫過專門的文章,對每一個負載均衡算法進行了源碼的解讀,還分享了本身調試過程當中的一些騷操做。面試


新的負載均衡出來了,那必須的得解讀一波。算法


先看一下提交記錄:apache

https://github.com/chickenlj/incubator-dubbo/commit/6d2ba7ec7b5a1cb7971143d4262d0a1bfc826d45


負載均衡是基於 SPI 實現的,咱們看到對應的文件中多了一個名爲 shortestresponse 的 key。編程

這個,就是新增的負載均衡策略了。看名字,你也知道了這個策略的名稱就叫:最短響應。數組

因此截止 2.7.7 版本,官方提供了五種負載均衡算法了,他們分別是:緩存

  1. ConsistentHashLoadBalance 一致性哈希負載均衡 bash

  2. LeastActiveLoadBalance 最小活躍數負載均衡

  3. RandomLoadBalance 加權隨機負載均衡

  4. RoundRobinLoadBalance 加權輪詢負載均衡

  5. ShortestResponseLoadBalance 最短響應時間負載均衡

前面四種我已經在以前的文章中進行了詳細的分析。有的讀者反饋說想看合輯,因此我會在這篇文章中把以前文章也整合進來。

因此,須要特別強調一下的是,這篇文章集合了以前寫的三篇負載均衡的文章。看完最短響應時間負載均衡這一部分後,若是你看過我以前的那三篇文章,你能夠溫故而知新,也能夠直接拉到文末看看我推薦的一個活動,而後點個贊再走。若是你沒有看過那三篇,這篇文章若是你細看,確定有不少收穫,之後談起負載均衡的時候若數家珍,可是確定須要看很是很是長的時間,作好心理準備。


我已經預感到了,這篇文章妥妥的會超過 2 萬字。屬於硬核勸退文章,想一想就懼怕。



最短響應時間負載均衡

首先,咱們看一下這個類上的註解,先有個總體的認知。

org.apache.dubbo.rpc.cluster.loadbalance.ShortestResponseLoadBalance

我來翻譯一下是什麼意思:


  1. 從多個服務提供者中選擇出調用成功的且響應時間最短的服務提供者,因爲知足這樣條件的服務提供者有可能有多個。因此當選擇出多個服務提供者後要根據他們的權重作分析。

  2. 可是若是隻選擇出來了一個,直接用選出來這個。

  3. 若是真的有多個,看它們的權重是否同樣,若是不同,則走加權隨機算法的邏輯。

  4. 若是它們的權重是同樣的,則隨機調用一個。


再配個圖,就好理解了,能夠先無論圖片中的標號:

有了上面的總體概念的鋪墊了,接下來分析源碼的時候就簡單了。


源碼一共就 66 行,我把它分爲 5 個片斷去一一分析。


這裏一到五的標號,對應上面流程圖中的標號。咱們一個個的說。


標號爲①的部分


這一部分是定義並初始化一些參數,爲接下來的代碼服務的,翻譯一下每一個參數對應的註釋:


  1. length 參數:服務提供者的數量。

  2. shortestResponse 參數:全部服務提供者的估計最短響應時間。(這個地方我以爲註釋描述的不太準確,看後面的代碼能夠知道這只是一個零時變量,在循環中存儲當前最短響應時間是多少。)

  3. shortCount 參數:具備相同最短響應時間的服務提供者個數,初始化爲 0。

  4. shortestIndexes 參數:數組裏面放的是具備相同最短響應時間的服務提供者的下標。

  5. weights 參數:每個服務提供者的權重。

  6. totalWeight 參數:多個具備相同最短響應時間的服務提供者對應的預熱(預熱這個點仍是挺重要的,在下面講最小活躍數負載均衡的時候有詳細說明)權重之和。

  7. firstWeight 參數:第一個具備最短響應時間的服務提供者的權重。

  8. sameWeight 參數:多個知足條件的提供者的權重是否一致。


標號爲②的部分



這一部分代碼的關鍵,就在上面框起來的部分。而框起來的部分,最關鍵的地方,就在於第一行。


獲取調用成功的平均時間。


成功調用的平均時間怎麼算的?


調用成功的請求數總數對應的總耗時 / 調用成功的請求數總數 = 成功調用的平均時間。


因此,在下面這個方法中,首先獲取到了調用成功的請求數總數:


這個 succeeded 參數是怎麼來的呢?


答案就是:總的請求數減去請求失敗的數量,就是請求成功的總數!


那麼爲何不能直接獲取請求成功的總數呢?


別問,問就是沒有這個選項啊。你看,在 RpcStatus 裏面沒有這個參數呀。


請求成功的總數咱們有了,接下來成功總耗時怎麼拿到的呢?


答案就是:總的請求時間減去請求失敗的總時間,就是請求成功的總耗時!

那麼爲何不能直接獲取請求成功的總耗時呢?


別問,問就是......


咱們看一下 RpcStatus 中的這幾個參數是在哪裏維護的:

org.apache.dubbo.rpc.RpcStatus#endCount(org.apache.dubbo.rpc.RpcStatus, long, boolean)

其中的第二個入參是本次請求調用時長,第三個入參是本次調用是否成功。


具體的方法沒必要細說了吧,已經顯而易見了。


再回去看框起來的那三行代碼:


  1. 第一行獲取到了該服務提供者成功請求的平均耗時。

  2. 第二行獲取的是該服務提供者的活躍數,也就是堆積的請求數。

  3. 第三行獲取的就是若是當前這個請求發給這個服務提供者預計須要等待的時間。乘以 active 的緣由是由於它須要排在堆積的請求的後面嘛。


這裏,咱們就獲取到了若是選擇當前循環中的服務提供者的預計等待時間是多長。


後面的代碼怎麼寫?


固然是出來一個更短的就把這個踢出去呀,或者出來一個同樣長時間的就記錄一下,接着去 pk 權重了。


因此,接下來 shortestIndexes 參數和 weights 參數就排上用場了:


另外,多說一句的,它裏面有這樣的一行註釋:


和 LeastActiveLoadBalance 負載均衡策略一致,我給你截圖對比一下:

能夠看到,確實是很是的類似,只是一個是判斷誰的響應時間短,一個是判斷誰的活躍數低。

標號爲③的地方


標號爲③的地方是這樣的:


裏面參數的含義咱們都知道了,因此,標號爲③的地方的含義就很好解釋了:通過選擇後只有一個服務提供者知足條件。因此,直接使用這個服務提供者。


標號爲④的地方


這個地方我就不展開講了(後面的加權隨機負載均衡那一小節有詳細說明),熟悉的朋友一眼就看出來這是加權隨機負載均衡的寫法了。


不信?我給你對比一下:


你看,是否是如出一轍的。

標號爲⑤的地方


一行代碼,沒啥說的。就是從多個知足條件的且權重同樣的服務提供者中隨機選擇一個。


若是必定要多說一句的話,我截個圖吧:

能夠看到,這行代碼在最短響應時間、加權隨機、最小活躍數負載均衡策略中都出現了,且都在最後一行。


好了,到這裏最短響應時間負載均衡策略就講完了,你再回過頭去看那張流程圖,會發現其實流程很是的清晰,徹底能夠根據代碼結構畫出流程圖。一個是說明這個算法是真的不復雜,另外一個是說明好的代碼會說話。


優雅


你知道 Dubbo 加入這個新的負載均衡算法提交了幾個文件嗎?


四個文件,其中還包含兩個測試文件:


這裏就是策略模式和 SPI 的好處。對原有的負載均衡策略沒有任何侵略性。只須要按照規則擴展配置文件,實現對應接口便可。


這是什麼?


這就是值得學習優雅!


那咱們優雅的進入下一議題。


最小活躍數負載均衡

這一小節所示源碼,沒有特別標註的地方均爲 2.6.0 版本。


爲何沒有用截止目前(我當時寫這段文章的時候是2019年12月01日)的最新的版本號 2.7.4.1 呢?由於 2.6.0 這個版本里面有兩個 bug 。從 bug 講起來,印象更加深入。


最後會對 2.6.0/2.6.5/2.7.4.1 版本進行對比,經過對比學習,加深印象。


我這裏補充一句啊,僅僅半年的時間,版本號就從 2.7.4.1 到了 2.7.7。其中還包含一個 2.7.5 這樣的大版本。


因此還有人說 Dubbo 不夠活躍?(幾年前的文章如今還有人在發。)

對吧,咱們不吵架,咱們擺事實,聊數據嘛。


Demo 準備


我看源碼的習慣是先搞個 Demo 把調試環境搭起來。而後帶着疑問去抽絲剝繭的 Debug,不放過在這個過程當中在腦海裏面一閃而過的任何疑問。


這一小節分享的是Dubbo負載均衡策略之一最小活躍數(LeastActiveLoadBalance)。因此我先搭建一個 Dubbo 的項目,並啓動三個 provider 供 consumer 調用。


三個 provider 的 loadbalance 均配置的是 leastactive。權重分別是默認權重、200、300。


默認權重是多少?後面看源碼的時候,源碼會告訴你。


三個不一樣的服務提供者會給調用方返回本身是什麼權重的服務。


啓動三個實例。(注:上面的 provider.xml 和 DemoServiceImpl 其實只有一個,每次啓動的時候手動修改端口、權重便可。)


到 zookeeper 上檢查一下,服務提供者是否正常:

能夠看到三個服務提供者分別在 20880、2088一、20882 端口。(每一個紅框的最後5個數字就是端口號)。


最後,咱們再看服務消費者。消費者很簡單,配置consumer.xml


直接調用接口並打印返回值便可。


斷點打在哪?


相信不少朋友也很想看源碼,可是不知道從何處下手。處於一種在源碼裏面"亂逛"的狀態,一圈逛下來,收穫並不大。


這一部分我想分享一下我是怎麼去看源碼。首先我會帶着問題去源碼裏面尋找答案,即有針對性的看源碼。


若是是這種框架類的,正如上面寫的,我會先翻一翻官網(Dubbo 的官方文檔其實寫的挺好了),而後搭建一個簡單的 Demo 項目,而後 Debug 跟進去看。Debug 的時候固然須要是設置斷點的,那麼這個斷點如何設置呢?


第一個斷點,固然毋庸置疑,是打在調用方法的地方,好比本文中,第一個斷點是在這個地方:


接下里怎麼辦?


你固然能夠從第一個斷點處,一步一步的跟進去。可是在這個過程當中,你發現了嗎?大多數狀況你都是被源碼牽着鼻子走的。原本你就只帶着一個問題去看源碼的,有可能你Debug了十分鐘,還沒找到關鍵的代碼。也有可能你Debug了十分鐘,問題從一個變成了無數個。


因此不要慌,咱們點支菸,慢慢分析。


首先怎麼避免被源碼牽着四處亂逛呢?


咱們得找到一個突破口,還記得我在《很開心,在使用mybatis的過程當中我踩到一個坑》這篇文章中提到的逆向排查的方法嗎?此次的文章,我再次展現一下該方法。


看源碼以前,咱們的目標要十分明確,就是想要找到 Dubbo 最小活躍數算法的具體實現類以及實現類的具體邏輯是什麼。


根據咱們的 provider.xml 裏面的:


很明顯,咱們知道 loadbalance 是關鍵字。因此咱們拿着 loadbalance 全局搜索,能夠看到 Dubbo 包下面的 LoadBalance。


這是一個 SPI 接口 com.alibaba.dubbo.rpc.cluster.LoadBalance:


其實現類爲:

com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance

AbstractLoadBalance 是一個抽象類,該類裏面有一個抽象方法doSelect。這個抽象方法其中的一個實現類就是咱們要分析的最少活躍次數負載均衡的源碼。


同時,到這裏咱們知道了 LoadBalance 是一個 SPI 接口,說明咱們能夠擴展本身的負載均衡策略。抽象方法 doSelect 有四個實現類。這個四個實現類,就是 Dubbo 官方提供的負載均衡策略(截止 2.7.7 版本以前),他們分別是:

ConsistentHashLoadBalance 一致性哈希算法

LeastActiveLoadBalance 最小活躍數算法

RandomLoadBalance 加權隨機算法

RoundRobinLoadBalance 加權輪詢算法


咱們已經找到了 LeastActiveLoadBalance 這個類了,那麼咱們的第二個斷點打在哪裏已經很明確了。


目前看來,兩個斷點就能夠支撐咱們的分析了。


有的朋友可能想問,那我想知道 Dubbo 是怎麼識別出咱們想要的是最少活躍次數算法,而不是其餘的算法呢?其餘的算法是怎麼實現的呢?從第一個斷點到第二個斷點直接有着怎樣的調用鏈呢?


在沒有完全搞清楚最少活躍數算法以前,這些通通先記錄在案但不予理睬。必定要明確目標,帶着一個問題進來,就先把帶來的問題解決了。以後再去解決在這個過程當中碰到的其餘問題。在這樣環環相扣解決問題的過程當中,你就慢慢的把握了源碼的精髓。這是我我的的一點看源碼的心得。供諸君參考。


模擬環境


既然叫作最小活躍數策略。那咱們得讓現有的三個消費者都有一些調用次數。因此咱們得改造一下服務提供者和消費者。


服務提供者端的改造以下:

PS:這裏以權重爲 300 的服務端爲例。另外的兩個服務端改造點相同。


客戶端的改造點以下:

一共發送 21 個請求:其中前 20 個先發到服務端讓其 hold 住(由於服務端有 sleep),最後一個請求就是咱們須要 Debug 跟蹤的請求。


運行一下,讓程序停在斷點的地方,而後看看控制檯的輸出:

▲上下滑動查看更多


  1. 權重爲300的服務端共計收到9個請求

  2. 權重爲200的服務端共計收到6個請求

  3. 默認權重的服務端共計收到5個請求


咱們還有一個請求在 Debug。直接進入到咱們的第二個斷點的位置,並 Debug 到下圖所示的一行代碼(能夠點看查看大圖):


正如上面這圖所說的:weight=100 回答了一個問題,active=0 提出的一個問題。


weight=100 回答了什麼問題呢?


默認權重是多少?是 100。


咱們服務端的活躍數分別應該是下面這樣的


  1. 權重爲300的服務端,active=9

  2. 權重爲200的服務端,active=6

  3. 默認權重(100)的服務端,active=5


可是這裏爲何截圖中的active會等於 0 呢?這是一個問題。


繼續往下 Debug 你會發現,每個服務端的 active 都是 0。因此相比之下沒有一個 invoker 有最小 active 。因而程序走到了根據權重選擇 invoker 的邏輯中。


active爲何是0?


active 爲 0 說明在 Dubbo 調用的過程當中 active 並無發生變化。那 active 爲何是 0,其實就是在問 active 何時發生變化?


要回答這個問題咱們得知道 active 是在哪裏定義的,由於在其定義的地方,必有其修改的方法。


下面這圖說明了active是定義在RpcStatus類裏面的一個類型爲AtomicInteger 的成員變量。



在 RpcStatus 類中,有三處()調用 active 值的方法,一個增長、一個減小、一個獲取:


很明顯,咱們須要看的是第一個,在哪裏增長。


因此咱們找到了 beginCount(URL,String) 方法,該方法只有兩個 Filter 調用。ActiveLimitFilter,見名知意,這就是咱們要找的東西。


com.alibaba.dubbo.rpc.filter.ActiveLimitFilter具體以下:


看到這裏,咱們就知道怎麼去回答這個問題了:爲何active是0呢?由於在客戶端沒有配置ActiveLimitFilter。因此,ActiveLimitFilter沒有生效,致使active沒有發生變化。


怎麼讓其生效呢?已經呼之欲出了。


好了,再來試驗一次:

▲上下滑動查看更多

加上Filter以後,咱們經過Debug能夠看到,對應權重的活躍數就和咱們預期的是一致的了。


1.權重爲300的活躍數爲6

2.權重爲200的活躍數爲11

3.默認權重(100)的活躍數爲3



根據活躍數咱們能夠分析出來,最後咱們Debug住的這個請求,必定會選擇默認權重的invoker去執行,由於他是當前活躍數最小的invoker。以下所示:


雖然到這裏咱們還沒開始進行源碼的分析,只是把流程梳理清楚了。可是把Demo完整的搭建了起來,並且知道了最少活躍數負載均衡算法必須配合ActiveLimitFilter使用,位於RpcStatus類的active字段纔會起做用,不然,它就是一個基於權重的算法。


比起其餘地方直接告訴你,要配置ActiveLimitFilter才行哦,咱們本身實驗得出的結論,能讓咱們的印象更加深入。


咱們再仔細看一下加上ActiveLimitFilter以後的各個服務的活躍數狀況:


1.權重爲300的活躍數爲6

2.權重爲200的活躍數爲11

3.默認權重(100)的活躍數爲3


你不以爲奇怪嗎,爲何權重爲200的活躍數是最高的?


其在業務上的含義是:咱們有三臺性能各異的服務器,A服務器性能最好,因此權重爲300,B服務器性能中等,因此權重爲200,C服務器性能最差,因此權重爲100。


當咱們選擇最小活躍次數的負載均衡算法時,咱們指望的是性能最好的A服務器承擔更多的請求,而真實的狀況是性能中等的B服務器承擔的請求更多。這與咱們的設定相悖。


若是你說20個請求數據量太少,多是巧合,不足以說明問題。說明你還沒被我帶偏,咱們不能基於巧合編程。

因此爲了驗證這個地方確實有問題,我把請求擴大到一萬個。


同時,記得擴大 provider 端的 Dubbo 線程池:


因爲每一個服務端運行的代碼都是同樣的,因此咱們指望的結果應該是權重最高的承擔更多的請求。可是最終的結果如圖所示:


各個服務器均攤了請求。這就是我文章最開始的時候說的Dubbo 2.6.0 版本中最小活躍數負載均衡算法的Bug之一。


接下來,咱們帶着這個問題,去分析源碼。


剖析源碼


com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance的源碼以下,我逐行進行了解讀。能夠點開查看大圖,細細品讀,很是爽:


下圖中紅框框起來的部分就是一個基於權重選擇invoker的邏輯:


我給你們畫圖分析一下:


請仔細分析圖中給出的舉例說明。同時,上面這圖也是按照比例畫的,能夠直觀的看到,對於某一個請求,區間(權重)越大的服務器,就越可能會承擔這個請求。因此,當請求足夠多的時候,各個服務器承擔的請求數,應該就是區間,即權重的比值。

其中第 81 行有調用 getWeight 方法,位於抽象類 AbstractLoadBalance 中,也須要進行重點解讀的代碼。


com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance 的源碼以下,我也進行了大量的備註:


在 AbstractLoadBalance 類中提到了一個預熱的概念。官網中是這樣的介紹該功能的:

權重的計算過程主要用於保證當服務運行時長小於服務預熱時間時,對服務進行降權,避免讓服務在啓動之初就處於高負載狀態。服務預熱是一個優化手段,與此相似的還有 JVM 預熱。主要目的是讓服務啓動後「低功率」運行一段時間,使其效率慢慢提高至最佳狀態。


從上圖代碼裏面的公式(演變後):計算後的權重=(uptime/warmup)*weight 能夠看出:隨着服務啓動時間的增長(uptime),計算後的權重會愈來愈接近weight。從實際場景的角度來看,隨着服務啓動時間的增長,服務承擔的流量會慢慢上升,沒有一個陡升的過程。因此這是一個優化手段。同時 Dubbo 接口還支持延遲暴露。


在仔細的看完上面的源碼解析圖後,配合官網的總結加上個人靈魂畫做,相信你能夠對最小活躍數負載均衡算法有一個比較深刻的理解:


  1. 遍歷 invokers 列表,尋找活躍數最小的 Invoker

  2. 若是有多個 Invoker 具備相同的最小活躍數,此時記錄下這些 Invoker 在 invokers 集合中的下標,並累加它們的權重,比較它們的權重值是否相等

  3. 若是隻有一個 Invoker 具備最小的活躍數,此時直接返回該 Invoker 便可

  4. 若是有多個 Invoker 具備最小活躍數,且它們的權重不相等,此時處理方式和 RandomLoadBalance 一致

  5. 若是有多個 Invoker 具備最小活躍數,但它們的權重相等,此時隨機返回一個便可

因此我以爲最小活躍數負載均衡的全稱應該叫作:有最小活躍數用最小活躍數,沒有最小活躍數根據權重選擇,權重同樣則隨機返回的負載均衡算法。


Bug在哪裏?


Dubbo2.6.0最小活躍數算法Bug一


問題出在標號爲 ① 和 ② 這兩行代碼中:


標號爲 ① 的代碼在url中取出的是沒有通過 getWeight 方法降權處理的權重值,這個值會被累加到權重總和(totalWeight)中。


標號爲 ② 的代碼取的是通過 getWeight 方法處理後的權重值。


取值的差別會致使一個問題,標號爲 ② 的代碼的左邊,offsetWeight 是一個在 [0,totalWeight) 範圍內的隨機數,右邊是通過 getWeight 方法降權後的權重。因此在通過 leastCount 次的循環減法後,offsetWeight 在服務啓動時間還沒到熱啓動設置(默認10分鐘)的這段時間內,極大可能仍然大於 0。致使不會進入到標號爲 ③ 的代碼中。直接到標號爲 ④ 的代碼處,變成了隨機調用策略。這與設計不符,因此是個 bug。


前面章節說的狀況就是這個Bug致使的。


這個Bug對應的issues地址和pull request分爲:

https://github.com/apache/dubbo/issues/904

https://github.com/apache/dubbo/pull/2172


那怎麼修復的呢?咱們直接對比 Dubbo 2.7.4.1 (目前最新版本)的代碼:


能夠看到獲取weight的方法變了:從url中直接獲取變成了經過getWeight方法獲取。獲取到的變量名稱也變了:從weight變成了afterWarmup,更加的見名知意。


還有一處變化是獲取隨機值的方法的變化,從Randmo變成了ThreadLoaclRandom,性能獲得了提高。這處變化就不展開講了,有興趣的朋友能夠去了解一下。

ThreadLocalRandom複製代碼



Dubbo2.6.0最小活躍數算法Bug二


這個Bug我沒有遇到,可是我在官方文檔上看了其描述(官方文檔中的版本是2.6.4),引用以下:


官網上說這個問題在2.6.5版本進行修復。我對比了2.6.0/2.6.5/2.7.4.1三個版本,發現每一個版本都略有不一樣。以下所示:


圖中標記爲①的三處代碼:


2.6.0版本的是有Bug的代碼,緣由在上面說過了。


2.6.5版本的修復方式是獲取隨機數的時候加一,因此取值範圍就從[0,totalWeight)變成了[0,totalWeight],這樣就能夠避免這個問題。

2.7.4.1版本的取值範圍仍是[0,totalWeight),可是它的修復方法體如今了標記爲②的代碼處。2.6.0/2.6.5版本標記爲②的地方都是if(offsetWeight<=0),而2.7.4.1版本變成了if(offsetWeight<0)


你品一品,是否是效果是同樣的,可是更加優雅了。

朋友們,魔鬼,都在細節裏啊!


好了,進入下一議題。


一致性哈希負載均衡

這一部分是對於Dubbo負載均衡策略之一的一致性哈希負載均衡的詳細分析。對源碼逐行解讀、根據實際運行結果,配以豐富的圖片,多是東半球講一致性哈希算法在Dubbo中的實現最詳細的文章了。

文中所示源碼,沒有特別標註的地方,均爲2.7.4.1版本。


在撰寫本文的過程當中,發現了Dubbo2.7.0版本以後的一個bug。會致使性能問題,若是大家的負載均衡配置的是一致性哈希或者考慮使用一致性哈希的話,能夠了解一下。


哈希算法


在介紹一致性哈希算法以前,咱們看看哈希算法,以及它解決了什麼問題,帶來了什麼問題。

如上圖所示,假設0,1,2號服務器都存儲的有用戶信息,那麼當咱們須要獲取某用戶信息時,由於咱們不知道該用戶信息存放在哪一臺服務器中,因此須要分別查詢0,1,2號服務器。這樣獲取數據的效率是極低的。


對於這樣的場景,咱們能夠引入哈希算法。

仍是上面的場景,但前提是每一臺服務器存放用戶信息時是根據某一種哈希算法存放的。因此取用戶信息的時候,也按照一樣的哈希算法取便可。


假設咱們要查詢用戶號爲100的用戶信息,通過某個哈希算法,好比這裏的userId mod n,即100 mod 3結果爲1。因此用戶號100的這個請求最終會被1號服務器接收並處理。


這樣就解決了無效查詢的問題。


可是這樣的方案會帶來什麼問題呢?


擴容或者縮容時,會致使大量的數據遷移。最少也會影響百分之50的數據。

爲了說明問題,咱們加入一臺服務器3。服務器的數量n就從3變成了4。仍是查詢用戶號爲100的用戶信息時,100 mod 4結果爲0。這時,請求就被0號服務器接收了。


當服務器數量爲3時,用戶號爲100的請求會被1號服務器處理。


當服務器數量爲4時,用戶號爲100的請求會被0號服務器處理。


因此,當服務器數量增長或者減小時,必定會涉及到大量數據遷移的問題。可謂是牽一髮而動全身。


對於上訴哈希算法其優勢是簡單易用,大多數分庫分表規則就採起的這種方式。通常是提早根據數據量,預先估算好分區數。


缺點是因爲擴容或收縮節點致使節點數量變化時,節點的映射關係須要從新計算,會致使數據進行遷移。因此擴容時一般採用翻倍擴容,避免數據映射所有被打亂,致使全量遷移的狀況,這樣只會發生50%的數據遷移。


假設這是一個緩存服務,數據的遷移會致使在遷移的時間段內,有緩存是失效的。


緩存失效,可怕啊。還記得我以前的文章嗎,《當週杰倫把QQ音樂幹翻的時候,做爲程序猿我看到了什麼?》就是講緩存擊穿、緩存穿透、緩存雪崩的場景和對應的解決方案。


一致性哈希算法


爲了解決哈希算法帶來的數據遷移問題,一致性哈希算法應運而生。


對於一致性哈希算法,官方說法以下:

一致性哈希算法在1997年由麻省理工學院提出,是一種特殊的哈希算法,在移除或者添加一個服務器時,可以儘量小地改變已存在的服務請求與處理請求服務器之間的映射關係。一致性哈希解決了簡單哈希算法在分佈式哈希表( Distributed Hash Table,DHT) 中存在的動態伸縮等問題。


什麼意思呢?我用大白話加畫圖的方式給你簡單的介紹一下。


一致性哈希,你能夠想象成一個哈希環,它由0到2^32-1個點組成。A,B,C分別是三臺服務器,每一臺的IP加端口通過哈希計算後的值,在哈希環上對應以下:


當請求到來時,對請求中的某些參數進行哈希計算後,也會得出一個哈希值,此值在哈希環上也會有對應的位置,這個請求會沿着順時針的方向,尋找最近的服務器來處理它,以下圖所示:


一致性哈希就是這麼個東西。那它是怎麼解決服務器的擴容或收縮致使大量的數據遷移的呢?


看一下當咱們使用一致性哈希算法時,加入服務器會發什麼事情。

當咱們加入一個D服務器後,假設其IP加端口,通過哈希計算後落在了哈希環上圖中所示的位置。


這時影響的範圍只有圖中標註了五角星的區間。這個區間的請求從原來的由C服務器處理變成了由D服務器請求。而D到C,C到A,A到B這個區間的請求沒有影響,加入D節點後,A、B服務器是無感知的。


因此,在一致性哈希算法中,若是增長一臺服務器,則受影響的區間僅僅是新服務器(D)在哈希環空間中,逆時針方向遇到的第一臺服務器(B)之間的區間,其它區間(D到C,C到A,A到B)不會受到影響。


在加入了D服務器的狀況下,咱們再假設一段時間後,C服務器宕機了:

當C服務器宕機後,影響的範圍也是圖中標註了五角星的區間。C節點宕機後,B、D服務器是無感知的。


因此,在一致性哈希算法中,若是宕機一臺服務器,則受影響的區間僅僅是宕機服務器(C)在哈希環空間中,逆時針方向遇到的第一臺服務器(D)之間的區間,其它區間(C到A,A到B,B到D)不會受到影響。


綜上所述,在一致性哈希算法中,無論是增長節點,仍是宕機節點,受影響的區間僅僅是增長或者宕機服務器在哈希環空間中,逆時針方向遇到的第一臺服務器之間的區間,其它區間不會受到影響。


是否是很完美?


不是的,理想和現實的差距是巨大的。


一致性哈希算法帶來了什麼問題?

當節點不多的時候可能會出現這樣的分佈狀況,A服務會承擔大部分請求。這種狀況就叫作數據傾斜。


怎麼解決數據傾斜呢?加入虛擬節點。


怎麼去理解這個虛擬節點呢?


首先一個服務器根據須要能夠有多個虛擬節點。假設一臺服務器有n個虛擬節點。那麼哈希計算時,可使用IP+端口+編號的形式進行哈希值計算。其中的編號就是0到n的數字。因爲IP+端口是同樣的,因此這n個節點都是指向的同一臺機器。


以下圖所示:

在沒有加入虛擬節點以前,A服務器承擔了絕大多數的請求。可是假設每一個服務器有一個虛擬節點(A-1,B-1,C-1),通過哈希計算後落在瞭如上圖所示的位置。那麼A服務器的承擔的請求就在必定程度上(圖中標註了五角星的部分)分攤給了B-一、C-1虛擬節點,實際上就是分攤給了B、C服務器。


一致性哈希算法中,加入虛擬節點,能夠解決數據傾斜問題。


當你在面試的過程當中,若是聽到了相似於數據傾斜的字眼。那大機率是在問你一致性哈希算法和虛擬節點。


在介紹了相關背景後,咱們能夠去看看一致性哈希算法在Dubbo中的應用了。


一致性哈希算法在Dubbo中的應用


前面咱們說了Dubbo中負載均衡的實現是經過org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance中的 doSelect 抽象方法實現的,一致性哈希負載均衡的實現類以下所示:

org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance


因爲一致性哈希實現類看起來稍微有點抽象,不太好演示,因此我想到了一個"騷"操做。前面的文章說過 LoadBalance 是一個 SPI 接口:


既然是一個 SPI 接口,那咱們能夠本身擴展一個如出一轍的算法,只是在算法裏面加入一點輸出語句方便咱們觀察狀況。怎麼擴展 SPI 接口就不描述了,只要記住代碼裏面的輸出語句都是額外加的,此外沒有任何改動便可,以下:


整個類以下圖片所示,請先看完整個類,有一個總體的概念後,我會進行方法級別的分析。


圖片很長,其中我加了不少註釋和輸出語句,能夠點開大圖查看,必定會幫你更加好的理解一致性哈希在Dubbo中的應用:


改造以後,咱們先把程序跑起來,有了輸出就好分析了。


服務端代碼以下:

其中的端口是須要手動修改的,我分別啓動服務在20881和20882端口。


項目中provider.xml配置以下:


consumer.xml配置以下:


而後,啓動在20881和20882端口分別啓動兩個服務端。客戶端消費以下:


運行結果輸出以下,能夠先看個大概的輸出,下面會對每一部分輸出進行逐一的解讀。



好了,用例也跑起來了,日誌也有了。接下來開始結合代碼和日誌進行方法級別的分析。


首先是doSelect方法的入口:

從上圖咱們知道了,第一次調用須要對selectors進行put操做,selectors的 key 是接口中定義的方法,value 是 ConsistentHashSelector 內部類


ConsistentHashSelector經過調用其構造函數進行初始化的。invokers(服務端)做爲參數傳遞到了構造函數中,構造函數裏面的邏輯,就是把服務端映射到哈希環上的過程,請看下圖,結合代碼,仔細分析輸出數據:

從上圖能夠看出,當 ConsistentHashSelector 的構造方法調用完成後,8個虛擬節點在哈希環上已經映射完成。兩臺服務器,每一臺4個虛擬節點組成了這8個虛擬節點。


doSelect方法繼續執行,並打印出每一個虛擬節點的哈希值和對應的服務端,請仔細品讀下圖:


說明一下:上面圖中的哈希環是沒有考慮比例的,僅僅是展示了兩個服務器在哈希環上的相對位置。並且爲了演示說明方便,僅僅只有8個節點。假設咱們有4臺服務器,每臺服務器的虛擬節點是默認值(160),這個狀況下哈希環上一共有160*4=640個節點。


哈希環映射完成後,接下來的邏輯是把此次請求通過哈希計算後,映射到哈希環上,並順時針方向尋找遇到的第一個節點,讓該節點處理該請求:


還記得地址爲 468e8565 的 A 服務器是什麼端口嗎?前面的圖片中有哦,該服務對應的端口是 20882 。


最後咱們看看輸出結果:

和咱們預期的一致。整個調用就算是完成了。

再對兩個方法進行一個補充說明。


第一個方法是 selectForKey,這個方法裏面邏輯以下圖所示:


虛擬節點都存儲在 TreeMap 中。順時針查詢的邏輯由 TreeMap 保證。看一下下面的 Demo 你就明白了。


第二個方法是 hash 方法,其中的 & 0xFFFFFFFFL 的目的以下:


&是位運算符,而 0xFFFFFFFFL 轉換爲四字節表現後,其低32位全是1,因此保證了哈希環的範圍是 [0,Integer.MAX_VALUE]:


因此這裏咱們能夠改造這個哈希環的範圍,假設咱們改成 100000。十進制的 100000 對於的 16 進製爲 186A0 。因此咱們改造後的哈希算法爲:


再次調用後能夠看到,計算後的哈希值都在10萬之內。可是分佈極不均勻,說明修改數據後這個哈希算法不是一個優秀的哈希算法:


以上,就是對一致性哈希算法在Dubbo中的實現的解讀。須要特殊說明一下的是,一致性哈希負載均衡策略和權重沒有任何關係。


我又發現了一個BUG


前面我介紹了Dubbo 2.6.5版本以前,最小活躍數算法的兩個 bug。


很不幸,此次我又發現了Dubbo 2.7.4.1版本,一致性哈希負載均衡策略的一個bug,我提交了issue 地址以下:

https://github.com/apache/dubbo/issues/5429


我在這裏詳細說一下這個Bug現象、緣由和個人解決方案。


現象以下,咱們調用三次服務端:


輸出日誌以下(有部分刪減):


能夠看到,在三次調用的過程當中並無發生服務的上下線操做,可是每一次調用都從新進行了哈希環的映射。而咱們預期的結果是應該只有在第一次調用的時候進行哈希環的映射,若是沒有服務上下線的操做,後續請求根據已經映射好的哈希環進行處理。


上面輸出的緣由是因爲每次調用的invokers的identityHashCode發生了變化:


咱們看一下三次調用invokers的狀況:

通過debug咱們能夠看出由於每次調用的invokers地址值不是同一個,因此System.identityHashCode(invokers)方法返回的值都不同。


接下來的問題就是爲何每次調用的invokers地址值都不同呢?


通過Debug以後,能夠找到這個地方:

org.apache.dubbo.rpc.cluster.RouterChain#route


問題就出在這個TagRouter中:

org.apache.dubbo.rpc.cluster.router.tag.TagRouter#filterInvoker


因此,在TagRouter中的stream操做,改變了invokers,致使每次調用時其

System.identityHashCode(invokers)返回的值不同。因此每次調用都會進行哈希環的映射操做,在服務節點多,虛擬節點多的狀況下會有必定的性能問題。


到這一步,問題又發生了變化。這個TagRouter怎麼來的呢?


若是瞭解Dubbo 2.7.x版本新特性的朋友可能知道,標籤路由是Dubbo2.7引入的新功能。


經過加載下面的配置加載了RouterFactrory:

META-INF\dubbo\internal\org.apache.dubbo.rpc.cluster.RouterFactory(Dubbo 2.7.0版本以前)

META-INF\dubbo\internal\com.alibaba.dubbo.rpc.cluster.RouterFactory(Dubbo 2.7.0以前)

下面是Dubbo 2.6.7(2.6.x的最後一個版本)和Dubbo 2.7.0版本該文件的對比:


能夠看到確實是在 Dubbo 2.7.0 以後引入了 TagRouter。


至此,Dubbo 2.7.0 版本以後,一致性哈希負載均衡算法的 Bug 的前因後果也介紹清楚了。


解決方案是什麼呢?特別簡單,把獲取 identityHashCode 的方法從 System.identityHashCode(invokers) 修改成 invokers.hashCode() 便可。


此方案是我提的 issue 裏面的評論,這裏 System.identityHashCode 和 hashCode 之間的聯繫和區別就不進行展開講述了,不清楚的你們能夠自行了解一下。


(能夠看看個人這篇文章:夠強!一行代碼就修復了我提的Dubbo的Bug。




改完以後,咱們再看看運行效果:


能夠看到第二次調用的時候並無進行哈希環的映射操做,而是直接取到了值,進行調用。


加入節點,畫圖分析


最後,我再分析一種狀況。在A、B、C三個服務器(2088一、2088二、20883端口)都在正常運行,哈希映射已經完成的狀況下,咱們再啓動一個D節點(20884端口),這時的日誌輸出和對應的哈希環變化狀況以下:


根據日誌做圖以下:


根據輸出日誌和上圖再加上源碼,你再細細回味一下。我我的以爲仍是講的很是詳細了。


一致性哈希的應用場景


當你們談到一致性哈希算法的時候,首先的第一印象應該是在緩存場景下的使用,由於在一個優秀的哈希算法加持下,其上下線節點對總體數據的影響(遷移)都是比較友好的。


可是想一下爲何 Dubbo 在負載均衡策略裏面提供了基於一致性哈希的負載均衡策略?它的實際使用場景是什麼?


我最開始也想不明白。我想的是在 Dubbo 的場景下,假設需求是想要一個用戶的請求一直讓一臺服務器處理,那咱們能夠採用一致性哈希負載均衡策略,把用戶號進行哈希計算,能夠實現這樣的需求。可是這樣的需求未免有點太牽強了,適用場景略小。


直到有天晚上,我睡覺以前,電光火石之間忽然想到了一個稍微適用的場景了。當時的狀況大概是這樣的。


若是需求是須要保證某一類請求必須順序處理呢?


若是你用其餘負載均衡策略,請求分發到了不一樣的機器上去,就很難保證請求的順序處理了。好比A,B請求要求順序處理,如今A請求先發送,被負載到了A服務器上,B請求後發送,被負載到了B服務器上。而B服務器因爲性能好或者當前沒有其餘請求或者其餘緣由極有可能在A服務器還在處理A請求以前就把B請求處理完成了。這樣不符合咱們的要求。


這時,一致性哈希負載均衡策略就上場了,它幫咱們保證了某一類請求都發送到固定的機器上去執行。好比把同一個用戶的請求發送到同一臺機器上去執行,就意味着把某一類請求發送到同一臺機器上去執行。因此咱們只須要在該機器上運行的程序中保證順序執行就好了,好比你加一個隊列。


一致性哈希算法+隊列,能夠實現順序處理的需求。


好了,一致性哈希負載均衡算法就寫到這裏。


繼續進入下一個議題。


加權輪詢負載均衡

這一小節是對於Dubbo負載均衡策略之一的加權隨機算法的詳細分析。


2.6.4 版本聊起,該版本在某些狀況下存在着比較嚴重的性能問題。由問題入手,層層深刻,瞭解該算法在 Dubbo 中的演變過程,讀懂它的前世此生。

什麼是輪詢?


在描述加權輪詢以前,先解釋一下什麼是輪詢算法,以下圖所示:


假設咱們有A、B、C三臺服務器,共計處理6個請求,服務處理請求的狀況以下:


  1. 第一個請求發送給了A服務器

  2. 第二個請求發送給了B服務器

  3. 第三個請求發送給了C服務器

  4. 第四個請求發送給了A服務器

  5. 第五個請求發送給了B服務器

  6. 第六個請求發送給了C服務器

  7. ......


上面這個例子演示的過程就叫作輪詢。能夠看出,所謂輪詢就是將請求輪流分配給每臺服務器


輪詢的優勢是無需記錄當前全部服務器的連接狀態,因此它一種無狀態負載均衡算法,實現簡單,適用於每臺服務器性能相近的場景下。


輪詢的缺點也是顯而易見的,它的應用場景要求全部服務器的性能都相同,很是的侷限。


大多數實際狀況下,服務器性能是各有差別,針對性能好的服務器,咱們須要讓它承擔更多的請求,即須要給它配上更高的權重。


因此加權輪詢,應運而生。


什麼是加權輪詢?


爲了解決輪詢算法應用場景的侷限性。當遇到每臺服務器的性能不一致的狀況,咱們須要對輪詢過程進行加權,以調控每臺服務器的負載。


通過加權後,每臺服務器可以獲得的請求數比例,接近或等於他們的權重比。好比服務器 A、B、C 權重比爲 5:3:2。那麼在10次請求中,服務器 A 將收到其中的5次請求,服務器 B 會收到其中的3次請求,服務器 C 則收到其中的2次請求。

這裏要和加權隨機算法作區分哦。直接把前面介紹的加權隨機算法畫的圖拿過來:

上面這圖是按照比例畫的,能夠直觀的看到,對於某一個請求,區間(權重)越大的服務器,就越可能會承擔這個請求。因此,當請求足夠多的時候,各個服務器承擔的請求數,應該就是區間,即權重的比值。

假設有A、B、C三臺服務器,權重之比爲5:3:2,一共處理10個請求。

那麼負載均衡採用加權隨機算法時,頗有可能A、B服務就處理完了這10個請求,由於它是隨機調用。

採用負載均衡採用輪詢加權算法時,A、B、C服務必定是分別承擔五、三、2個請求。

Dubbo2.6.4版本的實現

對於Dubbo2.6.4版本的實現分析,能夠看下圖,我加了不少註釋,其中的輸出語句都是我加的:

示例代碼仍是沿用以前文章中的Demo,這裏分別在 2088一、2088二、20883 端口啓動三個服務,各自的權重分別爲 1,2,3。

客戶端調用 8 次:

輸出結果以下:

能夠看到第七次調用後mod=0,回到了第一次調用的狀態。造成了一個閉環。

再看看判斷的條件是什麼:

其中mod在代碼中扮演了極其重要的角色,mod根據一個方法的調用次數不一樣而不一樣,取值範圍是[0,weightSum)。

由於weightSum=6,因此列舉mod不一樣值時,最終的選擇結果和權重變化:

能夠看到20881,20882,20883承擔的請求數量比值爲1:2:3。同時咱們能夠看出,當 mod >= 1 後,20881端口的服務就不會被選中了,由於它的權重被減爲0了。當 mod >= 4 後,20882端口的服務就不會被選中了,由於它的權重被減爲0了。

結合判斷條件和輸出結果,咱們詳細分析一下(下面內容稍微有點繞,若是看不懂,多結合上面的圖片看幾回):

第一次調用

mod=0,第一次循環就知足代碼塊①的條件,直接返回當前循環的invoker,即20881端口的服務。此時各端口的權重狀況以下:

第二次調用

mod=1,須要進入代碼塊②,對mod進行一次遞減。

  1. 第一次循環對20881端口的服務權重減一,mod-1=0。

  2. 第二次循環,mod=0,循環對象是20882端口的服務,權重爲2,知足代碼塊①,返回當前循環的20882端口的服務。


此時各端口的權重狀況以下:



第三次調用



mod=2,須要進入代碼塊②,對mod進行兩次遞減。


  1. 第一次循環對20881端口的服務權重減一,mod-1=1;

  2. 第二次循環對20882端口的服務權重減一,mod-1=0;

  3. 第三次循環時,mod已經爲0,當前循環的是20883端口的服務,權重爲3,知足代碼塊①,返回當前循環的20883端口的服務。


此時各端口的權重狀況以下:



第四次調用



mod=3,須要進入代碼塊②,對mod進行三次遞減。


  1. 第一次循環對20881端口的服務權重減一,從1變爲0,mod-1=2;

  2. 第二次循環對20882端口的服務權重減一,從2變爲1,mod-1=1;

  3. 第三次循環對20883端口的服務權重減一,從3變爲2,mod-1=0;

  4. 第四次循環的是20881端口的服務,此時mod已經爲0,可是20881端口的服務的權重已經變爲0了,不知足代碼塊①和代碼塊②,進入第五次循環。

  5. 第五次循環的是20882端口的服務,當前權重爲1,mod=0,知足代碼塊①,返回20882端口的服務。


此時各端口的權重狀況以下:



第五次調用



mod=4,須要進入代碼塊②,對mod進行四次遞減。


  1. 第一次循環對20881端口的服務權重減一,從1變爲0,mod-1=3;

  2. 第二次循環對20882端口的服務權重減一,從2變爲1,mod-1=2;

  3. 第三次循環對20883端口的服務權重減一,從3變爲2,mod-1=1;

  4. 第四次循環的是20881端口的服務,此時mod爲1,可是20881端口的服務的權重已經變爲0了,不知足代碼塊②,mod不變,進入第五次循環。

  5. 第五次循環時,mod爲1,循環對象是20882端口的服務,權重爲1,知足代碼塊②,權重從1變爲0,mod從1變爲0,進入第六次循環。

  6. 第六次循環時,mod爲0,循環對象是20883端口的服務,權重爲2,知足條件①,返回當前20883端口的服務。


此時各端口的權重狀況以下:



第六次調用



第六次調用,mod=5,會循環九次,最終選擇20883端口的服務,讀者能夠自行分析一波,分析出來了,就瞭解的透透的了。



第七次調用



第七次調用,又回到mod=0的狀態:


2.6.4版本的加權輪詢就分析完了,可是事情並無這麼簡單。這個版本的加權輪詢是有性能問題的。


該問題對應的issue地址以下:

https://github.com/apache/dubbo/issues/2578

問題出如今invoker返回的時機上:


截取issue裏面的一個回答:


10分鐘才選出一個invoker,還怎麼玩?


有時間能夠讀一讀這個issue,裏面各路大神針對該問題進行了激烈的討論,第一種改造方案被接受後,很快就被推翻,被第二種方案代替,能夠說優化思路十分值得學習,很精彩,接下來的行文路線就是按照該issue展開的。

推翻,重建。


上面的代碼時間複雜度是O(mod),而第一次修復以後時間複雜度下降到了常量級別。能夠說是一次很是優秀的優化,值得咱們學習,看一下優化以後的代碼:


其關鍵優化的點是這段代碼,我加入輸出語句,便於分析。

輸出日誌以下:


把上面的輸出轉化到表格中去,7次請求的選擇過程以下:

該算法的原理是:

把服務端都放到集合中(invokerToWeightList),而後獲取服務端個數(length),並計算出服務端權重最大的值(maxWeight)。


index表示本次請求到來時,處理該請求的服務端下標,初始值爲0,取值範圍是[0,length)。

currentWeight表示當前調度的權重,初始值爲0,取值範圍是[0,maxWeight)。

當請求到來時,從index(就是0)開始輪詢服務端集合(invokerToWeightList),若是是一輪循環的開始(index=0)時,則對currentWeight進行加一操做(不會超過maxWeight),在循環中找出第一個權重大於currentWeight的服務並返回。

這裏說的一輪循環是指index再次變爲0所經歷過的循環,這裏能夠把index=0看作是一輪循環的開始。每一輪循環的次數與Invoker的數量有關,Invoker數量一般不會太多,因此咱們能夠認爲上面代碼的時間複雜度爲常數級。

從issue上看出,這個算法最終被merged了。

可是很快又被推翻了:

這個算法不夠平滑。什麼意思呢?

翻譯一下上面的內容就是:服務器[A, B, C]對應權重[5, 1, 1]。進行7次負載均衡後,選擇出來的序列爲[A, A, A, A, A, B, C]。前5個請求所有都落在了服務器A上,這將會使服務器A短期內接收大量的請求,壓力陡增。而B和C此時無請求,處於空閒狀態。而咱們指望的結果是這樣的[A, A, B, A, C, A, A],不一樣服務器能夠穿插獲取請求。

咱們設置20881端口的權重爲5,2088二、20883端口的權重均爲1。

進行實驗,發現確實如此:能夠看到一共進行7次請求,第1次到5次請求都分發給了權重爲5的20881端口的服務,前五次請求,20881和20882都處於空閒狀態:

轉化爲表格以下:

從表格的最終結果一欄也能夠直觀的看出,七次請求對應的服務器端口爲:

分佈確實不夠均勻。

再推翻,再重建,平滑加權。

從issue中能夠看到,再次重構的加權算法的靈感來源是Nginx的平滑加權輪詢負載均衡:

看代碼以前,先介紹其計算過程。

假設每一個服務器有兩個權重,一個是配置的weight,不會變化,一個是currentWeight會動態調整,初始值爲0。當有新的請求進來時,遍歷服務器列表,讓它的currentWeight加上自身權重。遍歷完成後,找到最大的currentWeight,並將其減去權重總和,而後返回相應的服務器便可。

若是你仍是不知道上面的表格是如何算出來的,我再給你詳細的分析一下第一、2個請求的計算過程:

第一個請求計算過程以下:


第二個請求計算過程以下:

後面的請求你就能夠本身分析了。

從表格的最終結果一欄也能夠直觀的看出,七次請求對應的服務器端口爲:

能夠看到,權重之比一樣是5:1:1,可是最終的請求分發的就比較的"平滑"。對比一下:

對於平滑加權算法,我想多說一句。我以爲這個算法很是的神奇,我是完全的明白了它每一步的計算過程,知道它最終會造成一個閉環,可是我想了好久,我仍是不知道背後的數學原理是什麼,不明白爲何會造成一個閉環,很是的神奇。

很正常,我不糾結的,程序猿的工做不就是這樣嗎?我也不知道爲何,它能工做。別問,問就是玄學,若是必定要說出點什麼的話,我想,我願稱之爲:絕活吧。

可是咱們只要可以理解我前面所表達的平滑加權輪詢算法的計算過程,知道其最終會造成閉環,就能理解下面的代碼。配合代碼中的註釋食用,效果更佳。

如下代碼以及註釋來源官網:

http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html

總結

好了,到這裏關於Dubbo的五種負載均衡策略就講完了。簡單總結一下:(加權隨機算法在講最小活躍數算法的時候提到過,由於原理十分簡單,這裏就不專門拿出章節來描述了。)

最短響應時間負載均衡:在全部服務提供者中選出平均響應時間最短的一個,若是能選出來,則使用選出來的一個。若是不能選出來多個,再根據權重選,若是權重也同樣,則隨機選擇。

一致性哈希負載均衡:在一致性哈希算法中,無論是增長節點,仍是宕機節點,受影響的區間僅僅是增長或者宕機服務器在哈希環空間中,逆時針方向遇到的第一臺服務器之間的區間,其它區間不會受到影響。爲了解決數據傾斜的問題,引入了虛擬節點的概念。一致性哈希算法是 Dubbo 中惟一一個與權重沒有任何關係的負載均衡算法,能夠保證相同參數的請求打到同一臺機器上。

最小活躍數負載均衡:須要配合 activeFilter 使用,活躍數在方法調用先後進行維護,響應越快的服務器堆積的請求越少,對應的活躍數也少。Dubbo 在選擇的時候遵循下面的規則,有最小活躍數用最小活躍數,沒有最小活躍數根據權重選擇,權重同樣則隨機返回的負載均衡算法。

加權隨機算法:隨機,顧名思義,就是從多個服務提供者中隨機選擇一個出來。加權,就是指須要按照權重設置隨機機率。常見場景就是對於性能好的機器能夠把對應的權重設置的大一點,而性能相對較差的,權重設置的小一點。哎,像極了這個社會上的某些現象,對外宣傳是隨機搖號,背後指不定有一羣權重高的人呢。

加權輪詢負載均衡:輪詢就是雨露均沾的意思,全部的服務提供者都須要調用。而當輪詢遇到加權則可讓請求(不論多少)嚴格按照咱們的權重之比進行分配。好比有A、B、C三臺服務器,權重之比爲5:3:2,一共處理10個請求。那麼採用負載均衡採用輪詢加權算法時,A、B、C服務必定是分別承擔五、三、2個請求。同時須要注意的是加權輪詢算法的兩次升級過程,以及最終的「平滑」的解決方案。

                              

相關文章
相關標籤/搜索