Dubbo是一個分佈式服務框架,能避免單點故障和支持服務的橫向擴容。一個服務一般會部署多個實例。如何從多個服務 Provider 組成的集羣中挑選出一個進行調用,就涉及到一個負載均衡的策略。html
在討論負載均衡以前,我想先解釋一下這3個概念。node
這3個概念容易混淆。他們都描述了怎麼從多個 Provider 中選擇一個來進行調用。那他們到底有什麼區別呢?下面我來舉一個簡單的例子,把這幾個概念闡述清楚吧。算法
有一個Dubbo的用戶服務,在北京部署了10個,在上海部署了20個。一個杭州的服務消費方發起了一次調用,而後發生瞭如下的事情:數據庫
上面的第1,2,4步驟就分別對應了路由,負載均衡和集羣容錯。 Dubbo中,先經過路由,從多個 Provider 中按照路由規則,選出一個子集。再根據負載均衡從子集中選出一個 Provider 進行本次調用。若是調用失敗了,根據集羣容錯策略,進行重試或定時重發或快速失敗等。 能夠看到Dubbo中的路由,負載均衡和集羣容錯發生在一次RPC調用的不一樣階段。最早是路由,而後是負載均衡,最後是集羣容錯。 本文檔只討論負載均衡,路由和集羣容錯在其餘的文檔中進行說明。apache
Dubbo內置了4種負載均衡策略:緩存
顧名思義,隨機負載均衡策略就是從多個 Provider 中隨機選擇一個。可是 Dubbo 中的隨機負載均衡有一個權重的概念,即按照權重設置隨機機率。好比說,有10個 Provider,並非說,每一個 Provider 的機率都是同樣的,而是要結合這10個 Provider 的權重來分配機率。服務器
Dubbo中,能夠對 Provider 設置權重。好比機器性能好的,能夠設置大一點的權重,性能差的,能夠設置小一點的權重。權重會對負載均衡產生影響。能夠在Dubbo Admin中對 Provider 進行權重的設置。網絡
基於權重的負載均衡算法負載均衡
隨機策略會先判斷全部的 Invoker 的權重是否是同樣的,若是都是同樣的,那麼處理就比較簡單了。使用random.nexInt(length)就能夠隨機生成一個 Invoker 的序號,根據序號選擇對應的 Invoker 。若是沒有在Dubbo Admin中對服務 Provider 設置權重,那麼全部的 Invoker 的權重就是同樣的,默認是100。 若是權重不同,那就須要結合權重來設置隨機機率了。算法大概以下: 假若有4個 Invoker。框架
invoker | weight |
---|---|
A | 10 |
B | 20 |
C | 20 |
D | 30 |
A,B,C和D總的權重是10 + 20 + 20 + 30 = 80。將80個數分佈在以下的圖中:
+-----------------------------------------------------------------------------------+ | | | | | +-----------------------------------------------------------------------------------+ 1 10 30 50 80 |-----A----|---------B----------|----------C---------|---------------D--------------| ---------------------15 -------------------------------------------37 -----------------------------------------------------------54
上面的圖中一共有4塊區域,長度分別是A,B,C和D的權重。使用random.nextInt(10 + 20 + 20 + 30),從80個數中隨機選擇一個。而後再判斷該數分佈在哪一個區域。好比,若是隨機到37,37是分佈在C區域的,那麼就選擇 Invoker C。15是在B區域,54是在D區域。
隨機負載均衡源碼
(有權重:在權重和的範圍內生成一個隨機數,遍歷invoker,用權重和循環減去invoker的權重,結果小於0時的invoker被選中)
下面是隨機負載均衡的源碼,爲了方便閱讀和理解,我把無關部分都去掉了。
public class RandomLoadBalance extends AbstractLoadBalance { private final Random random = new Random(); protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { int length = invokers.size(); // Invoker 總數 int totalWeight = 0; // 全部 Invoker 的權重的和 // 判斷是否是全部的 Invoker 的權重都是同樣的 // 若是權重都同樣,就簡單了。直接用Random生成索引就能夠了。 boolean sameWeight = true; for (int i = 0; i < length; i++) { int weight = getWeight(invokers.get(i), invocation); totalWeight += weight; // Sum if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) { sameWeight = false; } } if (totalWeight > 0 && !sameWeight) { // 若是不是全部的 Invoker 權重都相同,那麼基於權重來隨機選擇。權重越大的,被選中的機率越大 int offset = random.nextInt(totalWeight); for (int i = 0; i < length; i++) { offset -= getWeight(invokers.get(i), invocation); if (offset < 0) { return invokers.get(i); } } } // 若是全部 Invoker 權重相同 return invokers.get(random.nextInt(length)); } }
輪詢負載均衡,就是依次的調用全部的 Provider。和隨機負載均衡策略同樣,輪詢負載均衡策略也有權重的概念。 輪詢負載均衡算法可讓RPC調用嚴格按照咱們設置的比例來分配。不論是少許的調用仍是大量的調用。可是輪詢負載均衡算法也有不足的地方,存在慢的 Provider 累積請求的問題,好比:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,長此以往,全部請求都卡在調到第二臺上,致使整個系統變慢。
官方解釋:
最少活躍調用數,相同活躍數的隨機,活躍數指調用先後計數差,使慢的機器收到更少。
這個解釋好像說的不是太明白。目的是讓更慢的機器收到更少的請求,但具體怎麼實現的仍是不太清楚。舉個例子:每一個服務維護一個活躍數計數器。當A機器開始處理請求,該計數器加1,此時A還未處理完成。若處理完畢則計數器減1。而B機器接受到請求後很快處理完畢。那麼A,B的活躍數分別是1,0。當又產生了一個新的請求,則選擇B機器去執行(B活躍數最小),這樣使慢的機器A收到少的請求。
處理一個新的請求時,Consumer 會檢查全部 Provider 的活躍數,若是具備最小活躍數的 Invoker 只有一個,直接返回該 Invoker:
if (leastCount == 1) { // 若是隻有一個最小則直接返回 return invokers.get(leastIndexs[0]); }
若是最小活躍數的 Invoker 有多個,且權重不相等同時總權重大於0,這時隨機生成一個權重,範圍在 (0,totalWeight) 間內。最後根據隨機生成的權重,來選擇 Invoker。
if (! sameWeight && totalWeight > 0) { // 若是權重不相同且權重大於0則按總權重數隨機 int offsetWeight = random.nextInt(totalWeight); // 並肯定隨機值落在哪一個片段上 for (int i = 0; i < leastCount; i++) { int leastIndex = leastIndexs[i]; offsetWeight -= getWeight(invokers.get(leastIndex), invocation); if (offsetWeight <= 0) return invokers.get(leastIndex); } }
概述:
若是採用經常使用的hash(object)%N算法,那麼在有機器添加或者刪除後,映射關係就變了,不少原有的緩存就沒法找到了
一致性hash:添加刪除機器先後映射關係一致,固然,不是嚴格一致。實現的關鍵是環形Hash空間。將數據和機器都hash到環上,數據映射到順時針離本身最近的機器中。
一致性hash單調性體如今:
不管是新增主機仍是刪除主機,被影響的都是離那臺主機最近的那些節點,其餘節點映射關係沒有影響
使用一致性 Hash 算法,讓相同參數的請求老是發到同一 Provider。 當某一臺 Provider 崩潰時,本來發往該 Provider 的請求,基於虛擬節點,平攤到其它 Provider,不會引發劇烈變更。 參見我另外一篇:https://www.cnblogs.com/twoheads/p/10135896.html
缺省只對第一個參數Hash,若是要修改,請配置:
<dubbo:parameter key="hash.arguments" value="0,1" />
缺省用160份虛擬節點,若是要修改,請配置:
<dubbo:parameter key="hash.nodes" value="320" />
優勢:一致性Hash算法能夠和緩存機制配合起來使用。好比有一個服務getUserInfo(String userId)。設置了Hash算法後,相同的userId的調用,都會發送到同一個 Provider。這個 Provider 上能夠把用戶數據在內存中進行緩存,減小訪問數據庫或分佈式緩存的次數。若是業務上容許這部分數據有一段時間的不一致,能夠考慮這種作法。減小對數據庫,緩存等中間件的依賴和訪問次數,同時減小了網絡IO操做,提升系統性能。
若是不指定負載均衡,默認使用隨機負載均衡。咱們也能夠根據本身的須要,顯式指定一個負載均衡。 能夠在多個地方類來配置負載均衡,好比 Provider 端,Consumer端,服務級別,方法級別等。
<dubbo:service interface="..." loadbalance="roundrobin" />
該服務的全部方法都使用roundrobin負載均衡。
<dubbo:reference interface="..." loadbalance="roundrobin" />
該服務的全部方法都使用roundrobin負載均衡。
<dubbo:service interface="..."> <dubbo:method name="hello" loadbalance="roundrobin"/> </dubbo:service>
只有該服務的hello方法使用roundrobin負載均衡。
<dubbo:reference interface="..."> <dubbo:method name="hello" loadbalance="roundrobin"/> </dubbo:reference>
只有該服務的hello方法使用roundrobin負載均衡。
和Dubbo其餘的配置相似,多個配置是有覆蓋關係的:
因此,上面4種配置的優先級是:
Dubbo的4種負載均衡的實現,大多數狀況下能知足要求。有時候,由於業務的須要,咱們可能須要實現本身的負載均衡策略。本章只說明如何配置負載均衡算法。關於Dubbo擴展機制的更多內容,請前往Dubbo可擴展機制實戰。
略
轉自dubbo官網:
http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html