任何你写的代码,超过6个月不去看它,当你再看时,都像是别人写的。
代码下载地址:https://github.com/f641385712/netflix-learning
关于IRule的实现,还差两个实现规则,一个是随机规则RandomRule
,一个是重试规则RetryRule
,本文将进行收尾,并且给出对所有IRule实现的总结列表。
随机选择一个server。使用ThreadLocalRandom.current().nextInt(serverCount);
随机来一个。
public class RandomRule extends AbstractLoadBalancerRule {
public Server choose(ILoadBalancer lb, Object key) {
...
Server server = null;
while (server == null) {
...
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
int index = chooseRandomInt(serverCount);
server = upList.get(index);
...
}
return server;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
}
说明:它的choose方法代码同
RoundRobinRule
的高度相似,但是RoundRobinRule
计算随机值和get的时候使用的均是allServers
,所以是没有bug的。但是本文是有bug的下,下面会使用示例模拟
这个算法非常的简单,但是,但是,但是,有个小细节你需要特别注意:
allList.size()
,也就是all所有Server的sizeupList.get(index)
来取值这很明显是个bug,下面我们通过代码示例来验证这个bug。
@Test
public void fun2() throws InterruptedException {
List<Server> serverList = new ArrayList<>();
serverList.add(createServer("华南", 1));
serverList.add(createServer("华东", 1));
serverList.add(createServer("华东", 2));
serverList.add(createServer("华北", 1));
serverList.add(createServer("华北", 2));
serverList.add(createServer("华北", 3));
serverList.add(createServer("华北", 4));
// 轮询策略:因为Servers均来自于lb,所以必须关联上一个lb实例哦
ILoadBalancer lb = new BaseLoadBalancer();
lb.addServers(serverList);
RandomRule rule = new RandomRule();
rule.setLoadBalancer(lb);
System.out.println("server总数:"+lb.getAllServers().size());
System.out.println("up的总数:"+lb.getReachableServers().size());
while (true) {
System.out.println(rule.choose(null));
TimeUnit.SECONDS.sleep(2);
}
}
private Server createServer(String zone, int index) {
Server server = new Server("www.baidu" + zone + ".com", index);
server.setAlive(true);
server.setReadyToServe(true);
server.setZone(zone);
return server;
}
运行程序,控制台输出:
server总数:7
up的总数:7
www.baidu华东.com:1
www.baidu华北.com:1
www.baidu华北.com:1
www.baidu华北.com:2
www.baidu华北.com:1
www.baidu华东.com:1
www.baidu华南.com:1
...
完美,结果是完全随机的。这样不会出任何问题,因为本利all = up
(都是7台)。下面模拟all > up的情况:
因为Server是否up是由IPing去决定的,因此只需要提供自定义的IPing规则便可模拟出现这个bug
lb.setPing(server -> server.getPort() % 10 > 2);
再次运行程序,抛错:
server总数:7
up的总数:2
java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at java.util.Collections$UnmodifiableList.get(Collections.java:1309)
at com.netflix.loadbalancer.RandomRule.choose(RandomRule.java:61)
at com.netflix.loadbalancer.RandomRule.choose(RandomRule.java:92)
因为upList
只有2台,而总数是7台,所以如果随机出来的数字加入是3,那get(index)
就抛错啦~
几乎不用。更何况它还有bug呢,更不用喽。由于Ribbon工程目前处于维护状态,且该规则几乎不会使用,因此bug官方也不会修这个bug。万一你偏要使用,请你自行实现而不要使用这个有bug的版本。
它可以在给定的任何IRule
的基础上再包一层重试逻辑(默认给定的RoundRobinRule
规则)。
public class RetryRule extends AbstractLoadBalancerRule {
IRule subRule = new RoundRobinRule();
// 最大重试时长 默认值是半分钟
long maxRetryMillis = 500;
... // 省略给这两个属性赋值的构造器和set方法们
@Override
public void setLoadBalancer(ILoadBalancer lb) {
super.setLoadBalancer(lb);
subRule.setLoadBalancer(lb);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
// 选择方法
public Server choose(ILoadBalancer lb, Object key) {
// 计算出一个开始选择 和 结束选择的时间
long requestTime = System.currentTimeMillis();
long deadline = requestTime + maxRetryMillis;
// 先通过subRule选择出一个server
Server answer = null;
answer = subRule.choose(key);
// 如果选择出的Server为null,或者不是活的
// 并且还在结束时间之前,就执行重试策略
if (((answer == null) || (!answer.isAlive()))
&& (System.currentTimeMillis() < deadline)) {
InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());
while (!Thread.interrupted()) {
... // 持续不断的调用subRule.choose(key),知道获取到或者时间到了
}
task.cancel();
}
// 如果最终还是没获取到可用的,那就返回null。否则返回正常结果
if ((answer == null) || (!answer.isAlive())) {
return null;
} else {
return answer;
}
}
}
RetryRule中又定义了一个subRule,它默认的实现类是RoundRobinRule
(你可以自己指定任何IRule实现),每次先采用subRule#choose()
规则来选择一个服务实例,如果选到的实例正常就返回不需要重试;如果选择的服务实例为null或者已经失效,则在失效时间deadline之前不断的进行重试(重试时获取服务的策略还是subRule#choose()
来选择),如果超过了deadline还是没取到则会返回一个null。
因为它属于包装器模式的一种实现,因此在实际生产中,我个人推荐使用它来包装你实际的IRule,这样会使得实例Server更加的健康,对网络波动的容忍度更高些,如你可以这么做new RetryRule(new BestAvailableRule())
。
RetryRule
配合上RoundRobinRule
的组合(也就是默认组合)效果很好:因为RoundRobinRule失效的策略是超过10次,而如果在配合上RetryRule
的话,容错性就会更强(当然相应的rt就会更长喽)~
最后用一张表格来归纳所有的IRule实现:
规则名 | 父类 | xxxxxxxxxxxxxxxxxxxxx | 备注 |
---|---|---|---|
RoundRobinRule | - | 线性轮询 | 轮询index,选择index对应位置的server |
WeightedResponseTimeRule | RoundRobinRule | 根据rt分配一个权重值,rt时间越长,weight越小,被选中的可能性就越低 | 使用一个后台线程默认每30s重新计算一次权重值 |
BestAvailableRule | ClientConfigEnabled… | 选择一个活跃请求数最小的Server | 忽略已经被熔断的Server |
PredicateBasedRule | ClientConfigEnabled… | 基于断言器实现的规则 | 本类为抽象类,具体过滤规则交给子类 |
AvailabilityFilteringRule | PredicateBasedRule | 过滤掉已熔断or活跃请求数太高的Server后,剩下的执行线性轮询 | 依赖于AvailabilityPredicate这个断言器实现过滤 |
ZoneAvoidanceRule | PredicateBasedRule | 复合判断。先选出可用区,然后在按上规则筛选出复合条件的Server们,执行线性轮询 | 使用ZoneAvoidancePredicate和AvailabilityPredicate两个主断言器实现过滤 |
RandomRule | - | 完全随机选择 | 此实现有bug,有bug,有bug |
RetryRule | - | 对任何IRule包一层重试机制 | 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
说明:若是直接extends AbstractLoadBalancerRule
此处不写出,因为所有的实现均继承了它,没必要显示写出
关于IRule的随机规则RandomRule
和重试规则RetryRule
就先介绍到这,本文首先了解了RandomRule
随机规则他在生产环境几乎不会使用,并且重点是它还存在bug,不能使用。而重试规则RetryRule
我个人推荐可以尝试在生产上使用,它使用起来也方便,能够提升系统的健康程度,当然牺牲的可能是rt时间~
到此LoadBalancer
的五大核心组件就全部,非常详细的介绍完了,下章将进入负载均衡的调度中心ILoadBalancer
的深入学习。