[享学Netflix] 五十二、Ribbon的LoadBalancer五大组件之:IRule(二)应用于大规模集群的可配置规则

如果你是房间里最聪明的人,那么你走错房间了。

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

前言

本文继续介绍IRule其它规则实现:BestAvailableRule最小并发数规则以及PredicateBasedRule基于断言器实现的的两种方式:AvailabilityFilteringRuleZoneAvoidanceRule

本文介绍的规则不是简单的轮询,它更关注可用性如:zone的可用性,以及每台Server自己的可用性方面,这些规则适用于大规模集群or多分区、多可用区环境的负载均衡策略。


正文

本文所有规则实现均是ClientConfigEnabledRoundRobinRule的子类,顾名思义它的规则是可以通过ClientConfig来配置的,并非是固定的。


ClientConfigEnabledRoundRobinRule

它选择策略的实现很简单,内部定义了RoundRobinRule,choose方法还是采用了RoundRobinRule的choose方法,所以它的选择策略和RoundRobinRule的选择策略一致。

public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {

	RoundRobinRule roundRobinRule = new RoundRobinRule();

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        roundRobinRule = new RoundRobinRule();
    }
    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
    	super.setLoadBalancer(lb);
    	roundRobinRule.setLoadBalancer(lb);
    }
    // 若子类不复写 效果同`RoundRobinRule`
    @Override
    public Server choose(Object key) {
        if (roundRobinRule != null) {
            return roundRobinRule.choose(key);
        } else {
            throw new IllegalArgumentException("This class has not been initialized with the RoundRobinRule class");
        }
    }
}

该策略较为特殊,我们一般不直接使用它。因为它本身并没有实现什么特殊的处理逻辑,效果同RoundRobinRule。通过继承该策略,默认的choose就实现了线性轮询机制,在子类中做一些高级策略时通常可能存在一些无法实施的情况,就可以用父类的实现作为备选,所以它作为父类用于兜底。

设计上,其实把该类Abstract化或许更为合理

它作为父类,有多个子类实现,从而使用各自的算法逻辑:

  • BestAvailableRule
  • PredicateBasedRule:基于AbstractServerPredicate实现的规则,有两个子类
    • ZoneAvoidanceRule
    • AvailabilityFilteringRule

BestAvailableRule 最小并发数规则

BestAvailableRule继承自ClientConfigEnabledRoundRobinRule。该策略的特性跳过已经被熔断的实例,并且顺表找出最空闲的实例。BestAvailable:最空闲、最可用的(也就是并发请求数最低的)。很明显,统计数据来自云LoadBalancerStats/ServerStats

public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {
	// 也就是说:LoadBalancerStats是来自于lb的
	private LoadBalancerStats loadBalancerStats;
    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        if (lb instanceof AbstractLoadBalancer) {
            loadBalancerStats = ((AbstractLoadBalancer) lb).getLoadBalancerStats();            
        }
    }

    @Override
    public Server choose(Object key) {
    	// 若没有统计信息,那就回退到轮询策略呗~~~~
        if (loadBalancerStats == null) {
            return super.choose(key);
        }
        List<Server> serverList = getLoadBalancer().getAllServers();
        // 记录所有Server中最小并发数
        int minimalConcurrentConnections = Integer.MAX_VALUE;
        long currentTime = System.currentTimeMillis();

        Server chosen = null;
        for (Server server: serverList) {
            ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
            // 只要该Server没有被熔断,就选择上
            if (!serverStats.isCircuitBreakerTripped(currentTime)) {
                int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
                if (concurrentConnections < minimalConcurrentConnections) {
                    minimalConcurrentConnections = concurrentConnections;
                    chosen = server;
                }
            }
           	// 为毛不判断Server的alive性和是否可服务性呢? 
           	// 我觉得这是个小bug,毕竟你对比的是getAllServers所有服务
           	// if (server.isAlive() && (server.isReadyToServe()))
        }

        if (chosen == null) {
            return super.choose(key);
        } else {
            return chosen;
        }
    }
}

请注意我在源码处标注的小bug,在生产中也是需要引起注意的。

如果loadBalancerStats为null,则BestAvailableRule将回退到采用它的父类即ClientConfigEnabledRoundRobinRule的服务选取策略,即线性轮询

说明:没有loadBalancerStats不代表没有ILoadBalancer。ILoadBalancer是必须的,因为Server列表均来自于它~


PredicateBasedRule 基于断言器规则

顾名思义,它是基于断言器AbstractServerPredicate来实现Server的筛选。

阅读它之前,请你务必已经了解了AbstractServerPredicate的原理,因为它才是核心。关于断言器AbstractServerPredicate的详解,请参见:[享学Netflix] 四十八、Ribbon服务器过滤逻辑的基础组件:AbstractServerPredicate

// 本类为抽象类,很好
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {

	// 抽象方法:具体的断言器交给子类去指定......
	public abstract AbstractServerPredicate getPredicate();

    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }
}

它自己是个抽象类,但是它规定了一个整体的负载均衡规则是:

  • 先通过内部指定的一个AbstractServerPredicate断言器过滤然后剩余下一个ServerList
  • 然后再采用线性轮询的方式从中选取一个服务实例

它有两个实现子类,分别依赖于AvailabilityPredicateZoneAvoidancePredicate这两个断言器,因此在了解了断言器的原理的前提下,去看此篇文章将毫无障碍。


AvailabilityFilteringRule 可用性过滤规则

它依赖于AvailabilityPredicate完成过滤后,在使用线性轮询方式选择Server。

public class AvailabilityFilteringRule extends PredicateBasedRule {

    private AbstractServerPredicate predicate;
    public AvailabilityFilteringRule() {
    	super();
    	predicate = CompositePredicate.withPredicate(new AvailabilityPredicate(this, null))
                .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
                .build();
    }
    // 实现父类抽象方法
    @Override
    public AbstractServerPredicate getPredicate() {
        return predicate;
    }
    ...

    @Override
    public Server choose(Object key) {
        int count = 0;
        Server server = roundRobinRule.choose(key);
        while (count++ <= 10) {
            if (predicate.apply(new PredicateKey(server))) {
                return server;
            }
            server = roundRobinRule.choose(key);
        }
        return super.choose(key);
    }

}

它的choose逻辑是:

  • 先轮询选一台Server出来,交给predicate去判断是否合格(木有被熔断,且活跃连接数没超过阈值才算合格,具体参考AvailabilityPredicate的逻辑),若合格就直接返回,否则重复此动作一共重复10次
    • 它能实现故障实例的自动T除,这个特点在大规模集群下特别实用
  • 若10此都还没找到合格的,那就调用父类兜底getPredicate().chooseRoundRobinAfterFiltering()去轮询一台出来

为何不直接使用父类的逻辑呢?毕竟也会先过滤呀再轮询呀。这其实是子类为性能考虑的一个小技巧,自己先向后试10次,而不用每次都遍历所有的Server再从中选一台,这样对大集群的效率是提高不少的(试想一下你的集群有1000台机器,那这么做会解决不少时间的。因为试10次能够处理99.99%的case,非常划算)。


ZoneAvoidanceRule 可用区规则

它使用的是一个CompositePredicate的组合过滤器:

ZoneAvoidanceRule:

    private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
        return CompositePredicate.withPredicates(p1, p2)
                             .addFallbackPredicate(p2)
                             .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
                             .build();
        
    }

解释此过滤器逻辑

  • 主过滤器为ZoneAvoidancePredicateAvailabilityPredicate两个,也就是说主条件是会过滤掉:熔断率偏高or平均负载过高的zone区域(zone级别的过滤),并且过滤掉已经熔断or活跃请求数过高的Server。
    • 需要注意的是,这是两个并且的关系
  • fallback过滤器为AvailabilityPredicate一个(也就是不考虑zone的情况,注意:默认配置下只有主过滤器一个Server都没留下时才会启用fallback去过滤)
    • 另需注意,fallback会用AbstractServerPredicate.alwaysTrue()兜底,以保证至少至少能有一台Server返回,毕竟处理慢总比不处理要好

备注:还是文首那句话,建议请一定先了解AbstractServerPredicate基础过滤组件后再阅读本文,效果更佳

本类并没有重写choose方法,而只需提供此过滤器就好,所以它的choose逻辑是:经过CompositePredicate组合过滤器过滤后剩下的Server,执行线性轮询找到一台Server。

另外,需要注意的是,本类提供了多个static工具方法:ZoneAvoidanceRule#getAvailableZones、randomChooseZone、createSnapshot这在前面文章都详细阐述过,可出门左拐参阅。


总结

关于可配置的规则如BestAvailableRule、AvailabilityFilteringRule、ZoneAvoidanceRule等就先介绍到这了。本文所介绍的规则有如下特点:

  • 规则均是可通过IClientConfig动态配置的(比如配置负载阈值、熔断比例等等)
  • 关注zone可用区的可用性、server本身的负载可用性等
  • 兜底方案均为线性轮询规则

分隔线

声明

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

发布了362 篇原创文章 · 获赞 531 · 访问量 48万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104970813