[享学Netflix] 五十三、Ribbon的LoadBalancer五大组件之:IRule(三)随机和重试,所有IRule实现总结

任何你写的代码,超过6个月不去看它,当你再看时,都像是别人写的。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

关于IRule的实现,还差两个实现规则,一个是随机规则RandomRule,一个是重试规则RetryRule,本文将进行收尾,并且给出对所有IRule实现的总结列表。


正文

RandomRule 随机规则

随机选择一个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的size
  • 但是,取值的时候却用upList.get(index)来取值

这很明显是个bug,下面我们通过代码示例来验证这个bug。


代码演示RandomRule的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的版本。


RetryRule 重试规则

它可以在给定的任何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所有内置规则归纳

最后用一张表格来归纳所有的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们,执行线性轮询 使用ZoneAvoidancePredicateAvailabilityPredicate两个主断言器实现过滤
RandomRule - 完全随机选择 此实现有bug,有bug,有bug
RetryRule - 对任何IRule包一层重试机制 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server

说明:若是直接extends AbstractLoadBalancerRule此处不写出,因为所有的实现均继承了它,没必要显示写出


总结

关于IRule的随机规则RandomRule和重试规则RetryRule就先介绍到这,本文首先了解了RandomRule随机规则他在生产环境几乎不会使用,并且重点是它还存在bug,不能使用。而重试规则RetryRule我个人推荐可以尝试在生产上使用,它使用起来也方便,能够提升系统的健康程度,当然牺牲的可能是rt时间~

到此LoadBalancer的五大核心组件就全部,非常详细的介绍完了,下章将进入负载均衡的调度中心ILoadBalancer的深入学习。

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了377 篇原创文章 · 获赞 572 · 访问量 51万+
App 阅读领勋章
微信扫码 下载APP
阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览