一、限流算法概述
限流(Rate Limiting)是系统防护的核心策略之一,用于控制单位时间内处理的请求数量,防止因流量激增(如突发请求、恶意攻击或流量高峰)导致服务过载、资源耗尽或性能下降。其核心目标是在保障系统稳定性的前提下,合理分配资源,常见应用场景包括 API 接口防护、数据库访问控制、微服务间通信限流等。
二、常见限流算法分类
2.1 固定窗口计数器算法(Fixed Window Counter)
基本原理
将时间划分为固定长度的窗口(如 1 秒、1 分钟),每个窗口内维护一个计数器,记录该窗口内的请求总数。当请求到达时:
-
若当前窗口计数器值 小于限流阈值,则允许请求并通过,计数器 +1;
-
若 达到或超过阈值,则拒绝请求。
实现示例
以 "1 分钟内最多 100 次请求" 为例:系统维护一个计数器 count和窗口起始时间 windowStart。每次请求到来时:
-
检查当前时间是否超出
windowStart + 1分钟:若是,则重置count=0并更新windowStart为当前时间; -
若
count < 100,则允许请求并count++;否则拒绝。
优缺点分析
-
优点:实现简单(仅需计数器 + 时间判断),计算开销极低,适合对实时性要求不高的场景。
-
缺点 :窗口边界存在流量突增风险。例如,若限流阈值为 100/分钟,第 59 秒时已处理 100 次请求,第 61 秒时又处理 100 次请求(实际 2 秒内通过了 200 次请求,违反限流目标)。
2.2 滑动窗口计数器算法(Sliding Window Counter)
基本原理
为解决固定窗口的边界突增问题,滑动窗口将时间划分为更细粒度的子窗口(如 1 秒为一个子窗口),统计 最近 N 秒(或自定义时间范围)内的总请求数。例如,若限流规则为 "1 分钟内最多 100 次请求",则统计当前时间往前 60 秒内的所有请求总数。
实现方式
-
方案 1(精确统计):记录每个请求的具体时间戳(如数组或队列),每次请求时清理超过时间范围的旧时间戳,并统计剩余有效时间戳的数量(需遍历,计算成本较高)。
-
方案 2(近似统计):将滑动窗口拆分为多个子窗口(如 10 个 6 秒的子窗口),每个子窗口维护独立的计数器,通过加权求和计算总请求数(平衡精度与性能)。
优缺点分析
-
优点 :相比固定窗口,边界突增问题大幅缓解(例如不会在窗口切换时突然允许双倍流量),限流效果更平滑。
-
缺点:实现复杂度高于固定窗口(需维护时间戳或子窗口计数器),若采用精确统计方案(如存储所有请求时间),高并发场景下可能因内存或计算开销影响性能。
2.3 漏桶算法(Leaky Bucket)
基本原理
将请求视为"水滴",系统通过一个固定容量的"漏桶"来控制处理速率。漏桶的特性包括:
-
固定容量 :桶最多容纳
N个请求(超出则拒绝); -
恒定速率流出 :无论请求如何到达,桶中的请求会以 固定的速率(如每秒 R 个)被处理(类似水从漏桶底部匀速漏出)。
实现逻辑
-
当请求到达时:若桶未满(当前请求数 < 容量),则将请求放入桶中;否则直接拒绝。
-
系统以固定速率从桶中取出请求进行处理(例如通过定时器或令牌生成机制模拟"漏水")。
优缺点分析
-
优点 :严格保证处理速率的稳定性(输出速率恒定,避免下游服务因突发流量过载),适合对响应时间一致性要求高的场景(如支付接口)。
-
缺点 :无法应对突发流量:即使系统当前空闲(桶中有空间),突发请求仍需按固定速率处理(例如桶容量为 10,但请求速率突然达到 100/s,实际处理速率仍为设定的固定值,多余请求会被拒绝)。
2.4 令牌桶算法(Token Bucket)
基本原理
与漏桶相反,令牌桶通过"发放令牌"来控制请求处理。其核心规则为:
-
固定速率生成令牌 :系统以恒定速率(如每秒 R 个)向桶中添加令牌(桶的容量上限为
N,超过则丢弃多余令牌); -
请求消耗令牌:每个请求需要获取 1 个令牌才能被处理,若桶中有足够令牌则允许请求并消耗 1 个令牌;若桶为空(无可用令牌),则拒绝请求。
实现逻辑
-
后台线程/定时器按固定速率向桶中添加令牌(例如每秒 +1,桶容量为 10,则最多累积 10 个令牌);
-
请求到来时检查桶中令牌数:若 ≥1 则扣减 1 个令牌并处理请求;否则拒绝。
优缺点分析
-
优点 :允许一定程度的突发流量(例如桶中有 10 个令牌时,可瞬间处理 10 个请求),同时长期来看仍能限制平均速率(因为令牌生成速率固定)。兼顾了灵活性与稳定性,是实际应用中最广泛的限流算法。
-
缺点:实现稍复杂(需维护令牌生成与消耗逻辑),若令牌生成速率与系统负载不匹配,可能导致令牌堆积或饥饿(例如突发流量持续超过令牌生成能力)。
2.5 令牌桶 vs 漏桶:核心区别
| 维度 | 令牌桶 | 漏桶 |
|---|---|---|
| 控制对象 | 控制请求的获取(需令牌才能处理) | 控制请求的处理速率(固定漏出) |
| 突发流量 | 允许(桶中有令牌时可突发处理) | 不允许(严格按固定速率处理) |
| 适用场景 | 需要兼顾突发与长期限流的场景(如 API 接口) | 需要严格稳定输出的场景(如流媒体传输) |
三、算法选型建议
-
简单场景(如低并发内部服务):固定窗口计数器(实现简单,成本低)。
-
中等并发且需平滑限流:滑动窗口计数器(平衡精度与性能)。
-
严格限制处理速率(如下游服务脆弱):漏桶算法(保证输出恒定)。
-
允许突发且需长期限流(主流推荐):令牌桶算法(灵活且稳定,如 Redis + Lua 实现的分布式限流)。
实际应用中,令牌桶因其兼顾突发与限流的特性,成为大多数系统(如 Nginx、Guava RateLimiter、Redis 限流模块)的首选方案。