当前位置: 首页 > 后端技术 > PHP

负载均衡算法——SmoothWeightedRoundRobin

时间:2023-03-29 22:49:07 PHP

最早由范浩博科学院发表在负载均衡算法——RoundRobin一文中,我们指出了加权轮循算法的一个明显缺陷。即在某些特殊权重下,加权轮询调度会产生不均匀的实例序列。这种不均匀的负载可能会在某些情况下导致瞬时高负载,从而导致系统停机的风险。为了解决这种调度缺陷,提出了一种平滑的加权循环调度算法。Openproblem为说明smoothweightedround-robin调度的平滑性,使用以下3个特殊的加权实例演示调度过程。服务实例权重值192.168.10.1:22025192.168.10.2:22021192.168.10.3:22021我们已经知道,通过加权轮询算法调度后,会产生如下不均匀的调度序列。Requesttheselectedinstance1192.168.10.1:22022192.168.10.1:22023192.168.10.1:22024192.168.10.1:22025192.168.10.1:22026192.168.10.2:22027192.168.10.1:22025192.168.10.1:22026192.168.10.2:22027192.168.10.1:Whatabouttheresultingsequenceof实例?算法描述假设有N个实例S={S1,S2,…,Sn},配置权重W={W1,W2,…,Wn},有效权重CW={CW1,CW2,…,CWn}。每个实例i除了一个配置权重Wi,还有一个当前有效权重CWi,CWi初始化为Wi;指标变量currentPos表示当前选中的实例ID,初始化为-1;所有实例的配置权重之和为weightSum;那么,调度算法可以描述为:1.初始时,每个实例i的当前有效权重CWi为配置权重Wi,得到配置权重和weightSum;2.选取当前有效权重最大的实例,将当前有效权重CWi减去所有实例的权重和weightSum,变量currentPos指向这个位置;3、将配置权重Wi加到每个实例i的当前有效权重CWi上;4、此时变量currentPos指向的实例就是要调度的实例;5、每次调度重复上述2、3、4步;以上3个服务,配置weight和weightSum为7,调度过程如下:请求选择前的当前权重currentPos选择后选择实例的当前权重1{5,1,1}0192.168.10.1:2202{-2,1,1}2{3,2,2}0192.168.10.1:2202{-4,2,2}3{1,3,3}1192.168.10.2:2202{1,-4,3}4{6,-3,4}0192.168.10.1:2202{-1,-3,4}5{4,-2,5}2192.168.10.3:2202{4,-2,-2}6{9,-1,-1}0192.168.10.1:2202{2,-1,-1}7{7,0,0}0192.168.10.1:2202{0,0,0}8{5,1,1}0192.168.10.1:2202{-2,1,1}可以看出,上述调度顺序的分散非常均匀,当前有效权重值在第8次调度时返回到{0,0,0},则实例状态与初始状态一致,以后可以重复调度操作。这种round-robin调度算法思想最早是由Nginx开发者提出的,参见phusion/nginx部分。这里的代码是使用PHP实现的,源码见fan-haobai/load-balance部分。类SmoothWeightedRobin实现RobinInterface{private$services=array();私人$总计;私人$currentPos=-1;publicfunctioninit(array$services){foreach($servicesas$ip=>$weight){$this->services[]=['ip'=>$ip,'weight'=>$weight,'current_weight'=>$重量,];$this->total=count($this->services);}publicfunctionnext(){//获取当前最大有效权重实例的位置$this->currentPos=$this->getMaxCurrentWeightPos();//当前重量减去重量总和$currentWeight=$this->getCurrentWeight($this->currentPos)-$this->getSumWeight();$this->setCurrentWeight($this->currentPos,$currentWeight);//每个实例当前的有效权重加上配置的权重$this->recoverCurrentWeight();返回$this->services[$this->currentPos]['ip'];其中,getSumWeight()为所有实例的配置权重总和;获取当前Weight()和setCurrentWeight()分别用于获取和设置指定实例的当前有效权重;getMaxCurrentWeightPos()获取当前有效权重最大的实例位置,实现如下:publicfunctiongetMaxCurrentWeightPos(){$currentWeight=$pos=0;foreach($this->servicesas$index=>$service){if($service['current_weight']>$currentWeight){$currentWeight=$service['current_weight'];$pos=$索引;}}return$pos;}recoverCurrentWeight()用于调整每个实例当前的有效权重,即加上配置权重,如下:publicfunctionrecoverCurrentWeight(){foreach($this->servicesas$index=>&$service){$service['current_weight']+=$service['weight'];}}需要注意的是,在配置services服务列表时,还需要指定其权重:$services=['192.168.10.1:2202'=>5,'192.168.10.2:2202'=>1,'192.168.10.3:2202'=>1,];数学证明遗憾的是,关于这个调度算法的严谨的数学证明很少,但是网友tenfy给出的安大神的证明过程非常值得参考和学习证明权重的合理性。如果有n个节点,请记住第i个节点的权重为$x_i$,总权重为$S=x_1+x_2+...+x_n$。选择分为两个步骤:1.将其权重值添加到每个节点;2.选择最大的节点减去总权重值;n个节点的初始化值为[0,0,...,0],数组的长度为n,取值全为0。执行完第一轮选择的step1后,取值该数组是$[x_1,x_2,…,x_n]$。假设经过第1步,最大节点为j,则从第j个节点减去S。所以第2步的数组是$[x_1,x_2,…,x_j-S,…,x_n]$。步骤2执行后,数组之和为:$x_1+x_2+...+x_j-S+...+x_n=>x_1+x_2+...+x_n-S=S-S=0$可以看出,每一轮第一步选择的操作是数组的和加上S,然后第二步的和从S中减去,所以每一轮选择后的数组的和为0.假设一共进行了S轮选择,第i个节点被选择了$m_i$次。当前第i个节点的权重为$w_i$。假设节点j在第t轮之前已经被选中了$x_j$次(t0$,并且永远不会再被选中节点j。可以得出第i个节点最多选择$x_i$,即$m_i<=x_i$。因为$S=m_1+m_2+…+m_n$和$S=x_1+x_2+…+x_n$。因此,可以得出$m_i==x_i$。证明平滑性证明平滑性,只要证明你不总是连续的选择那个节点即可。如上,假设总权重为S,如果一个节点i连续被选中t次($t(x_i-1)\*x_i-(x_i-1)\*S+x_i=>$$x_i^2-x_i\*S+S=>(x_i-1)\*(x_i-S)+x_i$因为$x_i$总是小于S,所以$x_i-S<=-1$。所以上面:$(x_i-1)\*(x_i-S)+x_i<=(x_i-1)\*-1+x_i=-x_i+1+x_i=1$所以在第t轮之后,执行值第1步$w_i+x_i<=1$。如果这t轮恰好是第t轮,那么肯定还有另外一个节点j的值为$x_j\*t$,所以$w_i+x_i<=1<1\*t