Context
Context
是对资源操作时的上下文环境, 同一个线程中通过thread local
进行传里, context
中维护了currentEntry
, 同一个线程中可以多次entry/exit
, 所以entry
同样维护了一个树状结构:
Entry
调用点,这里面保存着和某一特定资源的某一次调用相关的信息,比如这次调用的起始执行时间,结束时间,是否抛出异常,调用链路中的父子关系,该调用点对应资源的处理链(ProcessorSlotChain
),还保存了该调用点所对应的资源统计节点
Slot
slot
是规则处理器,通过责任链模式进行串联处理。
1.3、核心类图
sentinel主要通过Node实现结点维护统计信息、通过LeapArray实现滑动窗口算法。
2、Sentinel 流程
2.1、规则配置
应用通过订阅监听模式实现各种监听器,监听然后对应不同配置的进行更新,图中列出了几个主要监听器,如: 规则配置、基本配置、集群配置等。
2.2、规则验证
应用调用entry入口即启动了规则验证流程,主要步骤为获取context, 构建并执行slot chain,如果违反规则纪录失败计数,清理contxt;反之,则纪录成功计数,返回验证成功。
3、Sentinel 核心
3.1、限流算法
我们所熟知的限流算法包括: 1. 计数器 2. 固定窗口 3. 滑动窗口 4.漏桶 5.令牌桶等。
-
1、
计数器
,维护一个计数器,访问进入计数器+1,如果超过阙值则拒绝、访问结束计数器-1。 -
2、
固定窗口
,给定一个时间窗口,维护一个计数器、如果窗口内访问次数大于阙值则拒绝。 -
3、
滑动窗口
,固定窗口有临界问题,所以提出滑动窗口思想,将一个大的时间窗口切分成更细粒度的子窗口,每个子窗口单独统计,每过一个子窗口的时间,就向右滑动一个子窗口。 -
4、
漏桶
,滑动窗口不能解决平滑度问题,为了解决平滑度问题,提出漏桶算法进行流速控制。 -
5、
令牌桶
: 跟漏桶算法相反,以很定的速度产生令牌,桶满则丢弃,请求访问时从桶里后去令牌,如果能够获取,则通过否则拒绝。
Sentinel
中主要使用了计数器
、滑动窗口
、漏桶
等限流思想
。比如对于线程数的流控
,就是直接使用计数器
进行限制,对于QPS的限制则
使用了滑动窗口
的思想,滑动窗口
也是Sentinel
的主要流控实现方式。
3.2、滑动窗口
Sentinel
支持秒级的窗口,默认窗口大小1秒
,假设分为5个子窗口
,则每个子窗口的大小为200ms
, 如上图,每个格子有个索引timeId
, 那窗口是如何进行滑动呢?算法的关键实现就是在前面类图提到过的currentWindow()
方法:
-
1、根据当前时间戳计算timeId
-
2、根据timdId获取当前子窗口
-
3、如果子窗口不存在,则创建子窗口
-
4、如果子窗口已经存在,则根据当前时间计算开始开始时间,对比子窗口的开始时间,如果开始时间一致,则说明此子窗口即当前窗口,如果大于子窗口时间,则需要滑动
-
5、滑动其实就是resetWidow, 包括reset windowStartTime, window中的统计信息等,如上图所示
3.3、优先级
优先级的目的是为了区分正常流量
和其他低优先级流量
,比如压测流量
。对于正常流量
,如果超过阙值
,不想直接拒绝,尝试借用将来窗口的指标,如果借用成功,则请求通过,如果借用失败,则拒绝。当时间窗口
真正走到将来的时间点时,需要将借用窗口的计数器
恢复设置。
那如何当前请求是否可以借用呢?如下图,主要维护一个借用时间阙值,计算一秒窗口内的总数有没有超过QPS,如果没有超过看借用时间是否超过借用阙值:
3.4、热点参数
一般资源的定义需要可枚举的,不能爆炸式的,假如对每个userId进行限流,这样会导致sentinel的存储空间爆炸式增长,最后导致OOM. 对于这种细粒度的参数限流,sentinel提供了热点参数方案。
如图:
热点参数就是在正常统计信息而外增加了参数统计,一样支持线程数和QPS两种限流方式,对于线程数,每个参数索引维护了一个LRU cache; 对于QPS,每个参数索引、每个子窗口、每个统计纬度维护了一个LRU cache, cache中维护了各种计数。通过LRU cache可以避免内存爆炸, 达到热点参数限流的目的。