前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Sentinel基于滑动窗口的流量统计【源码笔记】

Sentinel基于滑动窗口的流量统计【源码笔记】

作者头像
瓜农老梁
发布2019-10-09 14:42:32
1.7K0
发布2019-10-09 14:42:32
举报
文章被收录于专栏:瓜农老梁瓜农老梁
一、问题思考

1.StatisticSlot主要职责是流量统计(为后面插槽链的流控和降级做准备),这些流量是如何统计出来的? 2.统计出来的流量在流控中是如何使用的?

二、StatisticSlot请求流量统计

以StatisticSlot为切入点,追踪请求流量统计调用链。

1.入口
代码语言:javascript
复制
// 代码坐标:StatisticSlot#entry
public void entry(){
    // 触发向下插槽执行
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
    // 请求通过递增线程数量
    node.increaseThreadNum();
    // 请求通过递增请求数量
    node.addPassRequest(count);
    ...
}

两个维度进行统计:一个是统计线程数通过StatisticNode#curThreadNum递增来完成,curThreadNum为AtomicInteger类型;另外一个是递增请求数量。

2.请求流量追踪
代码语言:javascript
复制
// 代码坐标:StatisticNode#addPassRequest
public void addPassRequest(int count) {
    rollingCounterInSecond.addPass(count);
    rollingCounterInMinute.addPass(count);
}

其中有两个成员变量rollingCounterInSecond和rollingCounterInMinute负责对请求流量进行统计。

代码语言:javascript
复制
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
        IntervalProperty.INTERVAL);

ArrayMetric接受两个参数:int sampleCount采样窗口数量默认为2;int intervalInMs统计区间默认为1秒;rollingCounterInSecond表示近1秒的请求流量统计。

代码语言:javascript
复制
 private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);

rollingCounterInMinute表示近1分钟的请求流量统计;ArrayMetric入参分别为60个采样窗口数量和1分钟统计区间。

代码语言:javascript
复制
// 代码坐标:ArrayMetric#addPass
public void addPass(int count) {
    WindowWrap<MetricBucket> wrap = data.currentWindow();
    wrap.value().addPass(count);
}

在这段代码中通过滑动窗口进行请求流量统计,详见下文分析。

三、滑动窗口流量统计
1.滑动窗口示意图

基于滑动窗口的限流,由于开始时间是浮动的,高峰流量不会出现在固定周期的开始时间段,使得整体负载趋于均衡。

时间窗口大小和统计区间可以自定义,以默认进行分析。如图所示: 统计区间:intervalInMs为1秒 滑动时间窗口大小:windowLengthInMs为500毫秒 采样窗口数量:默认2个 sampleCount=intervalInMs/windowLengthInMs=2 采样数据数组:数据大小默认2 使用数组来封装(array)数组大小与采样窗口数量相同 采样数据数组下标:

idx=(int)(timeId % array.length())

long timeId = timeMillis / windowLengthInMs

小结:随着时间(time)的向前推进,采样数据下标idx也在不断切换(由于2个窗口在0和1之间切换);根据下标进而获取采样数据,通过比较当前时间与采样数据中的窗口开始时间,确定当前时间是否属于该滑动窗口以及该采样数据的窗口是否过期;通过不断重置与更新采样数据的值实现统计数据的动态变化。

2.代码分析

通过分析代码详细解释滑动窗口示意图。

接上面代码

代码语言:javascript
复制
// 代码坐标:ArrayMetric#addPass
public void addPass(int count) {
    WindowWrap<MetricBucket> wrap = data.currentWindow();
    wrap.value().addPass(count);
}

注:addPass通过CAS对数据进行递增UNSAFE.compareAndSwapLong,详见:LongAdder#add(long x)。

根据给定的时间戳获取对应的滑动窗口数据。

代码语言:javascript
复制
// 代码坐标:LeapArray#currentWindow
public WindowWrap<T> currentWindow(long timeMillis) {
    // 计算数组array对应的下标
    int idx = calculateTimeIdx(timeMillis);
    // 计算窗口开始时间(剔除余数)
    long windowStart = calculateWindowStart(timeMillis);
    // 一直运行直到返回窗口
    while (true) {
        // 获取数据对应的采样数据
        WindowWrap<T> old = array.get(idx);
        // 没有窗口统计数据
        if (old == null) {
            // 构造窗口
            WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            return window;  
        // 处于同一个时间窗口
        } else if (windowStart == old.windowStart()) {
            return old;
        // 时间已经推进到下一个窗口原来窗口过期
        } else if (windowStart > old.windowStart()) {
            // 重置时间窗口
            return resetWindowTo(old, windowStart);   
        }
    }
}
3.获取滑动窗口流程图
4.数据打印测试

测试代码

代码语言:javascript
复制
@Test
public void testGetWindow() throws InterruptedException {
while(true){
    BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
    long time = TimeUtil.currentTimeMillis();
    WindowWrap<MetricBucket> window = leapArray.currentWindow(time);
    System.out.println("输入时间time:"+time);
    System.out.println("统计区间intervalInMs:"+intervalInMs);
    System.out.println("滑动窗口长度windowLengthInMs:" + window.windowLength());
    System.out.println("采样窗口数量sampleCount:" + sampleCount);
    System.out.println("当前时间对应窗口的开始时间windowStart:"+window.windowStart());
    System.out.println("---------------------------------------");
    Thread.sleep(new Random().nextInt(2000) );
}
}

结果输出:

代码语言:javascript
复制
采样数据下标idx:0
输入时间time:1570325921114
统计区间intervalInMs:1000
滑动窗口长度windowLengthInMs:500
采样窗口数量sampleCount:2
当前时间对应窗口的开始时间windowStart:1570325921000
---------------------------------------
采样数据下标idx:1
输入时间time:1570325921916
统计区间intervalInMs:1000
滑动窗口长度windowLengthInMs:500
采样窗口数量sampleCount:2
当前时间对应窗口的开始时间windowStart:1570325921500
---------------------------------------
采样数据下标idx:1
输入时间time:1570325923596
统计区间intervalInMs:1000
滑动窗口长度windowLengthInMs:500
采样窗口数量sampleCount:2
当前时间对应窗口的开始时间windowStart:1570325923500
---------------------------------------
采样数据下标idx:0
输入时间time:1570325925041
统计区间intervalInMs:1000
滑动窗口长度windowLengthInMs:500
采样窗口数量sampleCount:2
当前时间对应窗口的开始时间windowStart:1570325925000
四、滑动窗口流量数据使用

FlowSlot职责在于比较流控规则与已统计的流量,未达到阀值则放行;达到阀值则触发流控,以此为例跟踪下如何使用滑动窗口统计的流量。

1.代码调用链
代码语言:javascript
复制
1.FlowSlot#entry
2.FlowSlot#checkFlow
3.FlowRuleChecker#checkFlow
4.FlowRuleChecker#passLocalCheck
5.TrafficShapingController#canPass
6.DefaultController#canPass
2.代码分析
代码语言:javascript
复制
// 流控判断
public boolean canPass(Node node, int acquireCount, boolean prioritized){
    // 获取已使用的tokens(线程数或者QPS)
    int curCount = avgUsedTokens(node);
    // acquireCount入参为1
    // 超过阀值触发流控
    if (curCount + acquireCount > count) {
        ...
        return false;
    }
    return true;
}

流控判断代码中通过avgUsedTokens获取当前流量的平均值curCount,然后与阀值count进行比较。

代码语言:javascript
复制
// 以请求流量为例跟踪
private int avgUsedTokens(Node node) {
    if (node == null) {
        return DEFAULT_AVG_USED_TOKENS;
    }
    return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
}

public double passQps() {
    return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec();
}

rollingCounterInSecond.getWindowIntervalInSec()为滑动窗口统计区间默认为1秒,rollingCounterInSecond.pass()代码如下:

代码语言:javascript
复制
public long pass() {
    data.currentWindow();
    long pass = 0;
    // 获取所有有效的滑动窗口
    List<MetricBucket> list = data.values();
    for (MetricBucket window : list) {
        pass += window.pass();
    }
    return pass;
}

小结:ArrayMetric#pass通过获取当前所有有效的滑动窗口,计算每个窗口统计的流量(window.pass)之和即为该统计区间的总流量。统计区间的总流量(默认2个滑动窗口流量之和)除以统计区间时间(1秒)即为该统计区间的平均流量。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 瓜农老梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、问题思考
  • 二、StatisticSlot请求流量统计
  • 1.入口
  • 2.请求流量追踪
  • 三、滑动窗口流量统计
  • 1.滑动窗口示意图
  • 2.代码分析
  • 3.获取滑动窗口流程图
  • 4.数据打印测试
  • 四、滑动窗口流量数据使用
  • 1.代码调用链
  • 2.代码分析
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档