• 客户端负载均衡Ribbon之三:AvailabilityFilteringRule的坑(Spring Cloud Finchley.SR2)


    我们项目配置了AvailabilityFilteringRule作为所有Ribbon调用的负载均衡规则,它有那些坑呢(理解歧义和注意点)?

    首先来看com.netflix.loadbalancer.AvailabilityFilteringRule.java源码,核心是choose方法:

    public Server choose(Object key) {
        int count = 0;
        //通过轮询选择一个server
        Server server = roundRobinRule.choose(key);
        //尝试10次如果都不满足要求,就放弃,采用父类的choose
        //这里为啥尝试10次?
        //1. 轮询结果相互影响,可能导致某个请求每次调用轮询返回的都是同一个有问题的server
        //2. 集群很大时,遍历整个集群判断效率低,我们假设集群中健康的实例要比不健康的多,如果10次找不到,就用父类的choose,这也是一种快速失败机制
        while (count++ <= 10) {
            if (predicate.apply(new PredicateKey(server))) {
                return server;
            }
            server = roundRobinRule.choose(key);
        }
        return super.choose(key);
    }

    轮询是怎么轮询呢,为啥会相互影响?
    来看下RoundRobinRule的源码

    //多线程轮询算法
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            //当前值
            int current = nextServerCyclicCounter.get();
            //新值,通过对于modulo(就是实例个数)取余
            int next = (current + 1) % modulo;
            //只有设置成功才返回
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }
    
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }
    
        Server server = null;
        int count = 0;
        //这里也是10次,不遍历整个集群,防止一个请求执行过长时间在选server上,快速失败
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();
    
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
    
            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);
    
            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }
            //判断server状态
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }
    
            // Next.
            server = null;
        }
    
        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    AvailabilityFilteringRule如何判断Server满足条件?

    看下判断类AvailabilityPredicate的源码:

    这里涉及两个配置:

    public boolean apply(@Nullable PredicateKey input) {
        LoadBalancerStats stats = getLBStats();
        if (stats == null) {
            return true;
        }
        //判断是否满足条件
        return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
    }
    
    
    private boolean shouldSkipServer(ServerStats stats) {        
        //niws.loadbalancer.availabilityFilteringRule.filterCircuitTripped是否为true
        if ((CIRCUIT_BREAKER_FILTERING.get() &&
        //该Server是否为断路状态
        stats.isCircuitBreakerTripped()) 
        //本机发往这个Server未处理完的请求个数是否大于Server实例最大的活跃连接数
                || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
            return true;
        }
        return false;
    }

    Server是否为断路状态是如何判断的呢?
    ServerStats源码,这里详细源码我们不贴了,说一下机制:

    断路是通过时间判断实现的。每次失败记录上次失败时间。如果失败了触发判断是否断路的最小失败次数以上的次数,则判断:

    计算断路持续时间: (2^失败次数)* 断路时间因子,如果大于最大断路时间,则取最大断路时间。
    判断当前时间是否大于上次失败时间+短路持续时间,如果小于,则是断路状态
    这里又涉及三个配置(这里需要将default替换成你调用的微服务名称):

    • niws.loadbalancer.default.connectionFailureCountThreshold,默认为3, 触发判断是否断路的最小失败次数,也就是,默认如果失败三次,就会判断是否要断路了。
    • niws.loadbalancer.default.circuitTripTimeoutFactorSeconds, 默认为10, 断路时间因子,
    • niws.loadbalancer.default.circuitTripMaxTimeoutSeconds,默认为30,最大断路时间

    ServerStats如何更新呢?
    首先是清空,根据我的另一系列文章对于Eureka源码和配置的分析,每次在ribbon从eureka本地定时重新拉取server列表时,就会清空。这个配置是:

    #eureka客户端ribbon刷新时间
    #默认30s
    ribbon.ServerListRefreshInterval=1000

    这里我们配置是1秒,也就是1秒内如果断路三次,就会触发断路判断。

    然后是怎么增加断路次数?这里我们看调用这个方法的源码,有效调用里面都有一个判断:

    if (lbContext.getRetryHandler().isCircuitTrippingException(throwable)) {
        //调用增加断路次数
    }

    这个isCircuitTrippingException,对于默认的DefaultLoadBalancerRetryHandler就是判断是否为SocketException.class, SocketTimeoutException.class这两个异常。如果是,就会记录到断路次数

    SocketException.class, SocketTimeoutException.class两个异常的坑与Ribbon连接超时时间
    参考我另一篇文章,Ribbon对于SocketTimeOutException重试的坑以及重试代码解析,这里不要把Ribbon的连接超时设置太短,一般如下设置即可:

    #ribbon连接超时
    ribbon.ConnectTimeout=500



  • 相关阅读:
    git push提交出现Everything up-to-date提示问题
    启动Dubbo项目注册Zookeeper时提示zookeeper not connected异常原理解析
    linux环境搭建mysql5.7总结
    Hadoop学习笔记:运行wordcount对文件字符串进行统计案例
    kafka3.0创建topic出现zookeeper is not a recognized option
    sql_waf绕过
    win11环境映像劫持
    vulnhub靶场—devguru
    vulhub-Presidential靶场解题过程
    php命令执行无回显判断及利用方法
  • 原文地址:https://www.cnblogs.com/duanxz/p/6202982.html
Copyright © 2020-2023  润新知