成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Nacos客戶端是如何實現實例獲取的負載均衡呢?

開發 前端
本篇文章追蹤Nacos客戶端源碼,分析了從實例列表中獲得其中一個實例的算法,也就是隨機權重負載均衡算法。整體業務邏輯比較簡單,從ServiceInfo中獲得實例列表,一路篩選,選中目標實例,然后根據它們的權重進行二次處理,數據結構封裝,最后基于Arrays#binarySearch提供的二分查找法來獲得對應的實例。

[[418899]]

前面我們講了Nacos客戶端如何獲取實例列表,如何進行緩存處理,以及如何訂閱實例列表的變更。在獲取到一個實例列表之后,你是否想過一個問題:如果實例列表有100個實例,Nacos客戶端是如何從中選擇一個呢?

這篇文章,就帶大家從源碼層面分析一下,Nacos客戶端采用了如何的算法來從實例列表中獲取一個實例進行請求的。也可以稱作是Nacos客戶端的負載均衡算法。

單個實例獲取

NamingService不僅提供了獲取實例列表的方法,也提供了獲取單個實例的方法,比如:

  1. Instance selectOneHealthyInstance(String serviceName, String groupName, List<String> clusters, boolean subscribe) 
  2.         throws NacosException; 

該方法會根據預定義的負載算法,從實例列表中獲得一個健康的實例。其他重載的方法功能類似,最終都會調用該方法,我們就以此方法為例來分析一下具體的算法。

具體實現代碼:

  1. @Override 
  2. public Instance selectOneHealthyInstance(String serviceName, String groupName, List<String> clusters, 
  3.         boolean subscribe) throws NacosException { 
  4.     String clusterString = StringUtils.join(clusters, ","); 
  5.     if (subscribe) { 
  6.         // 獲取ServiceInfo 
  7.         ServiceInfo serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, clusterString); 
  8.         if (null == serviceInfo) { 
  9.             serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString); 
  10.         } 
  11.         // 通過負載均衡算法獲得其中一個實例 
  12.         return Balancer.RandomByWeight.selectHost(serviceInfo); 
  13.     } else { 
  14.         // 獲取ServiceInfo 
  15.         ServiceInfo serviceInfo = clientProxy 
  16.                 .queryInstancesOfService(serviceName, groupName, clusterString, 0, false); 
  17.         // 通過負載均衡算法獲得其中一個實例 
  18.         return Balancer.RandomByWeight.selectHost(serviceInfo); 
  19.     } 

selectOneHealthyInstance方法邏輯很簡單,調用我們之前講到的方法獲取ServiceInfo對象,然后作為參數傳遞給負載均衡算法,由負載均衡算法計算出最終使用哪個實例(Instance)。

算法參數封裝

先跟蹤一下代碼實現,非核心業務邏輯,只簡單提一下。

上面的代碼可以看出調用的是Balancer內部類RandomByWeight的selectHost方法:

  1. public static Instance selectHost(ServiceInfo dom) { 
  2.     // ServiceInfo中獲去實例列表 
  3.     List<Instance> hosts = selectAll(dom); 
  4.     // ... 
  5.     return getHostByRandomWeight(hosts); 

selectHost方法核心邏輯是從ServiceInfo中獲取實例列表,然后調用getHostByRandomWeight方法:

  1. protected static Instance getHostByRandomWeight(List<Instance> hosts) { 
  2.     // ... 判斷邏輯 
  3.     // 重新組織數據格式 
  4.     List<Pair<Instance>> hostsWithWeight = new ArrayList<Pair<Instance>>(); 
  5.     for (Instance host : hosts) { 
  6.         if (host.isHealthy()) { 
  7.             hostsWithWeight.add(new Pair<Instance>(host, host.getWeight())); 
  8.         } 
  9.     } 
  10.     // 通過Chooser來實現隨機權重負載均衡算法 
  11.     Chooser<String, Instance> vipChooser = new Chooser<String, Instance>("www.taobao.com"); 
  12.     vipChooser.refresh(hostsWithWeight); 
  13.     return vipChooser.randomWithWeight(); 

getHostByRandomWeight前半部分是將Instance列表及其中的權重數據進行轉換,封裝成一個Pair,也就是建立成對的關系。在此過程中只使用了健康的節點。

真正的算法實現則是通過Chooser類來實現的,看名字基本上知道實現的策略是基于權重的隨機算法。

負載均衡算法實現

所有的負載均衡算法實現均位于Chooser類中,Chooser類的提供了兩個方法refresh和randomWithWeight。

refresh方法用于篩選數據、檢查數據合法性和建立算法所需數據模型。

randomWithWeight方法基于前面的數據來進行隨機算法處理。

先看refresh方法:

  1. public void refresh(List<Pair<T>> itemsWithWeight) { 
  2.     Ref<T> newRef = new Ref<T>(itemsWithWeight); 
  3.     // 準備數據,檢查數據 
  4.     newRef.refresh(); 
  5.     // 上面數據刷新之后,這里重新初始化一個GenericPoller 
  6.     newRef.poller = this.ref.poller.refresh(newRef.items); 
  7.     this.ref = newRef; 

基本步驟:

  • 創建Ref類,該類為Chooser的內部類;
  • 調用Ref的refresh方法,用于準備數據、檢查數據等;
  • 數據篩選完成,調用poller#refresh方法,本質上就是創建一個GenericPoller對象;
  • 成員變量重新賦值;

這里重點看Ref#refresh方法:

  1. /** 
  2.  * 獲取參與計算的實例列表、計算遞增數組數總和并進行檢查 
  3.  */ 
  4. public void refresh() { 
  5.     // 實例權重總和 
  6.     Double originWeightSum = (double) 0; 
  7.      
  8.     // 所有健康權重求和 
  9.     for (Pair<T> item : itemsWithWeight) { 
  10.          
  11.         double weight = item.weight(); 
  12.         //ignore item which weight is zero.see test_randomWithWeight_weight0 in ChooserTest 
  13.         // 權重小于等于0則不參與計算 
  14.         if (weight <= 0) { 
  15.             continue
  16.         } 
  17.         // 有效實例放入列表 
  18.         items.add(item.item()); 
  19.         // 如果值無限大 
  20.         if (Double.isInfinite(weight)) { 
  21.             weight = 10000.0D; 
  22.         } 
  23.         // 如果值為非數字 
  24.         if (Double.isNaN(weight)) { 
  25.             weight = 1.0D; 
  26.         } 
  27.         // 權重值累加 
  28.         originWeightSum += weight; 
  29.     } 
  30.      
  31.     double[] exactWeights = new double[items.size()]; 
  32.     int index = 0; 
  33.     // 計算每個節點權重占比,放入數組 
  34.     for (Pair<T> item : itemsWithWeight) { 
  35.         double singleWeight = item.weight(); 
  36.         //ignore item which weight is zero.see test_randomWithWeight_weight0 in ChooserTest 
  37.         if (singleWeight <= 0) { 
  38.             continue
  39.         } 
  40.         // 計算每個節點權重占比 
  41.         exactWeights[index++] = singleWeight / originWeightSum; 
  42.     } 
  43.      
  44.     // 初始化遞增數組 
  45.     weights = new double[items.size()]; 
  46.     double randomRange = 0D; 
  47.     for (int i = 0; i < index; i++) { 
  48.         // 遞增數組第i項值為items前i個值總和 
  49.         weights[i] = randomRange + exactWeights[i]; 
  50.         randomRange += exactWeights[i]; 
  51.     } 
  52.      
  53.     double doublePrecisionDelta = 0.0001; 
  54.     // index遍歷完則返回; 
  55.     // 或weights最后一位值與1相比,誤差小于0.0001,則返回 
  56.     if (index == 0 || (Math.abs(weights[index - 1] - 1) < doublePrecisionDelta)) { 
  57.         return
  58.     } 
  59.     throw new IllegalStateException( 
  60.             "Cumulative Weight calculate wrong , the sum of probabilities does not equals 1."); 

可結合上面代碼中的注釋來理解,核心步驟包括以下:

  • 遍歷itemsWithWeight,計算權重總和數據;非健康節點會被剔除掉;
  • 計算每個節點的權重值在總權重值中的占比,并存儲在exactWeights數組當中;
  • 將exactWeights數組當中值進行數據重構,形成一個遞增數組weights(每個值都是exactWeights坐標值的總和),后面用于隨機算法;
  • 判斷是否循環完成或誤差在指定范圍內(0.0001),符合則返回。

所有數據準備完成,調用隨機算法方法randomWithWeight:

  1. public T randomWithWeight() { 
  2.     Ref<T> ref = this.ref; 
  3.     // 生成0-1之間的隨機數 
  4.     double random = ThreadLocalRandom.current().nextDouble(0, 1); 
  5.     // 采用二分法查找數組中指定值,如果不存在則返回(-(插入點) - 1),插入點即隨機數將要插入數組的位置,即第一個大于此鍵的元素索引。 
  6.     int index = Arrays.binarySearch(ref.weights, random); 
  7.     // 如果沒有查詢到(返回-1或"-插入點") 
  8.     if (index < 0) { 
  9.         index = -index - 1; 
  10.     } else { 
  11.         // 命中直接返回結果 
  12.         return ref.items.get(index); 
  13.     } 
  14.      
  15.     // 判斷坐標未越界 
  16.     if (index < ref.weights.length) { 
  17.         // 隨機數小于指定坐標的數值,則返回坐標值 
  18.         if (random < ref.weights[index]) { 
  19.             return ref.items.get(index); 
  20.         } 
  21.     } 
  22.      
  23.     // 此種情況不應該發生,但如果發生則返回最后一個位置的值 
  24.     /* This should never happen, but it ensures we will return a correct 
  25.      * object in case there is some floating point inequality problem 
  26.      * wrt the cumulative probabilities. */ 
  27.     return ref.items.get(ref.items.size() - 1); 

該方法的基本操作如下:

  • 生成一個0-1的隨機數;
  • 使用Arrays#binarySearch在數組中進行查找,也就是二分查找法。該方法會返回包含key的值,如果沒有則會返回”-1“或”-插入點“,插入點即隨機數將要插入數組的位置,即第一個大于此鍵的元素索引。
  • 如果命中則直接返回;如果未命中則對返回值取反減1,獲得index值;
  • 判斷index值,符合條件,則返回結果;

至此,關于Nacos客戶端實例獲取的負載均衡算法代碼層面追蹤完畢。

算法實例演示

下面用一個實例來演示一下,該算法中涉及的數據變化。為了數據美觀,這里采用4組數據,每組數據進來確保能被整除;

節點及權重數據(前面節點,后面權重)如下:

  1. 1 100 
  2. 2 25 
  3. 3 75 
  4. 4 200 

第一步,計算權重綜合:

  1. originWeightSum = 100 + 25 + 75 + 200 = 400 

第二步,計算每個節點權重比:

  1. exactWeights = {0.25, 0.0625, 0.1875, 0.5} 

第三步,計算遞增數組weights:

  1. weights = {0.25, 0.3125, 0.5, 1} 

第四步,生成0-1的隨機數:

  1. random = 0.3049980013493817 

第五步,調用Arrays#binarySearch從weights中搜索random:

  1. index = -2 

關于Arrays#binarySearch(double[] a, double key)方法這里再解釋一下,如果傳入的key恰好在數組中,比如1,則返回的index為3;如果key為上面的random值,則先找到插入點,取反,減一。

插入點即第一個大于此key的元素索引,那么上面第一個大于0.3049980013493817的值為0.3125,那么插入點值為1;

于是按照公式計算Arrays#binarySearch返回的index為:

  1. index = - ( 1 ) - 1 = -2 

第六步,也就是沒有恰好命中的情況:

  1. index = -( -2 ) - 1 = 1 

然后判斷index是否越界,很明顯 1 < 4,未越界,則返回坐標為1的值。

算法的核心

上面演示了算法,但這個算法真的能夠做到按權重負載嗎?我們來分析一下這個問題。

這個問題的重點不在random值,這個值基本上是隨機的,那么怎么保證權重大的節點獲得的機會更多呢?

這里先把遞增數組weights用另外一個形式來表示:

上面的算法可以看出,weights與exactWeights為size相同的數組,對于同一坐標(index),weights的值是exactWeights包含當前坐標及前面所有坐標值的和。

如果把weights理解成一條線,對應節點的值是線上的一個個點,體現在圖中便是(圖2到圖5)有色(灰色+橘黃色)部分。

而Arrays#binarySearch算法的插入點獲取的是第一個大于key(也就是random)的坐標,也就是說每個節點享有的隨機范圍不同,它們的范圍由當前點和前一個點的區間決定,而這個區間正好是權重比值。

權重比值大的節點,占有的區間就比較多,比如節點1占了1/4,節點4占了1/2。這樣,如果隨機數是均勻分布的,那么占有范圍比較大的節點更容易獲得青睞。也就達到了按照權重獲得被調用的機會了。

小結

本篇文章追蹤Nacos客戶端源碼,分析了從實例列表中獲得其中一個實例的算法,也就是隨機權重負載均衡算法。整體業務邏輯比較簡單,從ServiceInfo中獲得實例列表,一路篩選,選中目標實例,然后根據它們的權重進行二次處理,數據結構封裝,最后基于Arrays#binarySearch提供的二分查找法來獲得對應的實例。

 

而我們需要注意和學習的重點便是權重獲取算法的思想及具體實現,最終達到能夠在實踐中進行運用。

 

責任編輯:武曉燕 來源: 程序新視界
相關推薦

2010-04-21 12:57:33

RAC負載均衡配置

2021-04-30 08:19:32

SpringCloud客戶端負載Ribbo

2023-10-30 11:28:33

Kubernetes負載均衡

2011-08-17 10:10:59

2019-06-19 14:58:38

服務器負載均衡客戶端

2019-09-10 09:58:19

Dubbo負載均衡Hash

2010-12-21 11:03:15

獲取客戶端證書

2021-07-16 06:56:50

Nacos注冊源碼

2011-12-15 11:03:21

JavaNIO

2019-10-29 05:34:34

IPJava服務器

2021-06-22 15:06:13

Redis客戶端 Redis-clie

2023-11-15 13:50:07

服務端IP

2018-12-19 10:31:32

客戶端IP服務器

2025-04-15 10:00:00

Feign負載均衡微服務

2021-09-22 15:46:29

虛擬桌面瘦客戶端胖客戶端

2010-05-10 17:52:30

實現負載均衡

2021-08-06 06:51:14

NacosRibbon服務

2013-03-13 10:51:44

瘦客戶端VDI

2010-12-31 14:23:57

Exchange Se

2012-04-23 09:51:09

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品视频在线播放 | 91www在线观看 | 国产亚洲一区二区三区 | 久久国产精品视频 | 在线一区 | 国产色在线 | 成人免费视频在线观看 | 中文字幕亚洲欧美日韩在线不卡 | 久久精品视频99 | 欧美综合视频在线 | 久久一久久 | 国产婷婷精品av在线 | 黄色高清视频 | 欧美国产精品一区二区三区 | 久久国产精品免费视频 | 精品亚洲第一 | 天天干视频 | 人人操日日干 | 日韩一级精品视频在线观看 | 亚洲天堂一区 | 色婷婷精品国产一区二区三区 | 色综合99 | 成人日b视频| 在线一区二区三区 | 天堂中文字幕av | 欧美99| 操久久久| 在线婷婷| 国产精品久久久久久福利一牛影视 | 亚洲视频免费观看 | 天天舔天天 | 中文字幕在线第一页 | 久久草在线视频 | 国内av在线 | 毛片99| 国产四区| 亚洲视频一区在线 | 国产日韩欧美一区二区 | 欧美影院久久 | 国产h视频| 天堂精品|