Sentinel限流算法总结

常见四种限流算法

摘录仅供学习使用,原文大部分内容来源于:Sentinel限流算法详解(硬啃)-优快云博客


1.固定窗口计数器


固定窗口,相比其他的限流算法,这应该是最简单的一种。
它简单地对一个固定的时间窗口内的请求数量进行计数,如果超过请求数量的阈值,将被直接丢弃。
这个简单的限流算法优缺点都很明显。优点的话就是简单,缺点举个例子来说。
比如我们下图中的黄色区域就是固定时间窗口,默认时间范围是 60 秒,限流数量是 100。
如图中括号内所示,前面一段时间都没有流量,刚好后面 30 秒内来了 100 个请求,此时因为没有超过限流阈值,所以请求全部通过,然后下一个窗口的 20 秒内同样通过了 100 个请求。
所以变相的相当于在这个括号的 40 秒的时间内就通过了 200 个请求,超过了我们限流的阈值。

2.滑动窗口计数器


为了优化这个问题,于是有了滑动窗口算法。顾名思义,滑动窗口就是时间窗口在随着时间推移不停地移动。
滑动窗口把一个固定时间窗口再继续拆分成 N 个小窗口,然后对每个小窗口分别进行计数,所有小窗口请求之和不能超过我们设定的限流阈值。
以下图举例子来说:假设我们的窗口拆分成了 3 个小窗口,小窗口都是 20 秒,同样基于上面的例子,当在第三个 20 秒的时候来了 100 个请求,可以通过。
然后时间窗口滑动,下一个 20 秒请求又来了 100 个请求,此时我们滑动窗口的 60 秒范围内请求数量肯定就超过 100 了啊,所以请求被拒绝。

3.漏桶( Leaky bucket)


漏桶算法名副其实,就是一个漏的桶,不管请求的数量有多少,最终都会以固定的出口流量大小匀速流出。如果请求的流量超过漏桶大小,那么超出的流量将会被丢弃。
也就是说流量流入的速度是不定的,但是流出的速度是恒定的。
这个和 MQ 削峰填谷的思想比较类似,在面对突然激增的流量的时候,通过漏桶算法可以做到匀速排队,固定速度限流。
漏桶算法的优势是匀速,匀速是优点也是缺点,很多人说漏桶不能处理突增流量,这个说法并不准确。
漏桶本来就应该是为了处理间歇性的突增流量。流量一下起来了,然后系统处理不过来,可以在空闲的时候去处理,防止了突增流量导致系统崩溃,保护了系统的稳定性。
但是换一个思路来想,其实这些突增的流量对于系统来说完全没有压力,你还在慢慢地匀速排队,其实是对系统性能的浪费。
所以,对于这种有场景来说,令牌桶算法比漏桶就更有优势。

4.令牌桶( Token bucket)


令牌桶算法是指系统以一定地速度往令牌桶里丢令牌。当一个请求过来的时候,会去令牌桶里申请一个令牌,如果能够获取到令牌,那么请求就可以正常进行,反之被丢弃。
现在的令牌桶算法,像 Guava 和 Sentinel 的实现都有冷启动 / 预热的方式。为了避免在流量激增

在实际环境中,如果想在初始阶段或隔一段时间系统再次被调用时,有一个预热的过程,即启动时生产令牌的速率慢一些,然后逐步加速,经过预热阶段后达到正常的生产速率,就像车辆的启动阶段,先从1档起步,逐渐加快,2档,3档一直到最快的6档。

RateLimiter.java提供了这种算法的实现。

public static RateLimiter create(
              double permitsPerSecond, 
              long warmupPeriod, 
              TimeUnit unit)

它返回的实现类是SmoothRateLimiter.java类里的内嵌类SmoothWarmingUp。

SmoothWarmingUp的实现原理是怎样的呢,我们看下面的图。


X轴代表令牌桶存储的token数,Y轴代表限流的速率,单位是一个token的生成速率。XY代表了坐标轴围成的矩形面积,也就是(token生产速率)(token数量),它有什么含义呢?是的,它代表了生产n个token的时长,这里使用了积分进行计算。右边的梯型面积表示了热身区的token生产总时长,左下边的长方形面积表示稳定期的token生产时长。我们用一个具体的例子来说明。

RateLimiter limiter = RateLimiter.create(2.0, 4000, MILLISECONDS, 3.0, stopwatch);

这里2.0代表QPS,4000代表warmup为4秒,3.0代表coldFactor即冷却因子,因此

stable interval=1/QPS=1/2=0.5秒=500毫秒,

coldinterval=coldFactor*stable interval=1500毫秒。

通过初始化函数设置令牌桶参数:

@Override
    void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
      double oldMaxPermits = maxPermits;
      double coldIntervalMicros = stableIntervalMicros * coldFactor;
      thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;
      maxPermits =
          thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);
      slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);
      if (oldMaxPermits == Double.POSITIVE_INFINITY) {
        // if we don't special-case this, we would get storedPermits == NaN, below
        storedPermits = 0.0;
      } else {
        storedPermits =
            (oldMaxPermits == 0.0)
                ? maxPermits // initial state is cold
                : storedPermits * maxPermits / oldMaxPermits;
      }
    }

其中thresholdPermits设置为

thresholdPermits = 0.5 *warmupPeriodMicros /

stableIntervalMicros;

即0.5*4000/500=4,

maxPermits=thresholdPermits +

2.0 * warmupPeriodMicros /

(stableIntervalMicros +coldIntervalMicros);

maxPermits也就是右边梯型的下底,而warmupPeriodMicros是梯形的面积,所以maxPermits是利用梯形的面积公式导出的。而slope表示斜线的斜率,该字段用在计算热身区的token生产时间。初始状态令牌桶处于冷却态,也就是说初始时令牌桶的生产速率最慢,storedPermits等于maxpermits。

当我们从上述的limiter获取令牌时,

for (int i = 0; i < 8; i++) {
            limiter.acquire(); // #1
     }

输出的事件为:"R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50"(备注:参考com.google.common.util.concurrent.RateLimiterTest中的testWarmUp方法)。

通过上篇文章我们知道,R代表延迟时间,例如:R0.00代表无延迟获取一个令牌,R1.38代表等待1.38秒获取令牌。

i=0,第一次循环,通过第一篇文章我们知道ratelimiter是寅吃卯粮的思想,因为nextFreeTicketMicros初始值为0,当前时钟也为0,因此延迟为0,生产该token的时间用于下次消费时补偿,也就是右边第一个小梯形的面积。

@Override
    long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
      double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
      long micros = 0;
      // measuring the integral on the right part of the function (the climbing line)
      if (availablePermitsAboveThreshold > 0.0) {
        double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
        // TODO(cpovirk): Figure out a good name for this variable.
        double length =
            permitsToTime(availablePermitsAboveThreshold)
                + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);
        micros = (long) (permitsAboveThresholdToTake * length / 2.0);
        permitsToTake -= permitsAboveThresholdToTake;
      }
      // measuring the integral on the left part of the function (the horizontal line)
      micros += (long) (stableIntervalMicros * permitsToTake);
      return micros;
}

private double permitsToTime(double permits) {
      return stableIntervalMicros + permits * slope;
    }

availablePermitsAboveThreshold 是储存的大于的threshold的token数量,第一次循环中,

availablePermitsAboveThreshold=maxpermits-threshold=4,

permitsAboveThresholdToTake是申请的token数量,为1,

private double permitsToTime(double permits)用来计算小梯形的上下底的高度,这里permitsToTime(availablePermitsAboveThreshold)获取了下底的高度,permitsToTime(availablePermitsAboveThreshold -

permitsAboveThresholdToTake)是上底的高度,所以

storedPermitsToWaitTime方法就是计算生产token的时间,在存储的token数量大于threshold,即availablePermitsAboveThreshold > 0.0时,计算右边小梯形的面积,这里计算的结果是1.38秒,反之,计算左边小长方形的面积。

i=1,2,3时,重复第二次循环的过程。延迟时间分别对应第2,3,4小梯形的面积,面积递减,分别是1.13,0.88,0.63。

i=4时,由于已经消费了4个token,总时长1.38, 1.13, 0.88, 0.63相加为4.02(由于存在舍入误差,其值不完全等于4),已经过了warmup期,所以进入稳定期,左边的小矩形面积为micros += (long) (stableIntervalMicros * permitsToTake),高为stableIntervalMicros,0.5秒,permitsToTake为1,即生产时长为0.5秒。

i=5,6,7,8重复上述过程。最后一次循环的生产时长需要后面的申请者偿还。

参考文章:

Sentinel限流算法详解(硬啃)-优快云博客

一文讲解在Sentinel中涉及到的限流算法_sentinel限流算法-优快云博客

令牌桶算法及实现(三) - 简书

<think>首先,用户的问题是关于Sentinel限流算法的原理和实现方式。我需要基于系统指令来构建我的回答。 关键系统指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题。 - 参考站内引用:[^1]到[^4]。 站内引用内容: - [^1]: 关于Sentinel源码的合集,提到了FlowSlot、限流算法等。 - [^2]: 比较了Guava的限流Sentinel,指出Sentinel支持更多策略。 - [^3]: 介绍Sentinel是阿里开源的流量控制组件。 - [^4]: 详细描述了Sentinel使用滑动窗口算法来统计流量数据。 我的回答需要覆盖Sentinel限流算法的原理和实现方式。基于引用[^4],核心原理是滑动窗口算法。 结构我的回答: 1. **介绍Sentinel限流**:简要说明什么是Sentinel及其作用。 2. **核心原理**:解释滑动窗口算法。 3. **实现方式**:描述如何实现,包括关键组件如FlowSlot。 4. **其他算法**:提到Sentinel支持的其他限流算法,如令牌桶、漏桶(基于引用[^2]和[^4])。 5. **引用**:在相关段落末尾添加引用标识。 6. **生成相关问题**:在最后添加相关问题部分。 使用数学表达式: - 在解释滑动窗口时,可能需要公式。例如,时间窗口的划分、请求计数等。 - 例如,窗口大小、阈值计算等。 确保LaTeX语法正确: - 行内:$...$ - 独立:$$...$$ 单独成段。 逐步解决问题: - 先解释原理,再讨论实现。 最终回答结构: 1. Sentinel限流概述 2. 核心算法:滑动窗口原理 3. 实现细节 4. 其他支持算法 5. 相关问题 生成相关问题: - 基于主题,如优化、应用场景等。 现在,草拟内容: - **介绍**:Sentinel是阿里开源的流量控制组件,用于保护服务稳定性[^3]。 - **原理**:核心是滑动窗口算法。时间被划分为固定长度的小窗口,每个窗口记录请求数。系统维护一个滑动窗口序列,统计一段时间内的总请求数。例如,如果窗口大小是1秒,滑动间隔是100毫秒,那么每100毫秒移动一次窗口,计算最近1秒内的请求数。 - 数学表达:设窗口大小为 $T$,滑动间隔为 $\Delta t$,请求数在时间 $t$ 为 $R(t)$。统计窗口 $[t - T, t]$ 内的请求数 $S(t) = \int_{t-T}^{t} R(\tau) d\tau$(离散化处理)。 - **实现方式**:在Sentinel源码中,FlowSlot负责限流逻辑。它使用滑动窗口来实时统计流量,并与预设的阈值比较。如果超过阈值,则触发限流[^1][^4]。 - **其他算法**:Sentinel还支持令牌桶、漏桶等算法,提供更灵活的限流策略[^2]。 - **相关问题**:列出3-5个相关的问题。 添加引用: - 在描述后自然地添加,如[...][^4]。 完整回答。</think>### Sentinel限流算法的原理和实现方式 Sentinel 是阿里开源的一款轻量级高可用流量控制组件,主要用于分布式服务架构中,通过流量控制、熔断降级等机制保护服务的稳定性[^3]。其核心限流功能基于高效的算法实现,下面我将逐步解释其原理和实现方式,确保内容清晰可靠。 #### 1. **限流算法的核心原理:滑动窗口算法** Sentinel限流核心采用滑动窗口算法(Sliding Window Algorithm),这是一种实时统计流量数据的机制。其原理是将时间划分为固定长度的小窗口(例如,每个窗口为1秒),并动态维护一个窗口序列。随着时间推移,新窗口不断加入,旧窗口逐步移出,系统实时计算滑动窗口内的总请求数。如果该请求数超过预设阈值,则触发限流。 - **数学建模**: - 设时间窗口大小为 $T$(单位:秒),滑动间隔为 $\Delta t$(例如,100毫秒)。 - 在任意时刻 $t$,系统统计最近 $T$ 时间内的请求数。离散化后,可表示为累计和: $$ S(t) = \sum_{k=0}^{N-1} R(t - k \cdot \Delta t) $$ 其中,$R(\tau)$ 表示在时间 $\tau$ 的请求计数,$N = T / \Delta t$ 是窗口数量。如果 $S(t) > Q$($Q$ 为预设阈值),则拒绝新请求[^4]。 - **优势**: - 实时性强:窗口滑动确保统计数据及时更新,避免固定时间窗口的“临界点问题”(例如,请求在窗口边界突发导致误判)。 - 精确控制:通过调整 $T$ 和 $\Delta t$,Sentinel 能适应不同流量模式,如平滑突发流量或处理周期性峰值[^4]。 #### 2. **实现方式:源码级解析** Sentinel 的实现基于 Java,其限流逻辑主要封装在 `FlowSlot` 组件中。以下是关键实现步骤(参考源码合集[^1]): - **滑动窗口的数据结构**: - Sentinel 使用 `MetricBucket` 类存储每个小窗口的流量数据(如请求数、成功数)。 - 窗口序列由 `LeapArray` 类管理,它维护一个环形数组,实现高效滑动。例如,当新请求到达时,系统定位当前窗口索引,并更新计数: ```java // 伪代码示例:基于 LeapArray 的窗口滑动 public void addRequest() { long currentTime = System.currentTimeMillis(); WindowWrap window = findWindow(currentTime); // 获取或创建当前窗口 window.value().addRequest(); // 增加请求计数 } ``` 滑动时,旧窗口数据自动过期,确保统计范围始终为最近 $T$ 时间[^1][^4]。 - **限流决策流程**: 1. **请求进入**:通过 `Entry` 对象封装请求上下文。 2. **流量统计**:`FlowSlot` 调用 `StatisticSlot` 收集滑动窗口数据,计算 $S(t)$。 3. **规则检查**:比较 $S(t)$ 与用户配置的阈值(例如,QPS 限制)。如果超出,则触发限流规则(如直接拒绝或排队等待)。 4. **熔断降级**:结合 `DegradeSlot`,当错误率过高时自动熔断,避免雪崩[^1][^3]。 - **性能优化**: - **内存高效**:使用原子操作(如 `AtomicLong`)更新计数,减少锁竞争。 - **动态配置**:支持运行时调整窗口参数(如 $T$ 和阈值),通过 Dashboard 实时生效。 - **扩展性**:插件式设计,允许集成其他算法(如令牌桶)[^1][^2]。 #### 3. **其他支持的限流算法** 除了滑动窗口,Sentinel 还实现了多种经典算法,提供更灵活的策略: - **令牌桶算法(Token Bucket)**:系统以固定速率生成令牌,请求需获取令牌才能通过。数学表达为:生成速率 $r$ tokens/s,桶容量 $b$。请求通过条件为可用令牌数 $ > 0$[^2][^4]。 - **漏桶算法(Leaky Bucket)**:请求以恒定速率流出,超出桶容量则丢弃。适合平滑流量,但灵活性较低[^2]。 Sentinel 通过 `TrafficShapingController` 接口支持这些算法,用户可根据场景选择(例如,令牌桶用于处理突发流量,滑动窗口用于精确统计)[^2][^4]。 #### 总结 Sentinel限流机制以滑动窗口为核心,结合实时统计和动态规则,实现高精度流量控制。其源码实现(如 `FlowSlot` 和 `LeapArray`)优化了性能和扩展性,适用于微服务、API 网关等场景[^1][^3]。如果您有特定配置或性能需求,建议查阅 Sentinel 官方文档或源码。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值