Redis 可以用来做限流,常见的限流实现方式包括以下几种:
1. 固定窗口计数限流
适用于简单的请求限流,比如每秒最多允许 100 次请求。
实现方式:
- 设定一个键(Key),比如
rate_limit:user_123
- 在固定时间窗口(如 1 秒)内累加请求计数
- 若计数超过阈值,则拒绝请求
示例代码(使用 Lua 脚本实现原子操作):
local key = KEYS[1] -- 限流的 Redis 键
local limit = tonumber(ARGV[1]) -- 最大请求数
local expire_time = tonumber(ARGV[2]) -- 窗口过期时间 (秒)
local current = redis.call("INCR", key)
if current == 1 then
redis.call("EXPIRE", key, expire_time)
end
if current > limit then
return 0 -- 限流
else
return 1 -- 允许请求
end
使用示例(在 Java 中执行):
String script = "..." // 上面的 Lua 脚本
List<String> keys = Collections.singletonList("rate_limit:user_123");
List<String> args = Arrays.asList("100", "1"); // 100 次/秒
Long result = (Long) jedis.eval(script, keys, args);
if (result == 0) {
System.out.println("请求过多,限流!");
}
2. 滑动窗口限流
固定窗口的缺点是,窗口切换时可能导致流量突增。滑动窗口限流可以缓解这个问题。
实现方式:
- 维护一个 有序集合(Sorted Set),记录请求时间戳
- 每次请求时,移除超出时间范围的数据
- 判断当前窗口内的请求数是否超过阈值
示例代码:
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window_size = tonumber(ARGV[2])
local max_count = tonumber(ARGV[3])
redis.call("ZREMRANGEBYSCORE", key, 0, now - window_size)
local count = redis.call("ZCARD", key)
if count >= max_count then
return 0
else
redis.call("ZADD", key, now, now)
redis.call("EXPIRE", key, window_size)
return 1
end
3. 令牌桶算法
令牌桶适用于 平滑流量,允许一定程度的突发流量。
实现方式:
- 设定一个令牌桶,每秒按固定速率生成令牌
- 请求时从桶里取令牌,若令牌不足则拒绝请求
Redis 实现(Lua 脚本):
local key = KEYS[1]
local rate = tonumber(ARGV[1]) -- 令牌生成速率(每秒生成多少)
local capacity = tonumber(ARGV[2]) -- 桶容量
local now = tonumber(ARGV[3])
local token_key = key .. ":tokens"
local timestamp_key = key .. ":timestamp"
local last_time = tonumber(redis.call("GET", timestamp_key)) or now
local tokens = tonumber(redis.call("GET", token_key)) or capacity
local elapsed = now - last_time
local new_tokens = math.min(capacity, tokens + (elapsed * rate))
if new_tokens < 1 then
return 0 -- 没有令牌,拒绝请求
else
redis.call("SET", token_key, new_tokens - 1)
redis.call("SET", timestamp_key, now)
return 1 -- 允许请求
end
4. 漏桶算法
漏桶算法适用于 平滑处理请求,防止流量突增。
实现方式:
- 设定一个队列(List),存放请求
- 以固定速率处理请求,多余的请求直接丢弃
示例:
local key = KEYS[1]
local rate = tonumber(ARGV[1]) -- 处理速率(多少秒处理一个请求)
local capacity = tonumber(ARGV[2]) -- 队列最大长度
local now = tonumber(ARGV[3])
local size = redis.call("LLEN", key)
if size >= capacity then
return 0 -- 队列已满,丢弃请求
else
redis.call("LPUSH", key, now)
redis.call("LTRIM", key, 0, capacity - 1)
redis.call("EXPIRE", key, rate * capacity)
return 1
end
选择适合的限流算法
算法 | 适用场景 | 特点 |
---|---|---|
固定窗口 | 简单限流,适合低并发 | 实现简单,窗口切换时可能突发 |
滑动窗口 | 适用于高并发场景 | 计算较复杂,能更平滑地限流 |
令牌桶 | 允许突发流量,保证一定吞吐量 | 适用于 API 请求限流 |
漏桶 | 平滑请求处理,防止流量抖动 | 控制流出速率,避免流量过载 |
你目前的项目是 高并发的 Spring Boot 订单系统,可以考虑 令牌桶算法,用 Redis 来实现限流,结合 Spring Boot 的拦截器或者网关过滤器来执行。