接上上次的内容,这次主要来看一下Spring Cloud中定义的各种负载均衡器和负载均衡策略,首先,我们通过上一篇的学习,知道了Ribbon在实现具体的客户端负载均衡时,是使用了ILoadBalancer接口实现的,所以,我们主要就是要看一下这个接口的各种实现类是怎么样的。

    AbstractLoadBalancer:

    AbstractLoadBalancer是ILoadBalancer接口的抽象实现,在这个抽象类中定义了一个关于服务实例的分组枚举类ServerGroup,这个枚举类里定义有三种实例类型:ALL——所有服务实例,STATUS_UP——正常服务的实例,STATUS_NOT_UP——停止服务的实例。另外,在这个抽象类里还实现了chooseServer()函数,定义了两个抽象函数getServerList(ServerGroup serverGroup)和getLoadBalancerStats(),这两个方法都可以看字面意思去理解,其中,第二个方法的返回值是LoadBalancerStats对象,这个对象用来存储负载均衡器中各个服务实例当前的属性和统计信息。

    BaseLoadBalancer:

    此类是Ribbon实现负载均衡器的基础实现类,它里面定义了两个存储服务实例Server对象的列表,一个用来存储所有服务实例,一个用来存储正常服务的实例

@Monitor(
    name = "LoadBalancer_AllServerList",
    type = DataSourceType.INFORMATIONAL
)
protected volatile List<Server> allServerList;
@Monitor(
    name = "LoadBalancer_UpServerList",
    type = DataSourceType.INFORMATIONAL
)
protected volatile List<Server> upServerList;

还定义了LoadBalancerStats对象,定义了检查服务实例是否正常的IPing对象,以及定义了检查服务实例操作的执行策略对象IPingStrategy类,以及最为重要的定义了负载均衡的处理规则IRule对象,我们下面可以看一下BaseLoadBalancer中ChooseServer方法的源码,可以看到,负载均衡器把实际的服务实例选择任务委托给了IRule实例中的choose函数去实现,这里默认初始化了RoundRobinRule为IRule的实现对象,采用了线性负载均衡策略:

public Server chooseServer(Object key) {
    if (this.counter == null) {
        this.counter = this.createCounter();
    }

    this.counter.increment();
    if (this.rule == null) {
        return null;
    } else {
        try {
            return this.rule.choose(key);
        } catch (Exception var3) {
            logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});
            return null;
        }
    }
}

此外,BaseLoadBalancer还负责启动ping任务,实现了addServers方法,实现了markServerDown方法(标记某个实例暂停服务),实现了getReachableServers方法,getAllServers方法等,这些方法的作用都比较直观,在这里也不再细说。

    DynamicServerListLoadBalancer

    这个类继承自BaseLoadBalancer类,这个类主要是实现了服务清单在运行期的动态更新能力,以及对服务实例清单的过滤功能,在这里就不从源码分析它是如何做到的了,就直接把最后的结论给出:它主要是利用了ServerListUpdater这个接口来实现更新本地服务实例清单,这个类通过自身的一个内部接口UpdateAction的实现方法doUpdate方法来实现更新本地服务实例清单,ServerListUpdater有两个实现类,分别是PollingServerListUpdater和EurekaNotificationServerListUpdater,前者是 DynamicServerListLoadBalancer中的默认实现,PollingServerListUpdater通过定时调用doUpdate方法来完成动态更新。

    而对服务清单的过滤功能主要是通过ServerListFilter这个接口实现的,它通过调用自己的getFilteredListOfServers方法完成过滤,这个接口顶层有三种具体实现,分别是ZonePreferenceServerListFilter,DefaultNIWSServerListFilter,ServerListSubsetFilter这三个类,其中ZonePreferenceServerListFilter是通过区域感知的方式实现过滤,即根据服务实例和消费者自身所处的Zone进行比较,过滤掉不在同一个Zone的实例,DefaultNIWSServerListFilter根据LoadBalancerStats提供的信息完成过滤,这个实现类也是默认是NIWS过滤器,ServerListSubsetFilter这个过滤器主要用于大规模集群。

    ZoneAwareLoadBalancer

    这个负载均衡器是对DynamicServerListLoadBalancer的扩展,这个负载均衡器也是Ribbon默认采用的实现。

负载均衡策略:

    这是本篇文章的第二个主要内容,其实各种负载均衡策略就是IRule的不同实现而已,除了抽象实现类AbstractLoadBalancerRule之外,一共有九种不同的负载均衡策略,下面我将分别解释它们具体的负载均衡策略都是怎么样的。

    1:RandomRule,顾名思义,这个策略就是从服务实例中随机选择一个服务实例。

    2:RoundRobinRule,这个我们之前见过,它是依靠线性轮询的方式选择每个服务实例。

    3:RetryRule,这是一个定义了重试机制的实例选择策略,它还是使用线性轮询的方式,但是它定义了一个尝试结束时间,如果超过了这个阈值就会返回null,否则就会一直重试。

    4:WeightedResponseTimeRule:这是RoundRobinRule的子类,它通过实例的运行情况来计算权重,并且根据权重来挑选实例,以达到最优的分配效果。通俗的说一下权重计算的方法:实例的平均响应时间越短,则被选中的概率越大(它内部的实现其实是有一个权重区间,最后根据区间的宽度来计算概率,在这里不细说,可以查看源码了解真正的原理)

    5:ClientConfigEnableRoundRobinRule,这个策略一般我们不会使用,而是会继承他来实现一些规则(接下来的四个策略都是它的子类),在这个策略内部定义了一个RoundRobinRule。

    6:BestAvailableRule,它继承自ClientConfigEnableRoundRobinRule,它注入了LoadBalancerStats对象,利用其保存的服务实例信息来选择满足要求的实例。从而选出最为空闲的实例。当LoadBalancerStats为空时,它会调用父类的线性轮询策略,这也是为什么我们要定义一个不怎么会使用到的ClientConfigEnableRoundRobinRule策略,就是当子策略有一些要求无法满足的时候,会自动使用线性轮询策略。

    7:PredicateBasedRule,这是一个基于Predicate实现的抽象策略,Predicate是Guava中对集合进行过滤的条件接口。(我对Guava也不怎么熟悉,所以当时看这个类也有点懵逼)它的逻辑就是先通过子类中实现的Predicate逻辑来过滤一部分服务实例,再以线性轮询方式从过滤得到的清单中选出一个实例

    8:AvailabilityFilteringRule,这个以及最后一个策略都继承自PredicateBasedRule,它实现了具体的Predicate逻辑——AvailabilityPredicate,它的内部主要有两个步骤,一是看断路器是否已经开路,二是看实例的请求并发数是否大于阈值,如果都不满足就返回true,最后,这个策略还优化了choose函数,它不是简单的线性轮询,而是采用了线性抽样的方式来尝试寻找较空闲且可用的实例使用,大大减小了开销。

    9:ZoneAvoidanceRule,它使用了CompositePredicate来进行服务实例清单的过滤,这是一个组合过滤条件,它是ZoneAvoidancePredicate和AvailabilityPredicate的结合。

这样,就把所有默认实现的负载均衡器和负载均衡策略都说完了,虽然有些地方没有具体到源码,不过把大致的思路都理了一遍,有兴趣的话可以结合源码重新学习一遍(其实还是懒,搬运源码结合源码细讲的话篇幅要加长几倍)

下一篇就开始进入Spring Cloud的服务容错保护组件——Spring Cloud Hystrix