sentinel滑动窗口及熔断限流实现原理
滑动窗口机制通过将一个时间窗口拆分为多个时间槽,每个时间槽都有独立的计数器,时间槽划分的越细,那么滑动窗口的滚动就越平滑,统计就会越精确。
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
的执行顺序是固定好的。但并不是绝对不能改变的。Sentinel
将ProcessorSlot
作为 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 或异常数据进行熔断判断。