滑动窗口算法是一种常用的限流(Rate Limiting)技术,用于控制在一定时间窗口内的请求速率。相比其他限流算法(如固定窗口算法),滑动窗口算法具有更高的精度和灵活性。
固定窗口算法的实现基于Redis 的计数器(incr)功能,能够轻松应对大多数基本的限流需求。固定窗口的缺点是
- 突变问题:在窗口切换时可能会出现短时间内超出限流的情况。
- 例如,第 1 秒的最后一毫秒发出 5 次请求,第 2 秒的第一毫秒又发出 5 次请求,实际总请求数达到了 10 次。
- 精度较低:无法精确控制任意时间段内的请求数量。
而滑动窗口算法通过动态调整窗口边界和清理过期请求,实现了对任意时间段内请求数量的精确控制。相比固定窗口算法,它的主要优势包括:
- 避免突变问题:不会因窗口切换导致短时间内超出限流。
- 高精度:始终统计最近
N秒内的请求数量,确保不会超出限流规则。 - 平滑限流:逐步减少旧窗口的影响,提供更自然的限流效果。
下面由代码演示一遍:
@Component
public class SlidingWindowRateLimiter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 判断当前请求是否允许
*
* @param key Redis 键名(用于区分不同的用户或 API)
* @param maxRequests 最大允许的请求数
* @param windowSize 时间窗口大小(秒)
* @return true 表示允许,false 表示拒绝
*/
public boolean allowRequest(String key, int maxRequests, int windowSize) {
long currentTimestamp = System.currentTimeMillis(); // 当前时间戳(毫秒)
long windowStart = currentTimestamp - windowSize * 1000L; // 窗口起始时间
// 清理过期的请求(删除早于窗口起始时间的数据)
redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
// 获取当前窗口内的请求数
Long requestCount = redisTemplate.opsForZSet().zCard(key);
if (requestCount < maxRequests) {
/*
ZADD key score member [score member ...]
分数可以重复,但成员是唯一的
*/
//允许请求,并记录当前时间戳
redisTemplate.opsForZSet().add(key, String.valueOf(currentTimestamp), currentTimestamp);
// 设置键的过期时间(防止数据长期占用内存)
redisTemplate.expire(key, windowSize, TimeUnit.SECONDS);
return true;
} else {
// 拒绝请求
return false;
}
}
}
@RestController
public class RateLimitController {
@Autowired
private SlidingWindowRateLimiter rateLimiter;
@GetMapping("/request")
public String handleRequest(@RequestParam String userId) {
String key = "rate_limit:" + userId; // 根据用户 ID 区分限流规则
int maxRequests = 10; // 每分钟最多允许 10 次请求
int windowSize = 5; // 时间窗口为 5 秒
if (rateLimiter.allowRequest(key, maxRequests, windowSize)) {
return "Request allowed!";
} else {
return "Request denied! Too many requests.";
}
}
}
当5秒内的请求超过10条时,就会报 Request denied! Too many requests. ,此时就解决了高并发读的问题



3423

被折叠的 条评论
为什么被折叠?



