今天在看《大型网站技术架构》时,里面介绍负载均衡算法时提到
加权轮询(Weighted Round Robin)
, 对其具体实现不太熟悉,网上的介绍有点乱,不太好理解,所以这里自己实现一下这个算法,以自己能通俗理解的方式记录一下
一. 加权轮询在nginx中的部分配置
http {
upstream cluster {
server 192.168.1.2 weight=5;
server 192.168.1.3 weight=3;
server 192.168.1.4 weight=1;
}
...
location / {
proxy_set_header X-Real-IP $remote_addr; //返回真实IP
proxy_pass http://cluster; //代理指向cluster
}
二. 加权轮询算法介绍
- 由于不同服务器的配置、部署的应用不同,其处理能力会不一样。所以,加权轮询算法的原理就是:根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。
- 在
Nginx加权轮询算法
中,每个节点有三个权重变量
-
weight
: 配置的权重,即在配置文件或初始化时约定好的每个节点的权重 -
currentWeight
: 节点当前权重,会一直变化 -
effectiveWeight
:有效权重,初始值为weight
, 通讯过程中发现节点异常,则-1
,之后再次选取本节点,调用成功一次则+1
,直达恢复到weight
。 用于健康检查,处理异常节点,降低其权重。
- 算法逻辑
(1) 轮询所有节点,计算当前状态下所有节点的effectiveWeight
之和totalWeight
;
(2)currentWeight = currentWeight + effectiveWeight
; 选出所有节点中currentWeight
中最大的一个节点作为选中节点;
(3) 选中节点的currentWeight = currentWeight - totalWeight
;
注:(为了简单清晰,后面的实现不考虑健康检查effectiveWeight
这个功能实现,假设所有节点都是100%可用
,所以上面的逻辑要把使用effectiveWeight
的地方换成weight
- 例子
假设有三个节点{serverA , serverB , serverC}
, 权重为分别为{4,3,2}
,现在请求7次
的分配情况如下:
请求次数 | 请求前currentWeight | 选中的节点 | 请求后currentWeight |
---|---|---|---|
1 | [ serverA= 4,serverB= 3,serverC= 2 ] | serverA | [ serverA= -1,serverB= 6,serverC= 4 ] |
2 | [ serverA= -1,serverB= 6,serverC= 4 ] | serverB | [ serverA= 3,serverB= 0,serverC= 6 ] |
3 | [ serverA= 3,serverB= 0,serverC= 6 ] | serverC | [ serverA= 7,serverB= 3,serverC= -1 ] |
4 | [ serverA= 7,serverB=3,serverC= -1 ] | serverA | [ serverA= 2,serverB= 6,serverC= 1 ] |
5 | [ serverA= 2,serverB= 6,serverC= 1 ] | serverB | [ serverA= 6,serverB= 0,serverC= 3 ] |
6 | [ serverA= 6,serverB= 0,serverC= 3 ] | serverA | [ serverA= 1,serverB= 3,serverC= 5 ] |
7 | [ serverA= 1,serverB= 3,serverC= 5 ] | serverC | [ serverA= 5,serverB= 6,serverC= -2 ] |
totaoWeight = 4 + 3 + 2 = 9
,
第一次请求serverA= 4 + 4(原始比重) = 8
,serverB= 3+3(原始比重) = 6
,serverC= 2+2(原始比重) = 4
, 最大的是serverA
, 所以serverA= 8 - 9 (权重总和) = -1
,所以最后值为serverA= -1,serverB= 6,serverC= 4
第二次请求serverA= -1 + 4(原始比重) = 3
,serverB= 6+3(原始比重) = 9
,serverC= 4+2(原始比重) = 6
, 最大的是serverB
, 所以serverB= 9 - 9 (权重总和) = 0
,所以最后值为serverA= 3,serverB= 0,serverC= 6
依此类推...
三. 代码实现(java)
-
DemoApplication
main方法
package com.example.demo;
public class DemoApplication {
public static void main(String[] args) {
/**
* 假设有三个服务器权重配置如下:
* server A weight = 4 ;
* server B weight = 3 ;
* server C weight = 2 ;
*/
Node serverA = new Node("serverA", 4);
Node serverB = new Node("serverB", 3);
Node serverC = new Node("serverC", 2);
SmoothWeightedRoundRobin smoothWeightedRoundRobin = new SmoothWeightedRoundRobin(serverA,serverB ,serverC);
for (int i = 0; i < 7; i++) {
Node i1 = smoothWeightedRoundRobin.select();
System.out.println(i1.getServerName());
}
}
}
-
SmoothWeightedRoundRobin
加权轮询算法
package com.example.demo;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
public class SmoothWeightedRoundRobin {
private volatile List<Node> nodeList = new ArrayList<>() ; // 保存权重
private ReentrantLock lock = new ReentrantLock() ;
public SmoothWeightedRoundRobin(Node ...nodes) {
for (Node node : nodes) {
nodeList.add(node) ;
}
}
public Node select(){
try {
lock.lock();
return this.selectInner() ;
}finally {
lock.unlock();
}
}
private Node selectInner(){
int totalWeight = 0 ;
Node maxNode = null ;
int maxWeight = 0 ;
for (int i = 0; i < nodeList.size(); i++) {
Node n = nodeList.get(i);
totalWeight += n.getWeight() ;
// 每个节点的当前权重要加上原始的权重
n.setCurrentWeight(n.getCurrentWeight() + n.getWeight());
// 保存当前权重最大的节点
if (maxNode == null || maxWeight < n.getCurrentWeight() ) {
maxNode = n ;
maxWeight = n.getCurrentWeight() ;
}
}
// 被选中的节点权重减掉总权重
maxNode.setCurrentWeight(maxNode.getCurrentWeight() - totalWeight);
// nodeList.forEach(node -> System.out.print(node.getCurrentWeight()));
return maxNode ;
}
}
-
Node
服务节点
package com.example.demo;
public class Node {
private final int weight ; // 初始权重 (保持不变)
private final String serverName ; // 服务名
private int currentWeight ; // 当前权重
public Node( String serverName, int weight) {
this.weight = weight;
this.serverName = serverName ;
this.currentWeight = weight ;
}
public int getCurrentWeight() {
return currentWeight;
}
public int getWeight() {
return weight;
}
public void setCurrentWeight(int currentWeight) {
this.currentWeight = currentWeight;
}
public String getServerName() {
return serverName;
}
}
这个算法相对保证了的平滑性和均匀性
网友评论