2、限流基础理论:四大核心算法原理
限流(Rate Limiting)是微服务网关与业务层的第一道防线。在 Gin 项目中,选择正确的限流算法直接决定了系统的吞吐、延迟与公平性。本节深入剖析四种经典限流算法的核心原理、数学模型与适用场景,为后续在 Gin 中间件中落地限流能力奠定理论基础。
2.1 计数器算法(Fixed Window Counter)
计数器算法是最直观的限流实现。它将时间划分为固定的窗口(如 1 秒),在每个窗口内维护一个计数器,请求到达时计数器加 1;当计数器超过预设阈值时,拒绝后续请求;窗口结束时计数器归零。
核心缺陷:临界突变问题。若窗口边界处出现突发流量(如第 1 秒最后 10ms 和第 2 秒前 10ms 各通过 100 个请求),实际 20ms 内通过了 200 个请求,远超阈值。
| 维度 | 说明 |
|---|---|
| 窗口粒度 | 固定时间长度(如 1s / 1min) |
| 计数器 | 原子整数(atomic int) |
| 重置策略 | 窗口到期立即归零 |
| 主要问题 | 窗口边界流量突刺 |
| 适用场景 | 低精度、简单限流需求 |
2.2 滑动窗口算法(Sliding Window Log / Sliding Window Counter)
滑动窗口算法将固定窗口细分为多个小格(如 1 秒分为 10 个 100ms 小格),每个小格独立计数。窗口随着时间推移向前滑动,丢弃过期小格,累加当前窗口内所有小格的计数。相比计数器算法,滑动窗口大幅缓解了临界突变问题。
工程实践:Gin 项目中常用 Redis Sorted Set 实现滑动窗口,以时间戳为 score,每次请求添加成员并移除窗口外的过期成员,通过 ZCARD 获取当前窗口请求数。
| 维度 | 计数器算法 | 滑动窗口算法 |
|---|---|---|
| 窗口粒度 | 1 个粗粒度窗口 | N 个细粒度子窗口 |
| 临界突刺 | 严重 | 大幅缓解(子窗口越多越平滑) |
| 内存开销 | 低(1 个计数器) | 较高(N 个计数器 + 时间戳列表) |
| 实现复杂度 | 低 | 中 |
2.3 漏桶算法(Leaky Bucket)
漏桶算法将请求视为水滴,以恒定速率从桶底漏出。桶有固定容量,当桶满时新请求被丢弃。漏桶强制输出速率恒定,无论输入流量如何突发,输出始终平滑。
注意:漏桶算法对突发流量不友好。即使桶未满,也无法加速处理积压请求;适合需要严格平滑输出的场景(如数据库写入、下游慢服务保护)。
- 桶容量(capacity):最大积压请求数,对应系统容忍的瞬时突发量。
- 漏出速率(leak rate):每秒处理请求数,对应下游服务的最大吞吐。
- 当前水位(water level):桶内积压请求数,随时间递减。
- 丢弃策略:桶满时新请求直接拒绝(HTTP 429 Too Many Requests)。
2.4 令牌桶算法(Token Bucket)
令牌桶算法是目前业界最广泛使用的限流算法(如 Guava RateLimiter、gRPC 流控)。它以固定速率向桶中放入令牌,每个请求消耗一个令牌;桶有最大容量,令牌超过容量则丢弃。当桶中令牌耗尽时,请求被限流。
优势:令牌桶允许一定程度的突发流量(桶内积攒的令牌可被一次性消耗),同时保证长期平均速率不超过阈值。相比漏桶,令牌桶更适合 Web API 场景。
| 特性 | 令牌桶 | 漏桶 |
|---|---|---|
| 输出速率 | 允许突发(桶内有令牌时) | 严格恒定 |
| 桶容量含义 | 最大突发请求数 | 最大积压请求数 |
| 丢弃时机 | 令牌不足时 | 桶满时 |
| 适用场景 | API 网关、微服务入口 | 下游慢服务保护 |
2.5 四种算法对比总结
| 算法 | 突发容忍 | 输出平滑度 | 内存开销 | 实现复杂度 | Gin 推荐度 |
|---|---|---|---|---|---|
| 计数器 | 差(临界突刺) | 差 | 极低 | 极低 | ⭐(不推荐) |
| 滑动窗口 | 中(子窗口越多越好) | 中 | 中 | 中 | ⭐⭐⭐ |
| 漏桶 | 无 | 极高 | 低 | 低 | ⭐⭐(下游保护) |
| 令牌桶 | 好 | 高 | 低 | 低 | ⭐⭐⭐⭐⭐ |
Gin 项目选型建议:入口网关层优先选择令牌桶算法(支持突发、实现简单);下游脆弱服务保护优先选择漏桶算法(强制平滑);分布式场景下可结合 Redis 实现滑动窗口算法。计数器算法仅用于非关键路径的简易限流。