4. 限流算法基础(一):计数器算法原理、实现方式、优缺点分析
在催收触达系统中,API 网关需要应对突发流量、异常重试以及爬虫攻击。计数器算法是最直观的限流手段,它通过统计单位时间内的请求次数来决定是否放行。本节将深入剖析其原理、代码实现与适用边界。
4.1 算法原理
计数器算法维护一个时间窗口(如 1 秒)和一个计数器。每当请求到达时,执行以下逻辑:
- 检查时间窗口:若当前时间已超出窗口起始时间 + 窗口长度,则重置计数器并更新窗口起始时间。
- 递增计数器:将计数器加 1。
- 阈值判断:若计数器 ≤ 阈值,放行请求;否则拒绝请求。
核心公式:
窗口内累计请求数 ≤ 阈值 → 通过;否则 → 拒绝。
窗口内累计请求数 ≤ 阈值 → 通过;否则 → 拒绝。
下图展示了计数器算法在 1 秒窗口、阈值 5 的场景下的工作流程:
4.2 实现方式
计数器算法在单机环境与分布式环境下的实现差异较大。以下分别给出 Java 伪代码与 Redis 实现。
4.2.1 单机实现(Java 伪代码)
public class CounterRateLimiter {
private final long windowSizeMillis; // 窗口大小,单位毫秒
private final int threshold; // 阈值
private long windowStart; // 当前窗口起始时间戳
private int counter; // 当前计数器
public CounterRateLimiter(long windowSizeMillis, int threshold) {
this.windowSizeMillis = windowSizeMillis;
this.threshold = threshold;
this.windowStart = System.currentTimeMillis();
this.counter = 0;
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 1. 检查是否进入新窗口
if (now - windowStart >= windowSizeMillis) {
windowStart = now;
counter = 0;
}
// 2. 递增计数器
counter++;
// 3. 判断是否超过阈值
return counter <= threshold;
}
}
注意:单机实现必须加
synchronized 或使用 AtomicInteger 保证线程安全。高并发下锁竞争可能成为瓶颈。
4.2.2 分布式实现(Redis + Lua)
利用 Redis 的 INCR 和 EXPIRE 命令,配合 Lua 脚本保证原子性:
-- Lua 脚本:counter_rate_limiter.lua
local key = KEYS[1] -- 限流 key,如 "rate_limit:api:v1"
local threshold = tonumber(ARGV[1]) -- 阈值
local windowSeconds = tonumber(ARGV[2]) -- 窗口大小(秒)
local current = redis.call('INCR', key)
if current == 1 then
-- 第一次访问,设置过期时间
redis.call('EXPIRE', key, windowSeconds)
end
if current > threshold then
return 0 -- 拒绝
else
return 1 -- 放行
end
调用方通过 EVALSHA 执行该脚本,返回 1 表示放行,0 表示拒绝。
4.3 优缺点分析
| 维度 | 说明 |
|---|---|
| 优点 |
|
| 缺点 |
|
4.4 临界突变问题详解
计数器算法最典型的缺陷是“窗口边界突刺”。假设阈值 5,窗口 1 秒:
- 第 0.9 秒时计数器为 5,后续请求被拒绝。
- 第 1.0 秒窗口重置,计数器归零。
- 第 1.01 秒又有 5 个请求同时到达,全部放行。
实际在 0.2 秒内(0.9s ~ 1.1s)通过了 10 个请求,远超阈值。下图直观展示该问题:
催收场景警示:若催收系统在整点或整秒批量发送触达请求,计数器算法的临界突变可能导致下游催收通道被瞬间打满,引发通道超时或封禁。建议结合滑动窗口或令牌桶算法使用。
4.5 适用场景与选型建议
| 场景 | 推荐度 | 原因 |
|---|---|---|
| 内部测试环境限流 | ★★★★★ | 实现简单,流量可控,无需高精度 |
| 非核心 API 的粗粒度限流 | ★★★★☆ | 可接受少量突刺,维护成本低 |
| 催收触达核心通道限流 | ★★☆☆☆ | 临界突变风险高,建议使用滑动窗口 |
| 高精度计费或配额控制 | ★☆☆☆☆ | 无法保证窗口内均匀分布,易产生争议 |
实践建议:在催收触达系统中,计数器算法可作为“第一道防线”用于拦截明显超量的请求(如每秒超过 1000 次),再配合更精细的限流算法做二次控制。切勿在核心催收通道单独使用计数器算法。