一、📌 背景介绍
在高并发场景下,为防止服务被短时间内的大量请求压垮,我们需要限流机制来控制访问速率。令牌桶(Token Bucket)是其中一种常见的限流算法,它相比漏桶,更灵活地支持突发请求。
结合 Redis 的原子操作和高性能,我们可以使用 Redis + Lua 脚本 高效地实现分布式令牌桶限流。
二、🧠 令牌桶原理回顾
-
系统以固定速率向桶中加入令牌(如每秒 10 个);
-
桶有最大容量,超出不再添加;
-
每次请求需要取一个令牌,没有令牌则被限流;
-
可允许一定程度的突发请求(桶中积累的令牌)。
三、🔧 Redis 实现令牌桶的关键点
我们需要用 Redis 存储以下几个信息:
Key | 描述 |
---|---|
tokens | 当前令牌数 |
last_time | 上次填充令牌的时间戳 |
每次请求时,我们需要做以下操作:
-
计算当前时间距离上次填充的时间差;
-
计算应该补充的令牌数;
-
判断当前令牌是否足够;
-
修改
tokens
和last_time
;
四、📜 Lua 脚本实现
-- redis-limiter.lua
local key = KEYS[1] -- 令牌桶的key
local rate = tonumber(ARGV[1]) -- 令牌填充速率(每秒生成令牌数)
local capacity = tonumber(ARGV[2]) -- 桶容量
local now = tonumber(ARGV[3]) -- 当前时间戳(秒)
local requested = tonumber(ARGV[4]) -- 请求令牌数量
-- 获取上次令牌和时间
local last_tokens = tonumber(redis.call("HGET", key, "tokens")) or capacity
local last_time = tonumber(redis.call("HGET", key, "last_time")) or now
-- 计算可新增令牌数
local delta = math.max(0, now - last_time)
local new_tokens = math.min(capacity, last_tokens + delta * rate)
-- 判断是否能放行
local allowed = new_tokens >= requested
local left_tokens = new_tokens
if allowed then
left_tokens = new_tokens - requested
end
-- 写入 Redis
redis.call("HSET", key, "tokens", left_tokens)
redis.call("HSET", key, "last_time", now)
return allowed
五、☕ Java 实现调用
public class RedisRateLimiter {
private final RedisTemplate<String, String> redisTemplate;
private final DefaultRedisScript<Long> script;
public RedisRateLimiter(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
this.script = new DefaultRedisScript<>();
this.script.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis-limiter.lua")));
this.script.setResultType(Long.class);
}
public boolean tryAcquire(String key, int rate, int capacity, int requestedTokens) {
long now = System.currentTimeMillis() / 1000;
List<String> keys = Collections.singletonList(key);
Long result = redisTemplate.execute(
script,
keys,
String.valueOf(rate),
String.valueOf(capacity),
String.valueOf(now),
String.valueOf(requestedTokens)
);
return result != null && result == 1;
}
}
六、📌 实战场景应用
✅ API 网关限流
-
对 IP、用户 ID 进行限速;
-
防止恶意刷接口。
✅ 秒杀系统防刷
-
限制用户操作频率,保护数据库;
✅ 后台任务控制并发量
-
比如 Elasticsearch/Bulk 写入等;
七、🚨 注意事项
问题 | 建议 |
---|---|
时间不一致 | 由应用传入时间戳,避免 Redis 系统时间差异 |
分布式环境 | Redis 天然支持分布式,限流全局有效 |
精度问题 | 秒级已满足大部分需求,如需更高精度可换为毫秒实现 |
八、📚 总结
使用 Redis + Lua 实现的令牌桶限流器具备:
✅ 高性能
✅ 原子性操作
✅ 跨服务共享限流状态
✅ 易扩展、可观测性强