sentinel滑动窗口及熔断限流实现原理

发布于:2025-07-04 ⋅ 阅读:(16) ⋅ 点赞:(0)

滑动窗口机制通过将一个时间窗口拆分为多个时间槽,每个时间槽都有独立的计数器,时间槽划分的越细,那么滑动窗口的滚动就越平滑,统计就会越精确。

Sentinel滑动窗口的核心是抽象类LeapArray:

public abstract class LeapArray<T> {
    //滑动窗口大小
    protected int windowLengthInMs;
    //一个滑动窗口中时间槽的数量
    protected int sampleCount;
    //时间槽数组,存储每个槽的统计数据,该数组大小等于sampleCount,随着窗口推移,过期的时间槽被重置,重置现有对象可以减少 GC 压力
    protected final AtomicReferenceArray<WindowWrap<T>> array;
    private final ReentrantLock updateLock = new ReentrantLock();

    //根据当前时间戳获取当前时间槽
    public WindowWrap<T> currentWindow(long timeMillis) {
        if (timeMillis < 0) {
            return null;
        }
        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));
                if (array.compareAndSet(idx, null, window)) {
                    return window;
                } else {
                    Thread.yield();
                }
            } else if (windowStart == old.windowStart()) {
                return old;
            } else if (windowStart > old.windowStart()) {
                if (updateLock.tryLock()) {
                    try {
                        return resetWindowTo(old, windowStart);
                    } finally {
                        updateLock.unlock();
                    }
                } else {
                    Thread.yield();
                }
            } else if (windowStart < old.windowStart()) {
                return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            }
        }
    }

    private int calculateTimeIdx(long timeMillis) {
        long timeId = timeMillis / windowLengthInMs;
        return (int)(timeId % array.length());
    }

    protected long calculateWindowStart(long timeMillis) {
        return timeMillis - timeMillis % windowLengthInMs;
    }

    public abstract T newEmptyBucket(long timeMillis);
    protected abstract WindowWrap<T> resetWindowTo(WindowWrap<T> windowWrap, long startTime);

    //......

}

WindowWrap类包含以下属性:

  • long windowStart 时间槽开始时间,等于当前时间戳 - 当前时间戳 %  窗口时长windowLengthInMs,用于判断时间槽是否过期
  • T value 统计数据对象,范型类型,根据统计数据的类型,初始化及重置统计对象有不同的实现

获取当前时间槽首先通过calculateTimeIdx方法获取当前时间槽在array中的下标,检查该槽是否存在或过期,不存在则初始化槽对象,过期则重置槽对象中的统计对象。

以Sentinel异常数及异常比例断路器ExceptionCircuitBreaker为例,该类继承LeapArray<SimpleErrorCounter>,其中SimpleErrorCounter包含请求总量及异常数两个属性,每次处理完请求时都获取当前时间槽的SimpleErrorCounter对象,累加请求总量字段,请求异常时累加异常数字段,且当请求异常时,遍历时间槽数组array,统计所有时间槽的请求总量及异常数,计算异常数或异常比例是否达到熔断阈值。

只有当请求到达时才创建或重置时间槽,减少空窗口的资源占用。通过偏移量计算快速定位有效时间槽减少不必要的遍历。重用过期时间槽,减少内存消耗。

Sentinel责任链:

Sentinel熔断限流规则的执行是通过责任链模式(ProcessorSlotChain)实现的,每个 Slot 负责特定的功能(如限流、熔断、系统保护等),用户可以动态将规则注册到对应的Slot中。

Sentinel 核心 Slot 包括:

NodeSelectorSlot:构建调用链路的上下文(Context)和节点(Node),负责收集资源的路径,并将资源的调用路径以树状结构存储起来,可用于根据调用路径进行限流降级。

ClusterBuilderSlot:构建集群节点,用于统计全局数据

LogSlot:日志记录

StatisticSlot:主要功能是记录、统计不同维度的运行时指标监控信息。它会在请求进入和退出时,更新相关的统计数据,如记录请求通过数、阻塞数、异常数等,为后续的规则判断提供数据支持

AuthoritySlot:黑白名单控制

SystemSlot:系统保护(负载、CPU 使用率等)

FlowSlot:流量控制(限流)

DegradeSlot:熔断降级(基于异常比例 / 数量、RT)

Sentinel槽链中各Slot的执行顺序是固定好的。但并不是绝对不能改变的。SentinelProcessorSlot 作为 SPI 接口进行扩展,使得 SlotChain 具备了扩展能力。用户可以自定义Slot并编排Slot 间的顺序。

 Sentinel 提供如下的扩展点:

初始化过程扩展:提供 InitFunc SPI接口,可以添加自定义的一些初始化逻辑,如动态规则源注册等。
Slot/Slot Chain 扩展:用于给 Sentinel 功能链添加自定义的功能并自由编排。
指标统计扩展(StatisticSlot Callback):用于扩展 StatisticSlot 指标统计相关的逻辑。
Transport 扩展:提供 CommandHandler、CommandCenter 等接口,用于对心跳发送、监控 API Server 进行扩展。
集群流控扩展:可以方便地定制 token client/server 自定义实现,可参考对应文档
日志扩展:用于自定义 record log Logger,可用于对接 slf4j 等标准日志实现。

Sentinel规则注册:

熔断限流规则 由 FlowSlot 和 DegradeSlot 处理。用户动态配置的规则会通过 XxxRuleManager 注册到对应的 Slot 中。

// 注册限流规则
List<FlowRule> flowRules = new ArrayList<>();
flowRules.add(flowRule);
FlowRuleManager.loadRules(flowRules);

// 注册熔断规则
List<DegradeRule> degradeRules = new ArrayList<>();
degradeRules.add(degradeRule);
DegradeRuleManager.loadRules(degradeRules);

每个Slot被执行时会从对应的RuleManager 获取熔断规则,如DegradeSlot 会从 DegradeRuleManager 获取熔断规则,然后基于 StatisticSlot 统计的 RT 或异常数据进行熔断判断。