带权轮询负载均衡业务场景
负载均衡是在请求资源时,当资源有多个,我们应该请求哪一个才能让资源利用率最大化的方法。
轮询是最简单粗暴的一种策略,这种策略把每个资源都请求一遍,最大的好处是每个资源承受的请求量都一样。
而带权轮询负载均衡也是最常用的其中一种策略,它的使用场景是当多个资源可以承受的请求量不同(也就是系统的性能有差异)时,我们不应该像简单轮询一样让它们承担相同量的请求,而是针对每个资源不同的承载量去请求它们,这样才可以让每个资源被充分的利用。
接下来介绍三种实现带权轮询的方法。
取随机值法
这种方法是最不靠谱的方法🤣,但也是及其容易实现的方法,也就是取随机值,把随机值分为几个区间,每种资源对应一个区间,区间长度就表示资源对应的权值。
代码实现
public class RandomWeightLoadBalance {
// key:服务id value:权值
private final static Map<Integer, Integer> services = new ConcurrentHashMap<>();
static {
// 初始化三个服务
services.put(1,1);
services.put(2,2);
services.put(3,3);
}
public static int loadBalance(){
// 计算总权值
int countWeight = 0;
for(Map.Entry<Integer,Integer> service : services.entrySet()){
countWeight += service.getValue();
}
// 取随机值
double random = Math.random() * countWeight;
// 分区间匹配
int preWeight = 0;
int currentWeight = 0;
for(Map.Entry<Integer,Integer> service : services.entrySet()){
currentWeight += service.getValue();
if(preWeight < random && random < currentWeight){
return service.getKey();
}
preWeight = currentWeight;
}
// 返回默认值
return services.entrySet().iterator().next().getKey();
}
public static void main(String[] args) {
int[] counts = new int[4];
for(int i=0;i<100;i++){
counts[loadBalance()]++;
}
System.out.println(Arrays.toString(counts));
}
}
由于随机值无法预测,所以只能从概率上实现带权值轮询,所以这种方式显然不太靠谱,但是次数多了,随机值分布也就更均匀了。
但是这种方式的还有一个缺点就是,有可能大量的请求会集中请求同一个资源,这时随机值的不可预测性导致的。所以我们通常不会用这种方式(也就是拿出来做一下对比😂)
普通带权值轮询
普通带权轮询就比随机值的要好多了,它可以精确的保证每个资源承受的请求达到预期。
代码实现
public class WeightLoadBalance {
private static final Map<Integer, Integer> services = new ConcurrentHashMap<>();
private static int totalWeight;
static {
services.put(1, 1);
services.put(2, 3);
services.put(3, 2);
for(Map.Entry<Integer, Integer> service : services.entrySet()){
totalWeight += service.getValue();
}
}
private static int currentIndex = 0;
public static int loadBalance(){
currentIndex %= totalWeight;
int currentWeight = 0;
for(Map.Entry<Integer, Integer> service : services.entrySet()){
currentWeight += service.getValue();
if(currentIndex < currentWeight){
currentIndex ++;
return service.getKey();
}
}
return services.entrySet().iterator().next().getValue();
}
public static void main(String[] args) {
int[] count = new int[4];
for(int i=0;i<100;i++){
count[loadBalance()]++;
}
System.out.println(Arrays.toString(count));
}
}
但是普通带权值轮询会有连续请求同一个资源的情况,例如:
A(权值5)、B(权值3)、C(权值2),就会出现:
A-A-A-A-A-B-B-B-C-C
连续请求了五次的A资源,请求较集中。
而平滑带权值轮询就解决了这个问题。
平滑带权值轮询
接上,由于普通带权轮询负载均衡导致的连续请求同一个服务,导致请求不能分散。而使用平滑轮询后,请求的顺序变为了这样:
A-A-B-C-A-A-B-C-A-B
这样就将请求同一资源的请求分散开了,避免大量相同的请求堆集在一起请求同一个。
实现步骤
- 每个资源有一个权值和当前权值,当前权值初始化为0
- .每次请求时每个资源的当前权值变为当前权值和权值的和(currentWeight += weight)
- 选择权值最大的一个资源,然后将此资源的当前权值减掉总权值(currentWeight -= total Weight(totalWeight表示原始权制的和))
代码实现
public class SmoothWeightLoadBalance {
private static final Map<Integer, Integer> services = new ConcurrentHashMap<>();
private static int totalWeight;
private static final Map<Integer, Integer> currentWeights = new ConcurrentHashMap<>();
static {
services.put(1, 5);
services.put(2, 3);
services.put(3, 2);
for(Map.Entry<Integer, Integer> service : services.entrySet()){
totalWeight += service.getValue();
}
currentWeights.put(1, 0);
currentWeights.put(2, 0);
currentWeights.put(3, 0);
}
public static int loadBalance() {
int serviceId = services.entrySet().iterator().next().getKey();
int maxWeight = 0;
// 找出权值最大资源
for(Map.Entry<Integer,Integer> service : services.entrySet()) {
// 计算出现有权值并更新
int currentWeight = currentWeights.get(service.getKey()) + service.getValue();
currentWeights.put(service.getKey(), currentWeight);
// 最大权值
if(currentWeight > maxWeight){
serviceId = service.getKey();
maxWeight = currentWeight;
}
}
// 更新最大权值
currentWeights.put(serviceId, currentWeights.get(serviceId) - totalWeight);
return serviceId;
}
public static void main(String[] args) {
int[] count = new int[4];
for(int i=0;i<1000;i++){
count[loadBalance()]++;
}
System.out.println(Arrays.toString(count));
}
}
comments powered by Disqus