自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Nacos客戶端是如何實(shí)現(xiàn)實(shí)例獲取的負(fù)載均衡呢?

開發(fā) 前端
本篇文章追蹤Nacos客戶端源碼,分析了從實(shí)例列表中獲得其中一個(gè)實(shí)例的算法,也就是隨機(jī)權(quán)重負(fù)載均衡算法。整體業(yè)務(wù)邏輯比較簡(jiǎn)單,從ServiceInfo中獲得實(shí)例列表,一路篩選,選中目標(biāo)實(shí)例,然后根據(jù)它們的權(quán)重進(jìn)行二次處理,數(shù)據(jù)結(jié)構(gòu)封裝,最后基于Arrays#binarySearch提供的二分查找法來(lái)獲得對(duì)應(yīng)的實(shí)例。

[[418899]]

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

這篇文章,就帶大家從源碼層面分析一下,Nacos客戶端采用了如何的算法來(lái)從實(shí)例列表中獲取一個(gè)實(shí)例進(jìn)行請(qǐng)求的。也可以稱作是Nacos客戶端的負(fù)載均衡算法。

單個(gè)實(shí)例獲取

NamingService不僅提供了獲取實(shí)例列表的方法,也提供了獲取單個(gè)實(shí)例的方法,比如:

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

該方法會(huì)根據(jù)預(yù)定義的負(fù)載算法,從實(shí)例列表中獲得一個(gè)健康的實(shí)例。其他重載的方法功能類似,最終都會(huì)調(diào)用該方法,我們就以此方法為例來(lái)分析一下具體的算法。

具體實(shí)現(xiàn)代碼:

  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.         // 通過(guò)負(fù)載均衡算法獲得其中一個(gè)實(shí)例 
  12.         return Balancer.RandomByWeight.selectHost(serviceInfo); 
  13.     } else { 
  14.         // 獲取ServiceInfo 
  15.         ServiceInfo serviceInfo = clientProxy 
  16.                 .queryInstancesOfService(serviceName, groupName, clusterString, 0, false); 
  17.         // 通過(guò)負(fù)載均衡算法獲得其中一個(gè)實(shí)例 
  18.         return Balancer.RandomByWeight.selectHost(serviceInfo); 
  19.     } 

selectOneHealthyInstance方法邏輯很簡(jiǎn)單,調(diào)用我們之前講到的方法獲取ServiceInfo對(duì)象,然后作為參數(shù)傳遞給負(fù)載均衡算法,由負(fù)載均衡算法計(jì)算出最終使用哪個(gè)實(shí)例(Instance)。

算法參數(shù)封裝

先跟蹤一下代碼實(shí)現(xiàn),非核心業(yè)務(wù)邏輯,只簡(jiǎn)單提一下。

上面的代碼可以看出調(diào)用的是Balancer內(nèi)部類RandomByWeight的selectHost方法:

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

selectHost方法核心邏輯是從ServiceInfo中獲取實(shí)例列表,然后調(diào)用getHostByRandomWeight方法:

  1. protected static Instance getHostByRandomWeight(List<Instance> hosts) { 
  2.     // ... 判斷邏輯 
  3.     // 重新組織數(shù)據(jù)格式 
  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.     // 通過(guò)Chooser來(lái)實(shí)現(xiàn)隨機(jī)權(quán)重負(fù)載均衡算法 
  11.     Chooser<String, Instance> vipChooser = new Chooser<String, Instance>("www.taobao.com"); 
  12.     vipChooser.refresh(hostsWithWeight); 
  13.     return vipChooser.randomWithWeight(); 

getHostByRandomWeight前半部分是將Instance列表及其中的權(quán)重?cái)?shù)據(jù)進(jìn)行轉(zhuǎn)換,封裝成一個(gè)Pair,也就是建立成對(duì)的關(guān)系。在此過(guò)程中只使用了健康的節(jié)點(diǎn)。

真正的算法實(shí)現(xiàn)則是通過(guò)Chooser類來(lái)實(shí)現(xiàn)的,看名字基本上知道實(shí)現(xiàn)的策略是基于權(quán)重的隨機(jī)算法。

負(fù)載均衡算法實(shí)現(xiàn)

所有的負(fù)載均衡算法實(shí)現(xiàn)均位于Chooser類中,Chooser類的提供了兩個(gè)方法refresh和randomWithWeight。

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

randomWithWeight方法基于前面的數(shù)據(jù)來(lái)進(jìn)行隨機(jī)算法處理。

先看refresh方法:

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

基本步驟:

  • 創(chuàng)建Ref類,該類為Chooser的內(nèi)部類;
  • 調(diào)用Ref的refresh方法,用于準(zhǔn)備數(shù)據(jù)、檢查數(shù)據(jù)等;
  • 數(shù)據(jù)篩選完成,調(diào)用poller#refresh方法,本質(zhì)上就是創(chuàng)建一個(gè)GenericPoller對(duì)象;
  • 成員變量重新賦值;

這里重點(diǎn)看Ref#refresh方法:

  1. /** 
  2.  * 獲取參與計(jì)算的實(shí)例列表、計(jì)算遞增數(shù)組數(shù)總和并進(jìn)行檢查 
  3.  */ 
  4. public void refresh() { 
  5.     // 實(shí)例權(quán)重總和 
  6.     Double originWeightSum = (double) 0; 
  7.      
  8.     // 所有健康權(quán)重求和 
  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.         // 權(quán)重小于等于0則不參與計(jì)算 
  14.         if (weight <= 0) { 
  15.             continue
  16.         } 
  17.         // 有效實(shí)例放入列表 
  18.         items.add(item.item()); 
  19.         // 如果值無(wú)限大 
  20.         if (Double.isInfinite(weight)) { 
  21.             weight = 10000.0D; 
  22.         } 
  23.         // 如果值為非數(shù)字 
  24.         if (Double.isNaN(weight)) { 
  25.             weight = 1.0D; 
  26.         } 
  27.         // 權(quán)重值累加 
  28.         originWeightSum += weight; 
  29.     } 
  30.      
  31.     double[] exactWeights = new double[items.size()]; 
  32.     int index = 0; 
  33.     // 計(jì)算每個(gè)節(jié)點(diǎn)權(quán)重占比,放入數(shù)組 
  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.         // 計(jì)算每個(gè)節(jié)點(diǎn)權(quán)重占比 
  41.         exactWeights[index++] = singleWeight / originWeightSum; 
  42.     } 
  43.      
  44.     // 初始化遞增數(shù)組 
  45.     weights = new double[items.size()]; 
  46.     double randomRange = 0D; 
  47.     for (int i = 0; i < index; i++) { 
  48.         // 遞增數(shù)組第i項(xiàng)值為items前i個(gè)值總和 
  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."); 

可結(jié)合上面代碼中的注釋來(lái)理解,核心步驟包括以下:

  • 遍歷itemsWithWeight,計(jì)算權(quán)重總和數(shù)據(jù);非健康節(jié)點(diǎn)會(huì)被剔除掉;
  • 計(jì)算每個(gè)節(jié)點(diǎn)的權(quán)重值在總權(quán)重值中的占比,并存儲(chǔ)在exactWeights數(shù)組當(dāng)中;
  • 將exactWeights數(shù)組當(dāng)中值進(jìn)行數(shù)據(jù)重構(gòu),形成一個(gè)遞增數(shù)組weights(每個(gè)值都是exactWeights坐標(biāo)值的總和),后面用于隨機(jī)算法;
  • 判斷是否循環(huán)完成或誤差在指定范圍內(nèi)(0.0001),符合則返回。

所有數(shù)據(jù)準(zhǔn)備完成,調(diào)用隨機(jī)算法方法randomWithWeight:

  1. public T randomWithWeight() { 
  2.     Ref<T> ref = this.ref; 
  3.     // 生成0-1之間的隨機(jī)數(shù) 
  4.     double random = ThreadLocalRandom.current().nextDouble(0, 1); 
  5.     // 采用二分法查找數(shù)組中指定值,如果不存在則返回(-(插入點(diǎn)) - 1),插入點(diǎn)即隨機(jī)數(shù)將要插入數(shù)組的位置,即第一個(gè)大于此鍵的元素索引。 
  6.     int index = Arrays.binarySearch(ref.weights, random); 
  7.     // 如果沒(méi)有查詢到(返回-1或"-插入點(diǎn)") 
  8.     if (index < 0) { 
  9.         index = -index - 1; 
  10.     } else { 
  11.         // 命中直接返回結(jié)果 
  12.         return ref.items.get(index); 
  13.     } 
  14.      
  15.     // 判斷坐標(biāo)未越界 
  16.     if (index < ref.weights.length) { 
  17.         // 隨機(jī)數(shù)小于指定坐標(biāo)的數(shù)值,則返回坐標(biāo)值 
  18.         if (random < ref.weights[index]) { 
  19.             return ref.items.get(index); 
  20.         } 
  21.     } 
  22.      
  23.     // 此種情況不應(yīng)該發(fā)生,但如果發(fā)生則返回最后一個(gè)位置的值 
  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); 

該方法的基本操作如下:

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

至此,關(guān)于Nacos客戶端實(shí)例獲取的負(fù)載均衡算法代碼層面追蹤完畢。

算法實(shí)例演示

下面用一個(gè)實(shí)例來(lái)演示一下,該算法中涉及的數(shù)據(jù)變化。為了數(shù)據(jù)美觀,這里采用4組數(shù)據(jù),每組數(shù)據(jù)進(jìn)來(lái)確保能被整除;

節(jié)點(diǎn)及權(quán)重?cái)?shù)據(jù)(前面節(jié)點(diǎn),后面權(quán)重)如下:

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

第一步,計(jì)算權(quán)重綜合:

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

第二步,計(jì)算每個(gè)節(jié)點(diǎn)權(quán)重比:

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

第三步,計(jì)算遞增數(shù)組weights:

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

第四步,生成0-1的隨機(jī)數(shù):

  1. random = 0.3049980013493817 

第五步,調(diào)用Arrays#binarySearch從weights中搜索random:

  1. index = -2 

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

插入點(diǎn)即第一個(gè)大于此key的元素索引,那么上面第一個(gè)大于0.3049980013493817的值為0.3125,那么插入點(diǎn)值為1;

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

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

第六步,也就是沒(méi)有恰好命中的情況:

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

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

算法的核心

上面演示了算法,但這個(gè)算法真的能夠做到按權(quán)重負(fù)載嗎?我們來(lái)分析一下這個(gè)問(wèn)題。

這個(gè)問(wèn)題的重點(diǎn)不在random值,這個(gè)值基本上是隨機(jī)的,那么怎么保證權(quán)重大的節(jié)點(diǎn)獲得的機(jī)會(huì)更多呢?

這里先把遞增數(shù)組weights用另外一個(gè)形式來(lái)表示:

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

如果把weights理解成一條線,對(duì)應(yīng)節(jié)點(diǎn)的值是線上的一個(gè)個(gè)點(diǎn),體現(xiàn)在圖中便是(圖2到圖5)有色(灰色+橘黃色)部分。

而Arrays#binarySearch算法的插入點(diǎn)獲取的是第一個(gè)大于key(也就是random)的坐標(biāo),也就是說(shuō)每個(gè)節(jié)點(diǎn)享有的隨機(jī)范圍不同,它們的范圍由當(dāng)前點(diǎn)和前一個(gè)點(diǎn)的區(qū)間決定,而這個(gè)區(qū)間正好是權(quán)重比值。

權(quán)重比值大的節(jié)點(diǎn),占有的區(qū)間就比較多,比如節(jié)點(diǎn)1占了1/4,節(jié)點(diǎn)4占了1/2。這樣,如果隨機(jī)數(shù)是均勻分布的,那么占有范圍比較大的節(jié)點(diǎn)更容易獲得青睞。也就達(dá)到了按照權(quán)重獲得被調(diào)用的機(jī)會(huì)了。

小結(jié)

本篇文章追蹤Nacos客戶端源碼,分析了從實(shí)例列表中獲得其中一個(gè)實(shí)例的算法,也就是隨機(jī)權(quán)重負(fù)載均衡算法。整體業(yè)務(wù)邏輯比較簡(jiǎn)單,從ServiceInfo中獲得實(shí)例列表,一路篩選,選中目標(biāo)實(shí)例,然后根據(jù)它們的權(quán)重進(jìn)行二次處理,數(shù)據(jù)結(jié)構(gòu)封裝,最后基于Arrays#binarySearch提供的二分查找法來(lái)獲得對(duì)應(yīng)的實(shí)例。

 

而我們需要注意和學(xué)習(xí)的重點(diǎn)便是權(quán)重獲取算法的思想及具體實(shí)現(xiàn),最終達(dá)到能夠在實(shí)踐中進(jìn)行運(yùn)用。

 

責(zé)任編輯:武曉燕 來(lái)源: 程序新視界
相關(guān)推薦

2010-04-21 12:57:33

RAC負(fù)載均衡配置

2021-04-30 08:19:32

SpringCloud客戶端負(fù)載Ribbo

2023-10-30 11:28:33

Kubernetes負(fù)載均衡

2011-08-17 10:10:59

2019-06-19 14:58:38

服務(wù)器負(fù)載均衡客戶端

2019-09-10 09:58:19

Dubbo負(fù)載均衡Hash

2010-12-21 11:03:15

獲取客戶端證書

2021-07-16 06:56:50

Nacos注冊(cè)源碼

2011-12-15 11:03:21

JavaNIO

2019-10-29 05:34:34

IPJava服務(wù)器

2021-06-22 15:06:13

Redis客戶端 Redis-clie

2023-11-15 13:50:07

服務(wù)端IP

2018-12-19 10:31:32

客戶端IP服務(wù)器

2025-04-15 10:00:00

Feign負(fù)載均衡微服務(wù)

2021-09-22 15:46:29

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

2010-05-10 17:52:30

實(shí)現(xiàn)負(fù)載均衡

2013-03-13 10:51:44

瘦客戶端VDI

2021-08-06 06:51:14

NacosRibbon服務(wù)

2010-12-31 14:23:57

Exchange Se

2010-04-20 16:34:31

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)