天天看点

【通讯】微服务中的通讯组件

通讯组件主要包括编码&解码器,列表过滤,调用代理客户端,本地优先策略,容错,重试以及超时等相关内容。

LoadBalancerClient接口的所属包org.springframework.cloud.client.loadbalancer,对其内容进行整理,可以得出如下图的关系:

【通讯】微服务中的通讯组件

IRule有很多默认的实现类,这些实现类根据不同的算法和逻辑来处理负载均衡。在大多数情况下,这些默认的实现类是可以满足需求的,如果有特性的需求,可以自己实现。

  • BestAvailableRule 选择最小请求数
  • ClientConfigEnabledRoundRobinRule 轮询
  • RandomRule 随机选择一个server
  • RoundRobinRule 轮询选择server
  • RetryRule 根据轮询的方式重试
  • WeightedResponseTimeRule 根据响应时间去分配一个weight ,weight越低,被选择的可能性就越低
  • ZoneAvoidanceRule 根据server的zone区域和可用性来轮询选择
【通讯】微服务中的通讯组件

过滤器ZoneAvoidanceRule源码解读

ZoneAvoidanceRule源码解读

public class ZoneAvoidanceRule extends PredicateBasedRule {
 
    private static final Random random = new Random();
    //使用CompositePredicate来进行服务实例清单过滤。
    //组合过滤条件
    private CompositePredicate compositePredicate;
    
    public ZoneAvoidanceRule() {
        super();
        //主过滤条件
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        //次过滤条件
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }
......
}      

CompositePredicate源码解读

public class ZoneAvoidanceRule extends PredicateBasedRule {
 
    private static final Random random = new Random();
    //使用CompositePredicate来进行服务实例清单过滤。
    //组合过滤条件
    private CompositePredicate compositePredicate;
    
    public ZoneAvoidanceRule() {
        super();
        //主过滤条件
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        //次过滤条件
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }
......
}      

ZoneAvoidancePredicate源码解读

//服务实例所在的Zone必须可用
public class ZoneAvoidancePredicate extends  AbstractServerPredicate {
 
    ......
 
    @Override
    public boolean apply(@Nullable PredicateKey input) {
        if (!ENABLED.get()) {
            return true;
        }
        String serverZone = input.getServer().getZone();
        if (serverZone == null) {
            // there is no zone information from the server, we do not want to filter
            // out this server
            return true;
        }
        LoadBalancerStats lbStats = getLBStats();
        if (lbStats == null) {
            // no stats available, do not filter
            return true;
        }
        if (lbStats.getAvailableZones().size() <= 1) {
            // only one zone is available, do not filter
            return true;
        }
        Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
        if (!zoneSnapshot.keySet().contains(serverZone)) {
            // The server zone is unknown to the load balancer, do not filter it out
            return true;
        }
        logger.debug("Zone snapshots: {}", zoneSnapshot);
        Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
        logger.debug("Available zones: {}", availableZones);
        //服务实例所在的Zone必须可用
        if (availableZones != null) {
            return availableZones.contains(input.getServer().getZone());
        } else {
            return false;
        }
    }    
}      

列表过滤

大概的实现类流程如下:

【通讯】微服务中的通讯组件

ServerList就是server的列表,IRule做负载均衡的关键参数就是server列表,就是从这里获取的。

LoadBalancerStats内部有两个成员变量,一是upServerListZoneMap,二是serverStatsCache。

【通讯】微服务中的通讯组件

upServerListZoneMap定义为volatile

Map<String, List<? extends Server>>,其中存储了zone和server列表的对应关系。

serverStatsCache定义为LoadingCache<Server,

ServerStats>,存储了server和ServerStats对象的对应关系,其使用了guaua的cache。

根据以上两种结构,能够从每个server的状态统计到整个zone的状态。

Affinity即密切关系,这个filter是ribbon的区域亲和性过滤。默认情况下,单独使用ribbon,区域亲和的过滤器默认是关闭的,可以通过修改配置打开。

ZoneAffinityServerListFilter先进行亲和性过滤,之后再进行zone健康检查,如果zone认为不健康,则不再执行过滤,认为zone不健康的条件如下:

  • zone中断路器打开的server比例达到了整个集群中的80%
  • server平均负载(即存活的请求数)达到60%
  • server存活数小于2,即0或1

DefaultNIWSServerListFilter继承自ZoneAffinityServerListFilter,默认没有任何实现。

ZonePreferenceServerListFilter,spring-cloud自己实现的过滤器,即可以通过配置,强制使用配置的zone来过滤。

其实现也比较简单,即先用ZoneAffinityServerListFilter来过滤,如果父类没有启用过滤则再用配置的zone来强制过滤。

如果没有配置zone,则该filter退化为ZoneAffinityServerListFilter。

ServerListSubsetFilter这个类用于限制返回server列表的数量,特别适用于集群中有成百上千的server。因为让客户端http连接池跟每个server保持链接其实用处不大,它同样会剔除不健康的server。

调用代理客户端

WeightedResponseTimeRule,获取平均响应时间作为权重计算的策略。

AvailabilityFilteringRule,判断当前断路器是否打开作为是否可用的策略。使用活跃请求数量选择最小的server。

ZoneAvoidanceRule,获取可用的所有zone,获取zone状态信息。

ZoneAwareLoadBalancer,它有如下功能

  • 当选择server时能够避免某个不健康的zone。
  • 衡量zone健康状况的关键指标是平均活跃请求,即某个zone的平均活跃请求=这个zone中,还未完成的请求/存活的server

    当某个状况不好的zone慢慢的发生超时的时候(即大量的请求被卡住),这个指标是非常有效的。

  • 此均衡器将会计算所有的存活的zone的状况:当任何某个zone平均活跃请求达到配置的阈值时,这个zone将会从被从存活列表中移除;如果多个zone达到阈值,那么具有平均server请求量最大的zone将会被移除;
  • 一旦状况最坏的zone被移除,那么将会按照zone具有的实例数量作为概率来选择某个zone。
  • 选择完zone,将会使用IRule来选择一个server,默认的实现为ZoneAvoidanceRule(由于此时只有一个zone,故ZoneAvoidanceRule退化为AvailabilityFilteringRule)。

来看一下核心方法chooseServer是如何实现的,我们已经知道了最终负载均衡的实现是ZoneAwareLoadBalancer,而它也重写了chooseServer,它的choose过程如下:

【通讯】微服务中的通讯组件

单个zone的choose过程说明:

  • 从LoadBalancerStats查询存活的zone数量。
  • 调用BaseLoadBalancer的chooseServer
  • BaseLoadBalancer的实现很简单,直接委托给具体的Rule来选择
  • 最终会使用ZoneAvoidanceRule,而此时zone的数量为1,ZoneAvoidanceRule的zone过滤选择功能不起作用,退化为AvailabilityFilteringRule,即按照server断路器打开或者并发量过大过滤掉一些server,再轮询选择一个。

多个zone的choose过程说明

  • 从LoadBalancerStats查询存活的zone数量。
  • 发现>1,则调用ZoneAvoidanceRule进行zone的过滤和挑选。
  • 挑选好zone之后,跟 单个zone的choose过程一样了,委托给BaseLoadBalancer来进行具体的server挑选。

本中心优先策略

利用Ribbon的Zone-Affinity(区域亲和性)来解决。Ribbon的Zone-Affinity的原理是:服务消费者在获取注册服务的列表时,添加一个filter来过滤注册服务,根据配置选择跟服务消费者在同一个区的服务,这样一般可以降低延迟;如果过滤后的服务正忙或者异常,则停止区域感知。从而可以做到基于实时的服务运行状况,决定是否只选择本区域的服务,当本区域的服务发生故障时,可以快速的切换。

扩展IRule

可以通过以下配置项识别实例所在的区域:


eureka.instance.metadata-map.zone=xxx


深圳的服务全部加上:


eureka.instance.metadata-map.zone=sz
成都的服务全部加上:


eureka.instance.metadata-map.zone=cd
以达到本地优先效果。      

重试

负载均衡的重试机制
ribbon:
  OkToRetryOnAllOperations: true # 是否对所有请求都进行重试
  MaxAutoRetries: 0     #重试次数
  MaxAutoRetriesNextServer: 2   #重试切换实例次数
  ConnectTimeout: 250
  ReadTimeout: 300
  retryableStatusCodes: 503,500 # 这里不配置其实也可以,不配置的时候,仅仅当请求服务实例报错的时候重试,配置了的时候,当请求服务实例出现这里指定的状态,也会重试      

超时

#ribbon请求连接的超时时间,限制3秒内必须请求到服务,并不限制服务处理的返回时间
ribbon.ConnectTimeout=3000
ribbon.SocketTimeout=5000
#请求处理的超时时间 下级服务响应最大时间,超出时间消费方(路由也是消费方)返回timeout
ribbon.ReadTimeout=5000
# 单独设置某个服务的超时时间,会覆盖其他的超时时间限制,服务的名称以注册中心页面显示的名称为准,超时时间不可大于断路器的超时时间
#service-a.ribbon.ReadTimeout=50000
#service-a.ribbon.ConnectTimeout=50000      

关注公众号 soft张三丰 

继续阅读