1 三组概念
负载均衡、集群容错、服务降级这三个概念在DUBBO中非常重要,同理其它分布式框架也都有相同或者相近之概念。
从调用顺序角度分析,调用顺序依次是负载均衡、集群容错、服务降级。从解决问题角度分析,负载均衡解决了「选哪一个」问题,集群容错解决了「换哪一个」问题,服务降级解决了「全错怎么办」问题。
假设有1个服务消费者面对10个提供者,这时面临第一个问题就是「选哪一个」进行调用,所以负载均衡最先调用,假设选定了5号服务提供者进行服务调用。
假设消费者调用5号提供者发生了超时异常,这时面临第二个问题就是「换哪一个」进行调用:5号超时要不要换1号试一试,或者直接返回不进行重试,所以集群容错第二个调用。
假设已经重试了1号、3号、6号提供者全部超时,这时面临「全错怎么办」这第三个问题,这时可以直接返回一个固定值或者提示文案,所以服务降级第三个调用。
负载均衡作为整个调用链路第一个节点非常重要,本文结合DUBBO源码分析以下七种负载均衡策略:
- 简单随机
- 加权随机
- 简单轮询
- 简单加权轮询
- 平滑加权轮询
- 一致性哈希
- 最少活跃数
2 简单随机
简单随机含义是服务消费者每次会任意访问一个服务提供者,并且从概率角度看每个提供者被访问概率一致,可以通过指定范围随机数实现。第一步编写服务器代码
public class MyServer {
private String ip;
public MyServer(String ip) {
this.ip = ip;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
}
复制代码
第二步编写基础负载均衡策略,其它策略可以复用
public abstract class AbstractLoadBalance {
public MyServer select(List<MyServer> serverList) {
return doSelect(serverList);
}
public abstract MyServer doSelect(List<MyServer> serverList);
}
复制代码
第三步编写简单随机策略
public class RandomBalance extends AbstractLoadBalance {
@Override
public MyServer doSelect(List<MyServer> serverList) {
// 随机数范围[0,serverListSize)
int index = ThreadLocalRandom.current().nextInt(serverList.size());
return serverList.get(index);
}
}
复制代码
第四步编写测试代码
public class LoadBalanceTest {
public static void main(String[] args) {
List<MyServer> serverList = buildData();
testRandomBalance(serverList);
}
public static void testRandomBalance(List<MyServer> serverList) {
AbstractLoadBalance randomBalance = new RandomBalance();
for (int i = 0; i < 10; i++) {
MyServer server = randomBalance.select(serverList);
System.out.println("RandomBalance route server=" + server);
}
}
public static List<MyServer> buildData() {
List<MyServer> serverList = new ArrayList<MyServer>();
MyServer server1 = new MyServer("192.1.1.1");
MyServer server2 = new MyServer("192.1.1.2");
MyServer server3 = new MyServer("192.1.1.3");
serverList.add(server1);
serverList.add(server2);
serverList.add(server3);
return serverList;
}
}
复制代码
第五步输出结果,循环次数越多结果越准确
RandomBalance route server=MyServer(ip=192.1.1.2)
RandomBalance route server=MyServer(ip=192.1.1.1)
RandomBalance route server=MyServer(ip=192.1.1.3)
RandomBalance route server=MyServer(ip=192.1.1.2)
RandomBalance route server=MyServer(ip=192.1.1.1)
RandomBalance route server=MyServer(ip=192.1.1.1)
RandomBalance route server=MyServer(ip=192.1.1.2)
RandomBalance route server=MyServer(ip=192.1.1.2)
RandomBalance route server=MyServer(ip=192.1.1.3)
RandomBalance route server=MyServer(ip=192.1.1.3)
复制代码
3 加权随机
3.1 设计思路
加权随机新增了权重这个概念,假设服务器A权重等于1,服务器B权重等于5,从概率角度看B服务器被访问概率5倍于A服务器。实现按照权重访问有很多种方式,我们选择使用概率区间这个思路。
假设现在有三台服务器,服务器权重分别是3、5、2,那么三者构成如下图概率区间:
概率区间计算步骤如下图:
3.2 代码实例
第一步编写服务器代码
public class MyServer {
private String ip;
private int weight;
public MyServer(String ip) {
this.ip = ip;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
复制代码
第二步编写加权随机策略
public class RandomWeightBalance extends AbstractLoadBalance {
@Override
public MyServer doSelect(List<MyServer> serverList) {
// 所有服务器总权重
int totalWeight = 0;
// 第一个服务器权重
int firstWeight = serverList.get(0).getWeight();
// 所有服务器权重相等
boolean sameWeight = true;
// 遍历所有服务器
for (MyServer server : serverList) {
// 计算总权重
totalWeight += server.getWeight();
// 任意一个invoker权重不等于第一个权重则设置sameWeight=false
if (sameWeight && server.getWeight() != firstWeight) {
sameWeight = false;
}
}
// 权重不相等则根据权重选择
if (!sameWeight) {
// 在总区间范围[0,totalWeight)生成随机数A
Integer offset = ThreadLocalRandom.current().nextInt(totalWeight);
// 遍历所有服务器区间
for (MyServer server : serverList) {
// 如果A在server区间直接返回
if (offset < server.getWeight()) {
return server;
}
// 如果A不在server区间则减去此区间范围并继续匹配其它区间
offset -= server.getWeight();
}
}
// 所有服务器权重相等则随机选择
return serverList.get(ThreadLocalRandom.current().nextInt(serverList.size()));
}
}
复制代码
第三步编写测试代码
public class LoadBalanceTest {
public static void main(String[] args) {
List<MyServer> serverList = buildData();
testRandomWeightBalance(serverList);
}
public static void testRandomWeightBalance(List<MyServer> serverList) {
AbstractLoadBalance randomBalance = new RandomWeightBalance();
for (int i = 0; i < 10; i++) {
MyServer server = randomBalance.select(serverList);
System.out.println("RandomWeightBalance route server=" + server);
}
}
public static List<MyServer> buildData() {
List<MyServer> serverList = new ArrayList<MyServer>();
MyServer server1 = new MyServer("192.1.1.1", 3);
MyServer server2 = new MyServer("192.1.1.2", 5);
MyServer server3 = new MyServer("192.1.1.3", 2);
serverList.add(server1);
serverList.add(server2);
serverList.add(server3);
return serverList;
}
}
复制代码
第四步输出结果,循环次数越多结果越准确
RandomWeightBalance route server=MyServer(ip=192.1.1.2, weight=2)
RandomWeightBalance route server=MyServer(ip=192.1.1.2, weight=3)
RandomWeightBalance route server=MyServer(ip=192.1.1.1, weight=3)
RandomWeightBalance route server=MyServer(ip=192.1.1.1, weight=3)
RandomWeightBalance route server=MyServer(ip=192.1.1.3, weight=2)
RandomWeightBalance route server=MyServer(ip=192.1.1.3, weight=2)
RandomWeightBalance route server=MyServer(ip=192.1.1.2, weight=5)
RandomWeightBalance route server=MyServer(ip=192.1.1.1, weight=3)
RandomWeightBalance route server=MyServer(ip=192.1.1.2, weight=5)
RandomWeightBalance route server=MyServer(ip=192.1.1.2, weight=5)
复制代码
3.3 DUBBO源码
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// invoker数量
int length = invokers.size();
// 所有权重是否相等
boolean sameWeight = true;
// 权重数组
int[] weights = new int[length];
// 第一个权重
int firstWeight = getWeight(invokers.get(0), invocation);
weights[0] = firstWeight;
// 权重之和
int totalWeight = firstWeight;
// 遍历所有invoker
for (int i = 1; i < length; i++) {
// 获取权重
int weight = getWeig