Ribbon负载均衡器和策略

负载均衡器

AbstractloadBalancer 

AbstractLoadBalancer是ILoadBalancer接口的抽象实现

1:在该抽象类中定义 了一个关于服务实例的分组枚举类 ServerGroup, 它包含以下三种不同类型: 

 ALL: 所有服务实例    STATUS_UP: 正常服务的实例   STATUS_NOT_UP: 停止服务的实例

2:实现了 一 个 chooseServer () 函数, 该 函数通过调用接口中的 chooseServer (Object key)实现, 其中参数key为 null, 表示在选择具体服务实例 时忽略key的条件判断。

3:定义了两个抽象函数:

     getServerList(ServerGroup serverGroup): 定义了根据分组类型来获取 不同的服务实例的列表    

     getLoadBalancerStats(): 定义了获取 LoadBalancerStats 对象的方法, LoadBalancerStats 对象被用来存储负载均衡器中各个服务实例当前的属性和 统计信息。 这些信息非常有用, 我们可以利用这些信息来观察负载均衡器的运行情 况, 同时这些信息也是用来制定负载均衡策略的重要依据。

BaseloadBalancer

BaseLoadBalancer 类是和Ribbon 负载均衡器的基础实现类,在该类中定义了很多关 于负载均衡器相关的基础内容。 

 1:定义并维护了两个存储服务实例 Server 对象的列表。 一个用于存储所有服务实例 的清单,一个用于存储正常服务的实例清单

 2:定义了之前我们提到的用来存储负载均衡器各服务实例属性和统计信息的 LoadBalancerStats 对象

 3:定义了检查服务实例是否正常服务的IPing对象,在 BaseLoadBalancer 中默 认为 null, 需要在构造时注入它的具体实现

扫描二维码关注公众号,回复: 9670568 查看本文章

4:定义了检查服务实例操作的执行策略对象IpingStrategy,在BaseLoadBalancer 中默认使用了该类中定义的静态内部类 SerialPingStrategy 实现。 根据源码, 我们可以看到该策略采用线性遍历 ping 服务实例的方式实现检查。 该策略在当 IPing 的实现速度不理想, 或是 Server 列表过大时, 可能会影响系统性能, 这时 候需要通过实现 IPingStrategy 接口并重写 pingServers(IPing ping, Server[] servers) 函数去扩展 ping 的执行策略。

5:定义了负载均衡的处理规 则 IRule 对 象,从 BaseLoadBalancer 中 chooseServer (Object key) 的实现源码,我们可以知道,负载均衡器实际将服 务实例选择任务委托给了 IRule 实例中的 choose 函数来实现. 而在这里,  默认 初始化了 RoundRob江Rule 为工Rule 的实现对象。RoundRobinRule 实现了最 基本且常用的线性负载均衡规则。

6:启动 ping 任务:在 BaseLoadBalancer 的默认构造函数中,会直接启动一个用于 定时检查 Server 是否健康的任务。该任务默认的执行间隔为 10 秒。实现了 ILoadBalancer 接口定义的负载均衡器应具备以下一系列基本操作

7:addServers(List newServers): 向负载均衡器中增加新的服务实例列表, 该实现将原本已经维护着的所有服务实例清单 al1ServerList 和新传入的服 务实例清单 newServers 都加入到 newList 中, 然后通 过调用 setServersList 函数对 new口st 进行处理,在 BaseLoadBalancer 中实 现的时候会使用新的列表覆盖旧的列表

8:chooseServer (Object key): 挑选一个具体的服务实例

9:markServerDown (Server server): 标记某个服务实例暂停服务。

10:getReachableServers(): 获取可用的服务实例列表。由于 BaseLoadBalancer 中单独维护了一个正常服务的实例清单, 所以直接返回即可。

11:getAllServers (): 获取所有的服务实例列表。由于 BaseLoadBalancer 中单 独维护了一个所有服务的实例清单, 所以也直接返回它即可。

DynamicServerlistloadBalancer 

DynamicServerListLoadBalancer 类继承于 BaseLoadBalancer 类, 它是对 基础负载均衡器的扩展。 在该负载均衡器中, 实现了服务实例清单在运行期的动态更新能 力;同时, 它还具备了对服务实例清单的过滤功能

1:ServerList  (ServerList的实现类,DomainExtractingServerList)

它定义了两个抽象方法: getInitialListOfServers用于获取初始化的服务实例清单,而getUpdatedListOfServers用于获取更新的服务实例清单

2:ServerListUpdater:服务更新器, 实现类:

PollingServerListUpdater: 动态服务列表更新的默认策略, 也就是说, DynamicServer巨stLoadBalancer 负载均衡器中的默认实现就是它, 它通过 定时任务的方式进行服务列表的更新。 · EurekaNotificationServerListUpdater: 该更新器也可服务于 DynamicServerListLoadBalancer 负载均衡器,但是它的触发机制与 PollingServer巨stUpdater 不同,它需要利用 Eureka 的事件监听器来驱动服务列表的更新操作。

3:ServerListFilter

主要用于实现对服务实例列表的过滤, 通过传入的 服务实例清单, 根据一些规则返回过滤后的服务实例清单。实现类:

         1:AbstractServerListFilter: 这是一个抽象过滤器,在这里定义了过滤时需要 的一个重要依据对象 LoadBalancerStats

         2:ZoneAffinityServerListFilter: 该过滤器基于 “ 区域感知 (Zone Affinity)" 的方式实现服务实例的过滤, 也就是说,它会根据提供服务的实例所处的区域 (Zone) 与消费者自身的所处区域 (Zone) 进行比较, 过滤掉那些不是同处一个区 域的实例。对于服务实例列表的过滤是通过Iterables.filter(servers,this.zoneAffinityPredicate.getServerOnlyPredicate()) 来实现的, 其中判断依据由 ZoneAffinityPredicate 实现服务实例与消费者的 Zone比较。 而在过滤之后, 这里并不会马上返回过滤的结果, 而是通过 shouldEnableZoneAffnity 函数来判断是否要启用“区域感知” 的功能。 它使用了 LoadBalancerStats的 getZoneSnapshot 方法 来获取这些过滤后的同区域实例的基础指标(包含实例数量、断路器断开数、 活动请求数、 实例平均负载等),根据一系列的算法求出下面的几个评价值并与设置的阙值进行对比(下 面的为默认值), 若有一个条件符合, 就不启用 “ 区域感知 ” 过滤的服务实例清单。 这一算 法实现为集群出现区域故障时, 依然可以依靠其他区域的实例进行正常服务提供了完善的 高可用保障。 

                      blackOutServerPercentage: 故障实例百分比(断路器断开数/实例数量)>=0.8

                      activeReqeustsPerServer: 实例平均负载 >=0.6

                      availableServers: 可用实例数(实例数量 - 断路器断开数)<2

          3:DefaultNIWSServerListFilter: 该过滤器完全继承自ZoneAffnityServerListF辽ter, 是默认的NIWS (Netflix Internal Web Service)过滤器。

           4: ServerListSubsetSusetFilter: 该 过 滤器也继承自 ZoneAffinityServerListFilter, 它非常适用于拥有大规模服务器集群(上百或更多)的系统。 因为 它可以产生一个 “ 区域感知 ” 结果的子集列表, 同时它还能够通过比较服务实例的 通信失败数量和并发连接数来判定该服务是否健康来选择性地从服务实例列表中剔 除那些相对不够健康的实例。

该过滤器的实现主要分为以下三步:

                         1:获取 “ 区域感知 ” 的过滤结果, 作为候选的服务实例清单

                         2:从当前消费者维护的服务实例子集中剔除那些相对不够健康的实例(同时也将这 些实例从候选清单中剔除, 防止第三步的时候又被选入)不够健康的标准如下 所示。

                              a. 服务实例的并发连接数超过客户端配置的值, 默 认为0' 配置参数为 ..ServerListSubsetFilter.eliminat ionConnectionThresold。

                               b. 服务实 例的失败 数超过客户端配置的值 , 默 认为0' 配置参数为 ..ServerListSubsetFilter.eliminat ionFailureThresold。

 c如果按符合上面任一规则的服务实例剔除后,剔除比例小于客户端默认配置的 百分比, 默认为o .1 C 10%), 配置参数为.. ServerListSubsetFilter.forceEliminatePercent, 那么就先对剩 下的实例列表进行健康排序,再从最不健康的实例进行剔除,直到达到配置的 剔除百分比。

                     3:在完成剔除后, 清单已经少了至少10% C默认值)的服务实例, 最后通过随机 的方式从候选清单中选出 一批实例加入到清单中, 以保持服务实例子集与原来 的数量一致, 而默认的实例子集数量为20

ZoneAwareloadBalancer 

ZoneAwareLoadBalancer 负载均衡器是对 DynamicServerListLoadBalancer 的扩展。在 DynamicServerListLoadBalancer 中, 我们可以看到它并没有重写选择 具体服务实例的 chooseServer 函数, 所以它依然会采用在 BaseLoadBalancer 中实 现的算法。 使用RoundRobinRule 规则, 以线性轮询的方式来选择调用的服务实例, 该 算法实现简单并没有区域 (Zone) 的概念, 所以它会把所有实例视为一个 Zone下的节点来 看待, 这样就会周期性地产生跨区域 (Zone) 访问的情况, 由于跨区域会产生更高的延迟, 这些实例主要以防止区域性故障实现高可用为目的而不能作为常规访问的实例, 所以在多 区域部署的清况下会有一定的性能问题, 而该负载均衡器则可以避免这样的问题

负载均衡策略

  AbstractloadBalancerRule 

           负载均衡策略的抽象类,在该抽象类中定义了负载均衡器ILoadBalancer对象 ,该 对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息来作为分配依 据,并以此设计一些符法来实现针对特定场景的高效策略

  Random Rule 

          该策略实现了从服务实例清单中随机选择 一个服务实例的功能。 它的具体实现如下, 可以看到IRule 接口的 choose(Object key)函数实现, 委托给了该类中的 choose (ILoadBalancer lb, Object key), 该方法增加了 一个负载均衡器对象的参 数。 从具体的实现上看,它会使用传入的负载均衡器来获得可用实例列表upList和所有 实例列表all归st, 并通过rand.nextint(serverCount)函数来获取 一个 随机数,并将该随机数作为 upList 的索引值来返回具体实例。 同时, 具体的选择逻辑在一个 while (server == null) 循环之内, 而根据选择逻辑的实现, 正常情况下每次选择都应该选出 一个服务实例, 如果出现死循环获取不到服务实例时, 则很有可能存在并发的 Bug。

  RoundRobinRule

          该策略实现了按照线性轮询的方式依次选择每个服务实例的功能。 它的具体实现如下,其 详细结构与 RandomRule 非常类似。 除了循环条件不同外, 就是从可用列表中获取所谓的逻 辑不同。 从循环条件中, 我们可以看到增加了一个 count计数变量, 该变量会在每次循环之 后累加, 也就是说, 如果一直选择不到 server 超过 10 次, 那么就会结束尝试, 并打印一个警 告信息 No available alive servers after 10 tries from load balancer:而线性轮询的实现则是通过Atomicinteger nextServerCyclicCounter对象实现, 每次进行实例选择时通过调用 incrementAndGetModulo 函数实现递增。

  RetryRule

          该策略实现了一个具备重试机制的实例选择功能。 从下面的实现中我们可以看到, 在 其内部还定义了 一个 IRule 对象,默认使用了 RoundRobinRule 实例。 而在 choose 方 法中则实现了对内部定义的策略进行反复尝试的策略, 若期间能够选择到具体的服务实例 就返回,若选择不到就根据设置的尝试结束时间为阙值 (maxRetryMillis 参数定义的值 + choose 方法开始执行的时间戳), 当超过该阀值后就返回 null。

  WeightedResponseTimeRule

         该策略是对 RoundRobinRule 的扩展, 增加了根据实例的运行情况来计算权重, 并 根据权重来挑选实例, 以达到更优的分配效果, 它的实现主要有三个核心内容:

1:定时任务

      WeightedResponseTimeRule 策略在初始化的时候会通过 serverWeightTimer. schedule (new   DynarnicServerWeightTask (), 0, serverWeightTaskTimerinterval) 启动一个定时任务, 用来为每个服务实例计算权重, 该任务默认30秒执行一次。

2:权重计算

      在源码中我们可以轻松找到用千存储权重的对象 List<Double> accumulatedWeights = new ArrayList () ,该List 中每个权重值所处的位置对应了负载均衡器维护的服务实例清单中所有实例在清单中的位置。维护实例权重的计算过程通过 maintainWeights函数

该函数的实现主要分为两个步骤: 

根据LoadBalancerStats中记录的每个实例的统计信息, 累加所有 实例的平均 响应时间, 得到总平均响应时间totalResponseTime, 该值会用于后续的计算。 ·

为负载均衡器中维护的实例清单逐个计算权重(从第 一个开始),计算规则为 weightSoFar+totalResponseTime 实例的平均响应时间,其中weightSoFar 初始化为零, 并且每计算好一个权重需要累加到 weightSoFar上供下一次计算使用

 3:实例选择

       生成一个[ 0, 最大权重值)区间内的随机数。 

遍历权重列表, 比较权重值与随机数的大小, 如果权重值大于等千随机数, 就拿当 前权重列表的索引值去服务实例列表中获取具体的实例。 这就是在上一节中提到的 服务实例会根据权重区间挑选的原理, 而权重区间边界的开闭原则根据绊法, 正常 每个区间为(x, y)的形式, 但是第一个实例和最后一个实例为什么不同呢?由于 随机数的最小取值可以为O, 所以第一个实例的下限是闭区间, 同时随机数的最大 值取不到最大权重值, 所以最后一个实例的上限是开区间

ZoneAvoidanceRule

        ZoneAvoidanceRule 在实现的时候并没有像 AvailabilityFilteringRule 那 样重写 choose 函数来优化,所以它完全遵循了父类的过滤主逻辑:“先过滤清单,再轮询 选择” 。 其中过滤清单的条件就是我们上面提到的以 ZoneAvoidancePredicate 为主过滤条件、 AvailabilityPredicate 为次过滤条件的组合过滤条 件 CompositePredicate, 从CompositePredicate 的源码片段中,我们可以看到它定义了 一 个主过 滤条件 AbstractServerPredicate delegate 以及 一组次过沽条件列表List fallbacks, 所以它的次过滤列表是可以拥有多个的, 并且由于它采用了 List 存储所以次 过滤条件是按顺序执行的在获取过滤结果的实现函数 getEligibleServers 中, 它的处理逻辑如下所示:

1:使用主过滤条件对所有实例过滤并返回过滤后的实例清单

2:依次使用次过滤条件列表中的过滤条件对主过滤条件的结果进行过滤

3:每次过滤之后(包括主过滤条件和次过滤条件),都需要判断下面两个条件,只要有 一个符合就不再进行过滤, 将当前结果返回供线性轮询算法选择:

      1:过滤后的实例总数>=最小过滤实例数(minimalFilteredServers, 默认为 1)

      2:过滤后的实例  比例>最小过滤百分比 (minimalFilteredPercentage, 默认为 0)

发布了50 篇原创文章 · 获赞 2 · 访问量 2314

猜你喜欢

转载自blog.csdn.net/eafun_888/article/details/96567812
今日推荐