Sentinel 滑动时间窗口源码分析

前言:

Sentinel 的一个重要功能就是限流,对于限流来说有多种的限流算法,比如滑动时间窗口算法、漏桶算法、令牌桶算法等,Sentinel 对这几种算法都有具体的实现,如果我们对某一个资源设置了一个流控规则,并且选择的流控模式是“快速失败”,那么 Sentinel 就会采用滑动时间窗口算法来作为该资源的限流算法,本篇我们来分析 Sentinel 中滑动时间窗口算法的实现。

Sentinel 系列文章传送门:

Sentinel 初步认识及使用

Sentinel 核心概念和工作流程详解

Spring Cloud 整合 Nacos、Sentinel、OpenFigen 实战【微服务熔断降级实战】

Sentinel 源码分析入门【Entry、Chain、Context】

Sentine 源码分析之–NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot

Sentine 源码分析之–AuthoritySlot、SystemSlot、GatewayFlowSlot

Sentine 源码分析之–ParamFlowSlot

Sentine 源码分析之–FlowSlot

滑动窗口的原理

滑动窗口是一种常用的算法,用于统计一段时间内的事件或数据点,在限流场景中,滑动窗口将时间窗口分割成多个小的时间片段(通常称为桶,也可以叫做样本窗口),每个时间片段独立统计,随着时间的推移,最旧的时间片段的数据会被新的时间片段替换,形成“滑动”的效果,在具体实现上,‌滑动时间窗算法可以通过多种数据结构来实现,‌例如使用环形数组、‌哈希表等,可以使用一个环形数组来存储时间窗口内的数据点,‌数组的大小等于时间窗口的大小,每当有新的数据点进入时,‌旧的对应时间点的数据将被覆盖,‌从而实现滑动时间窗的效果,‌此外,‌也可以使用哈希表结构来实现滑动时间窗口,其中键为时间点,‌值为该时间点的数据值或变化量。‌

滑动窗口的优点

  • 实现更细粒度的时间控制,与固定窗口(整个时间窗口只统计一次)相比,滑动窗口通过连续滑动减少了窗口切换时的流量突变,避免了请求在窗口刚开始时因为累积的计数而被误判为超限。
  • 减少突发流量对系统的影响,保证服务的稳定性和可靠性,在实际应用中,流量往往呈现出突发性特征,如果使用固定窗口算法,在窗口重置的瞬间可能会接受大量请求(时间窗口的起始点聚集大量流量),造成短时间内的服务压力,滑动窗口可以更均匀、更细粒度的控制每个时间片段内的流量,从而降低了因突发流量导致的导致的系统压力。
  • 提高系统响应的实时性,滑动窗口提供了更实时的流量数据,系统能够基于最实时的流量情况做出响应,这对于需要快速适应流量变化的在线服务尤其重要,可以即时调整资源分配和访问策略。

Sentinel 滑动时间窗口的实现

Sentinel 官网的图就很清楚的告诉了我们 Sentinel 使用环形数组实现滑动窗口,下图中的右上角就是滑动窗口的示意图,是 StatisticSlot 的具体实现,底层采用的是 LeapArray 来统计实时的秒级指标数据,可以很好地支撑写多于读的高并发场景。

在这里插入图片描述

滑动窗口的核心数据结构

  • ArrayMetric:滑动窗口核心实现类。
  • LeapArray:滑动窗口顶层数据结构,主要存储窗口数据。
  • WindowWrap:每一个滑动窗口的包装类,其内部的数据结构用 MetricBucket 表示。
  • MetricBucket:指标桶,例如通过数量、阻塞数量、异常数量、成功数量、响应时间,已通过未来配额(抢占下一个滑动窗口的数量)。
  • MetricEvent:指标类型,例如通过数量、阻塞数量、异常数量、成功数量、响应时间等。

ArrayMetric 构造方法源码解析

ArrayMetric 是滑动窗口的入口类,实现了 Metric 接口,该接口主要定义一个滑动窗口中成功的数量、异常数量、阻塞数量,TPS、响应时间等,ArrayMetric 提供了两个构造方法,两个构造方法的区别是在于当前时间窗口达到限制之后,是否可以抢占下一个时间窗口,具体逻辑如下:

  • intervalInMs:滑动窗口的总时间,例如 1 分钟、1 秒中。
  • sampleCount:在一个滑动窗口的总时间中的抽样的个数,默认为 2,即一个滑动窗口的总时间包含两个相等的区间,一个区间就是一个窗口。
  • enableOccupy:是否允许抢占,即当前滑动窗口已经达到限制后,是否可以占用下一个时间窗口的容量。
public class ArrayMetric implements Metric {
   

    private final LeapArray<MetricBucket> data;

    //com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric#ArrayMetric(int, int)
    public ArrayMetric(int sampleCount, int intervalInMs) {
   
        //默认是可抢占的
        this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
    }

    //com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric#ArrayMetric(int, int, boolean)
    public ArrayMetric(int sampleCount, int intervalInMs, boolean enableOccupy) {
   
        //当前时间窗口容量满了 是否可抢占时间窗口
        if (enableOccupy) {
   
            //可抢占
            this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
        } else {
   
            //不可抢占
            this.data = new BucketLeapArray(sampleCount, intervalInMs);
        }
    }
}

LeapArray 源码分析

LeapArray 用来存储滑动窗口数据的,也就是所谓的环形数组,具体成员变量如下:

  • windowLengthInMs:样本窗口的时间间隔,单位秒。
  • sampleCount:样本窗口数量。
  • intervalInMs:一个滑动窗口跨越的时间长度,也就是总时间窗口。
  • array:样本窗口的集合,使用 AtomicReferenceArray 保证原子性。
public abstract class LeapArray<T> {
   

    //样本窗口的时间间隔 单位秒
    protected int windowLengthInMs;
    //样本窗口数量
    protected int sampleCount;
    //毫秒为单位 一个滑动窗口跨越的时间长度 也就是总时间窗口
    protected int intervalInMs;
    //样本窗口的集合 使用 AtomicReferenceArray 保证原子性
    protected final AtomicReferenceArray<WindowWrap<T>> array;

    /**
     * The conditional (predicate) update lock is used only when current bucket is deprecated.
     */
    private final ReentrantLock updateLock = new ReentrantLock();

    /**
     * The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}.
     *
     * @param sampleCount  bucket count of the sliding window
     * @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds
     */
    public LeapArray(int sampleCount, int intervalInMs) {
   
        AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount);
        AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive");
        AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");

        this.windowLengthInMs = intervalInMs / sampleCount;
        this.intervalInMs = intervalInMs;
        this.sampleCount 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值