【微服务】SpringCloud中Ribbon的WeightedResponseTimeRule策略

【微服务】SpringCloud中Ribbon的WeightedResponseTimeRule策略_第1张图片

✨【微服务】SpringCloud中Ribbon的轮询(RoundRobinRule)与重试(RetryRule)策略

✨【微服务】SpringCloud中Ribbon集成Eureka实现负载均衡

✨【微服务】SpringCloud轮询拉取注册表及服务发现源码解析

✨【微服务】SpringCloud微服务续约源码解析

✨【微服务】SpringCloud微服务注册源码解析

✨【微服务】Nacos2.x服务发现?RPC调用?重试机制?

✨【微服务】Nacos通知客户端服务变更以及重试机制

✨【微服务】Nacos服务发现源码分析

✨【微服务】SpringBoot监听器机制以及在Nacos中的应用

✨【微服务】Nacos服务端完成微服务注册以及健康检查流程

✨【微服务】Nacos客户端微服务注册原理流程

✨【微服务】SpringCloud中使用Ribbon实现负载均衡的原理

✨【微服务】SpringBoot启动流程注册FeignClient

✨【微服务】SpringBoot启动流程初始化OpenFeign的入口

✨Spring Bean的生命周期

✨Spring事务原理

✨SpringBoot自动装配原理机制及过程

✨SpringBoot获取处理器流程

✨SpringBoot中处理器映射关系注册流程

✨Spring5.x中Bean初始化流程

✨Spring中Bean定义的注册流程

✨Spring的处理器映射器与适配器的架构设计

✨SpringMVC执行流程图解及源码

目录

一、前言

二、源码剖析

1、WeightedResponseTimeRule策略

1.1、初始化定时更新权重任务

1.2、计算权重任务

2、权重数据来源

2.1、维护响应数据的入口

3、如何根据权重选取服务


一、前言

    前面的文章也分析了Ribbon的负载均衡轮询算法的实现、重试、CAS、死循环。重试策略怎么实现的、多线程编程、延迟任务、中断。本篇文章探讨如何根据权重选取服务、如何计算权重的、定时任务、权重数据来源维护?

二、源码剖析

1、WeightedResponseTimeRule策略

【微服务】SpringCloud中Ribbon的WeightedResponseTimeRule策略_第2张图片

WeightedResponseTimeRule继承了RoundRobinRule,即继承了RoundRobinRule的功能并且进行了功能上面的扩展。

1.1、初始化定时更新权重任务

public class WeightedResponseTimeRule extends RoundRobinRule {
    
    // 按照毫秒单位,则30秒
    public static final int DEFAULT_TIMER_INTERVAL = 30 * 1000;
    
    private int serverWeightTaskTimerInterval = DEFAULT_TIMER_INTERVAL;
    
    // 包含从索引0到当前索引的累计权重,例如,索引2的元素包含从0到2的服务器权重之和
    private volatile List accumulatedWeights = new ArrayList<>();
    
    // 随机类,选择服务时用到
    private final Random random = new Random();
    // 用于定时任务
    protected Timer serverWeightTimer = null;
    // 用于CAS
    protected AtomicBoolean serverWeightAssignmentInProgress = new AtomicBoolean(false);

    String name = "unknown";

    public WeightedResponseTimeRule() {
        // 初始化父类
        super();
    }

    public WeightedResponseTimeRule(ILoadBalancer lb) {
        super(lb);
    }
    
    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        // 调用RoundRobinRule的setLoadBalancer(ILoadBalancer lb)方法
        super.setLoadBalancer(lb);
        if (lb instanceof BaseLoadBalancer) {
            name = ((BaseLoadBalancer) lb).getName();
        }
        initialize(lb);
    }

    void initialize(ILoadBalancer lb) {        
        if (serverWeightTimer != null) {
            serverWeightTimer.cancel();
        }
        serverWeightTimer = new Timer("NFLoadBalancer-serverWeightTimer-"
                + name, true);
        // 默认30妙
        serverWeightTimer.schedule(new DynamicServerWeightTask(), 0,
                serverWeightTaskTimerInterval);
        // do a initial run
        ServerWeight sw = new ServerWeight();
        sw.maintainWeights();

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                logger
                        .info("Stopping NFLoadBalancer-serverWeightTimer-"
                                + name);
                serverWeightTimer.cancel();
            }
        }));
    }
}

主要工作:

  1. 初始化时会初始化父类
  2. 通过super调用父类方法做一些初始化工作,initialize(lb)初始化定时任务
  3. 默认30秒更新权重

1.2、计算权重任务

    class DynamicServerWeightTask extends TimerTask {
        public void run() {
            ServerWeight serverWeight = new ServerWeight();
            try {
                serverWeight.maintainWeights();
            } catch (Exception e) {
                logger.error("Error running DynamicServerWeightTask for {}", name, e);
            }
        }
    }

    class ServerWeight {

        public void maintainWeights() {
            // 获取负载均衡器
            ILoadBalancer lb = getLoadBalancer();
            // 预检验,防止空指针异常
            if (lb == null) {
                return;
            }

            // CAS避免更新丢失,同一时刻只允许一个线程进入
            if (!serverWeightAssignmentInProgress.compareAndSet(false,  true))  {
                return; 
            }
            
            try {
                // 权重调整工作开始
                logger.info("Weight adjusting job started");
                // 强制类型转换,以便获取负载均衡器统计器及所有服务
                AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
                // 从负载均衡器获取负载均衡统计器,该统计是在负载均衡请求响应后或异常后进行的
                LoadBalancerStats stats = nlb.getLoadBalancerStats();
                if (stats == null) {
                    // no statistics, nothing to do没有统计数据,无所事事
                    return;
                }
                double totalResponseTime = 0;
                // 找到最大95% 的响应时间
                for (Server server : nlb.getAllServers()) {
                    // this will automatically load the stats if not in cache
                    // 如果不在缓存中,这将自动加载统计信息
                    ServerStats ss = stats.getSingleServerStat(server);
                    totalResponseTime += ss.getResponseTimeAvg();
                }
                
                // 每个服务器的权重是(所有服务器的 responseTime 之和-responseTime) ,
                // 因此响应时间越长,权重越小,选择的可能性越小
                Double weightSoFar = 0.0;
                
                // 创建新的列表和热交换引用
                List finalWeights = new ArrayList<>();
                // 从负载均衡器获取所有服务,然后遍历
                for (Server server : nlb.getAllServers()) {
                    // 根据服务获取服务(在负载均衡器中捕获每个服务器(节点)的)统计信息
                    ServerStats ss = stats.getSingleServerStat(server);
                    double weight = totalResponseTime - ss.getResponseTimeAvg();
                    weightSoFar += weight;
                    finalWeights.add(weightSoFar);   
                }
                // 更新权重
                setWeights(finalWeights);
            } catch (Exception e) {
                logger.error("Error calculating server weights", e);
            } finally {
                // 类似于释放锁,以便下次执行更新权重
                serverWeightAssignmentInProgress.set(false);
            }

        }
    }

主要逻辑:

  1. 获取负载均衡器,预检验,防止空指针异常,如果负载均衡器不存在结束方法体
  2. CAS避免更新丢失,同一时刻只允许一个线程进入,如果CAS失败结束方法体
  3. 强制类型转换为AbstractLoadBalancer,从负载均衡器获取负载均衡统计器,该统计是在负载均衡请求响应后或异常后进行的,如果统计信息没有则结束方法体
  4. 从负载均衡器获取所有服务并遍历,根据服务获取服务(在负载均衡器中捕获每个服务器(节点)的)统计信息,累加服务实例的平均响应时间和为totalResponseTime
  5. 从负载均衡器获取所有服务,然后遍历;根据服务获取服务(在负载均衡器中捕获每个服务器(节点)的)统计信息;每个服务器的权重是(所有服务器的 responseTime 之和-responseTime) ,因此响应时间越长,权重越小,选择的可能性越小;计算的权重累加weightSoFar 得到一个区间边界值

2、权重数据来源

2.1、维护响应数据的入口

                                        // ServerOperation匿名内部类调用call
                                        return operation.call(server).doOnEach(new Observer() {
                                            private T entity;
                                            @Override
                                            public void onCompleted() {
                                                recordStats(tracer, stats, entity, null);
                                                // TODO: What to do if onNext or onError are never called?
                                            }

                                            @Override
                                            public void onError(Throwable e) {
                                                recordStats(tracer, stats, null, e);
                                                logger.debug("Got error {} when executed on server {}", e, server);
                                                if (listenerInvoker != null) {
                                                    listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo());
                                                }
                                            }

                                            @Override
                                            public void onNext(T entity) {
                                                this.entity = entity;
                                                if (listenerInvoker != null) {
                                                    listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo());
                                                }
                                            }                            
                                            
                                            private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) {
                                                tracer.stop();
                                                // 记录请求完成的信息,用于统计,如:权重等
                                                loadBalancerContext.noteRequestCompletion(stats, entity, exception,
                                                        tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler);
                                            }
                                        });

ServerOperation匿名内部类调用call,无论是响应成功还是错误都会记录响应时间以便计算权重

3、如何根据权重选取服务

    @Override
    public Server choose(ILoadBalancer lb, Object key) {
        // 预校验,以防空指针异常
        if (lb == null) {
            // 负载均衡器空则返回null
            return null;
        }
        Server server = null;

        // 循环的边界server是否null
        while (server == null) {
            // 获取当前引用,以防它从另一个线程更改
            List currentWeights = accumulatedWeights;
            // 当前线程被中断,返回null
            if (Thread.interrupted()) {
                return null;
            }
            // 从负载均衡器获取所有服务
            List allList = lb.getAllServers();
            // 服务总数
            int serverCount = allList.size();
            // 服务总数为0,返回null
            if (serverCount == 0) {
                return null;
            }
            // 初始化服务索引由0开始
            int serverIndex = 0;

            // 列表中的最后一个是所有权重的总和
            double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1);
            // 尚未命中任何服务器,总重量未初始化为回退以使用循环
            if (maxTotalWeight < 0.001d || serverCount != currentWeights.size()) {
                // 当没有为服务器收集足够的统计信息时,该规则将回退到使用 RoundRobinRule。
                server =  super.choose(getLoadBalancer(), key);
                if(server == null) {
                    return server;
                }
            } else {
                // 生成一个从0(包含)到 maxTotalWeight (独占)之间的随机权重
                double randomWeight = random.nextDouble() * maxTotalWeight;
                // 根据随机索引选择服务器索引
                int n = 0;
                // 遍历权重列表
                for (Double d : currentWeights) {
                    // 如果当前权重d大于等于随机权重,则取该索引n,break跳出循环体
                    if (d >= randomWeight) {
                        serverIndex = n;
                        break;
                    } else {
                        // 否则继续选取
                        n++;
                    }
                }
                // 根据计算的索引从服务列表获取一个服务
                server = allList.get(serverIndex);
            }

            // 服务为空继续选取
            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            // 服务可用返回结束方法体
            if (server.isAlive()) {
                return (server);
            }

            // Next.服务不可用继续选取
            server = null;
        }
        return server;
    }

主要逻辑:

  1. 预校验,以防空指针异常。负载均衡器空则返回null
  2. 当前线程被中断,返回null
  3. 从负载均衡器获取所有服务,服务总数为0,返回null
  4. 列表中的最后一个是所有权重的总和maxTotalWeight,尚未命中任何服务器,总重量未初始化为回退以使用RoundRobinRule循环
  5. 生成一个从0(包含)到 maxTotalWeight (独占)之间的随机权重;遍历权重列表,如果当前权重d大于等于随机权重,则取该索引n(整形),break跳出循环体;否则继续;根据计算得出的索引从服务列表获取一个服务
  6. 如果服务为空继续选取
  7. 如果选取的服务可用返回结束方法体
  8. 如果选取到的服务不可用,则置空null再次选取

你可能感兴趣的:(Spring家族及微服务系列,spring,cloud,微服务,ribbon)