
1. 固定窗口(Fixed Window)
原理:
- 固定窗口算法将时间划分为固定的时间段(窗口),比如 1 秒、1 分钟等。在每个时间段内,允许最多一定数量的请求。如果请求超出配额,则拒绝。
优点:
缺点:
- 在窗口边界处可能出现流量突增的情况(称为“边界效应”),比如两个窗口交界处可能短时间内允许通过的请求数量翻倍。
Lua脚本:
local current = redis.call('GET', KEYS[1])
if current and tonumber(current) >= tonumber(ARGV[1]) then
return 0
else
current = redis.call('INCR', KEYS[1])
if tonumber(current) == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[2])
end
return 1
end
Java模拟限流:
package com.strap.common.redis.demo;
import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
public class FixedWindowExample {
private static final String LIMIT_SCRIPT =
"-- KEYS[1]: 限流的键(通常为用户ID或者API)\n" +
"-- ARGV[1]: 最大允许请求数\n" +
"-- ARGV[2]: 窗口时间(以秒为单位)\n" +
"\n" +
"local current = redis.call('GET', KEYS[1])\n" +
"\n" +
"if current and tonumber(current) >= tonumber(ARGV[1]) then\n" +
" return 0 -- 返回0表示超出限流\n" +
"else\n" +
" current = redis.call('INCR', KEYS[1])\n" +
" if tonumber(current) == 1 then\n" +
" redis.call('EXPIRE', KEYS[1], ARGV[2]) -- 设置窗口时间\n" +
" end\n" +
" return 1 -- 返回1表示未超限\n" +
"end";
@SneakyThrows
public static void main(String[] args) {
JedisPoolConfig config = new JedisPoolConfig();
config.setBlockWhenExhausted(true);
try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
for (int i = 0; i < 100; i++) {
Thread.sleep(100);
Object o = jedis.eval(LIMIT_SCRIPT, 1, "FixedWindowExample", "10", "5");
if (Long.valueOf(1).equals(o)) {
System.out.println(i + "=============================放行");
} else {
System.out.println(i + "拦截=============================");
}
}
}
}
}
2. 滑动窗口(Sliding Window)
原理:
- 滑动窗口改进了固定窗口的“边界效应”问题,它通过更细粒度的时间单位来平滑地控制请求。滑动窗口可以在较短的时间窗口内动态调整请求计数,防止瞬时流量激增。
优点:
缺点:
Lua脚本:
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[3] - ARGV[1] * 1000)
local count = redis.call('ZCARD', KEYS[1])