【你好Ribbon】七:Ribbon负载均衡服务器指标管理器 (二)ServerStats

每日一句

任何事情,你想做就有方法,不想做就有借口,你有选择的自由,也有承担后果的义务。不要明天再努力,只有努力过完今天的人才有明天!

前言

上篇文章简单的聊了一下netflix的多维度统计指标的模块netflix-statistics,当然这个功能是属于netflix-commons项目的一个模块,大家可以在github上去获取源码。基础类说完之后 我们就回到Ribbon 说一下Ribbon的一个非常基础的功能 服务器指标管理器。ServerStats

一个Demo

 		Server server = new Server("stonse", 80);
        ServerStats stats = new ServerStats();
        stats.initialize(server);
        //缓冲区大小为200 默认是60000  
        //这里设置的缓存区是不是就和前面说的 DataBuffer的缓存区有关了?
        stats.setBufferSize(200);
        //这里的发布 是不是就和我们上一篇文章里面说的那个发布器有关了?
        //由于默认值一分钟发布一次 这里设置3s搜集一次数据
        stats.setPublishInterval(3000);
        /**
         * 在一个异步的任务中开启10个线程 不停的模拟请求
         */
        CompletableFuture.runAsync(()->{
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    while (true){
                        int responseTime = new Random().nextInt(2000);
                        stats.incrementActiveRequestsCount();
                        stats.incrementNumRequests();
                        stats.incrementOpenConnectionsCount();
                        try {
                            Thread.sleep(responseTime);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (responseTime > 1000){
                            stats.incrementSuccessiveConnectionFailureCount();
                            stats.addToFailureCount();
                        }
                        stats.noteResponseTime(responseTime);
                        stats.decrementActiveRequestsCount();
                        stats.decrementOpenConnectionsCount();
                    }
                }).start();
            }
        }).thenRun(()->{
        //在主线程中 再开启一个线程 来获取运行时的一些指标数据 
                executor.scheduleAtFixedRate(()->{
                    System.out.println("=================时间:"+stats.getResponseTimePercentileTime()+"=====================");
                    System.out.println("总请求数:" + stats.getTotalRequestsCount());
                    System.out.println("时间窗口内(5min)的请求数量:" + stats.getMeasuredRequestsCount()); //这个值5分钟之后才有值 时间窗口是5分钟
                    System.out.println("一个时间窗口(1s)内请求失败数量:" + stats.getFailureCount());
                    System.out.println("当前活跃请求数量:" + stats.getActiveRequestsCount());
                    System.out.println("打开的连接数量:" + stats.getOpenConnectionsCount());
                    System.out.println("平均响应时间:" + stats.getResponseTimeAvg());
                    System.out.println("最大响应时间:" + stats.getResponseTimeMax());
                    System.out.println("最小响应时间:" + stats.getResponseTimeMin());
                }, 3,3,TimeUnit.SECONDS);
            }
        ).thenRun(()->{
        //这里hold住主线程
            try {
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

这个demo的代码比较多。但是很清晰 为了结构分明 所以用了CompletableFuture 这个类是JUC里面的 大神Doug Lea编写的。 简化了异步编程的 并且提供了函数式编程的支持。
这里思路很清晰
runAsync里面是模拟请求,第一个thenRun里面监控打印 ,第二个thenRun里面是hold住主线程。
输出:

=================时间:Tue Nov 24 23:05:43 CST 2020=====================
总请求数:36
时间窗口内(5min)的请求数量:0
一个时间窗口(1s)内请求失败数量:6
当前活跃请求数量:10
打开的连接数量:10
平均响应时间:928.2307692307693
最大响应时间:1906.0
最小响应时间:76.0
=================时间:Tue Nov 24 23:05:46 CST 2020=====================
总请求数:65
时间窗口内(5min)的请求数量:0
一个时间窗口(1s)内请求失败数量:0
当前活跃请求数量:10
打开的连接数量:10
平均响应时间:921.5090909090909
最大响应时间:1916.0
最小响应时间:76.0

通过上面的小示例我们可以拿到一些运行过程中的指标数据。通过上一篇文章我们知道 之所以能拿到这些数据 其核心就是netflix-statistics。我们下面就来看看ServerStats源码是如何工作的。

源码

构造方法

   public ServerStats() {
        connectionFailureThreshold = new CachedDynamicIntProperty(
                "niws.loadbalancer.default.connectionFailureCountThreshold", 3);        
        circuitTrippedTimeoutFactor = new CachedDynamicIntProperty(
                "niws.loadbalancer.default.circuitTripTimeoutFactorSeconds", 10);
        maxCircuitTrippedTimeout = new CachedDynamicIntProperty(
                "niws.loadbalancer.default.circuitTripMaxTimeoutSeconds", 30);
    }
  • connectionFailureThreshold:链接失败阈值 默认为3 超过就熔断 默认值由niws.loadbalancer.default.connectionFailureCountThreshold来指定
  • circuitTrippedTimeoutFactor:断路器超时因子 默认10s 默认值由niws.loadbalancer.default.circuitTripTimeoutFactorSeconds属性指定
  • maxCircuitTrippedTimeout断路器最大超时秒数 默认30s 默认值由niws.loadbalancer.default.circuitTripMaxTimeoutSeconds来指定

构造方法初始化了以上的三个值。都是和断路器有关。

initialize方法

	public void initialize(Server server) {
	//MeasuredRate 表示一段时间内的统计
	//serverFailureCounts 维护了在一段时间内的失败次数 默认为1s
        serverFailureCounts = new MeasuredRate(failureCountSlidingWindowInterval);
        //维护了一个时间窗口内的请求数量 5分钟
        requestCountInWindow = new MeasuredRate(300000L);
        //下面的就比较熟悉了 DataPublisher 数据发布器 和DataDistribution数据统计器
        if (publisher == null) {
            //实例化数据累加器 默认缓存 60000
            dataDist = new DataDistribution(getBufferSize(), PERCENTS);
            //一分钟发布一次
            publisher = new DataPublisher(dataDist, getPublishIntervalMillis());
            publisher.start();
        }
        this.server = server;
    }
  • serverFailureCounts 存储了一段时间内的失败次数 默认1s
  • requestCountInWindow 存储一段时间内的请求数量 默认5分钟
  • dataDist 初始化一个数据累加器 默认缓存是5w上一篇文章介绍过
  • publisher 初始化一个数据发布器 默认为1分钟发布一次

初始化完这些值之后就可以获取指标值了!

ServerStat 属性

除了上面初始化的属性 再看看其他值得注意的属性!

    //活跃请求超时时间600s
    private static final DynamicIntProperty activeRequestsCountTimeout = DynamicPropertyFactory.getInstance().getIntProperty("niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds", 60 * 10);
    //初始化计算中位数的数组 该数组里面的值是固定的 参考Percent枚举值
    private static final double[] PERCENTS = makePercentValues();
    //定时发布数据 默认1分钟一次
    private DataPublisher publisher = null;
    //仅仅需要持续累加数据 提供最大值最小值 平均值等所以是Distribution类型
    private final Distribution responseTimeDist = new Distribution(); 
    //当前的服务
    Server server;
    //连续请求异常数量 发生在Retry重试期间。在重试期间 但凡有一次成功了 就会把此参数设置为0.
    @VisibleForTesting
    AtomicInteger successiveConnectionFailureCount = new AtomicInteger(0);
    //活跃请求的数量(正在请求的数量 它能反应server的负载 压力)
    @VisibleForTesting
    AtomicInteger activeRequestsCount = new AtomicInteger(0);
    //最后一次失败的时间戳
    private volatile long lastConnectionFailedTimestamp;
    //activeRequestsCount 的值最后变化的时间戳
    private volatile long lastActiveRequestsCountChangeTimestamp;
    //断路器断开的总时长  连续失败大于等于3次 增加20-30秒
    private AtomicLong totalCircuitBreakerBlackOutPeriod = new AtomicLong(0);
    //最后访问时间戳
    private volatile long lastAccessedTimestamp;
    //第一次连接的时间戳 只会记录首次请求进来的时间
    private volatile long firstConnectionTimestamp = 0;

可以看到除了初始化的几个值以外 其他的属性都很简单。

getActiveRequestsCount 获取活跃的请求数

 public int getActiveRequestsCount(long currentTime) {
        int count = activeRequestsCount.get();
        if (count == 0) {
            return 0;
        } else if (currentTime - lastActiveRequestsCountChangeTimestamp > activeRequestsCountTimeout.get() * 1000 || count < 0) {
         //注意这个条件分支 如果最后一个请求 的超时时间已过 那么久吧当前负载 强制归0
            activeRequestsCount.set(0);
            return 0;            
        } else {
            return count;
        }
    }

getCircuitBreakerBlackoutPeriod 获取中断的时间值

private long getCircuitBreakerBlackoutPeriod() {
        //获取一端时间内请求的失败数量
        int failureCount = successiveConnectionFailureCount.get();
        //获取连续失败阈值 可在配置文件配置 默认是 3
        int threshold = connectionFailureThreshold.get();
        //如果没有达到阈值 返回0
        if (failureCount < threshold) {
            return 0;
        }
        //如果连续失败大于19次 返回16 否则返回差值 
        int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold);
        //通过上面计算出来的值 乘上超时因子默认是10 获得熔断时间 
        int blackOutSeconds = (1 << diff) * circuitTrippedTimeoutFactor.get();
        //如果上面计算的时间大于 默认的断路器超时最大时间 强制赋值为最大超时 默认30s
        //所以 在默认的情况下 连续失败 5次 就达到最大超时时间了 不管是5次还是100次都是熔断30s
        //所以上面的表达式 (1 << diff) * circuitTrippedTimeoutFactor.get() 是否有问题?
        if (blackOutSeconds > maxCircuitTrippedTimeout.get()) {
            blackOutSeconds = maxCircuitTrippedTimeout.get();
        }
        return blackOutSeconds * 1000L;
    }

先来清楚几个配置:
connectionFailureThreshold: 一段时间内失败的阈值 默认是3 可以理解为超过这个阈值就会熔断
circuitTrippedTimeoutFactor:超时因子 默认是10
maxCircuitTrippedTimeout:最长的超时时间 默认30s

我们再来看看上面的计算是否有问题:
例如当前server已连续失败5次 那么
int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold); 计算出来的值为 2
通过 表达式 (1 << diff) * circuitTrippedTimeoutFactor.get(); 计算出来的值是 40
那么最终40是要大于最大的超时时间30s的所有要返回30s。
大家看到这应该发现问题了。其实按照默认的设置 连续失败5次就会返回最高的值 它那个>大于16 是什么意思?如果是这样的话熔断的秒数只有两种可能 20s30s
所以我想这个结果并不是设计者要的。

好了 ServerStats我们就到这。内容是在)netflix-statistics基础之上。搞清楚了netflix-statistics 再来读这个类的源码还是很easy的!

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值