本篇重点讲清楚分布式环境下【滑动时间窗口算法】原理和应用场景,以及使用reids实现的核心代码。
滑动时间窗口原理
滑动窗口算法是一种更为灵活的流量控制方案,它比固定窗口算法能更平滑地处理突发流量。在滑动窗口中,时间窗口是重叠的,这意味着流量的计算是基于过去的一段连续时间内发生的事件。
![](https://img.haomeiwen.com/i9119758/7af0ce1796e21bb9.png)
工作流程:
- 窗口定义:确定窗口的大小,例如1秒钟,并设置窗口的滑动间隔,比如100毫秒。
- 计数与滑动:每个窗口都有自己的计数器。当一个新请求到达时,增加当前时间窗口及其前面相邻的窗口的计数。
- 限制检查:如果任何连续时间段内的请求总数超过阈值,则拒绝新的请求。
- 窗口更新:随着时间的推移,不断向前滑动窗口,并更新相应的计数器。
Redis实现限流
一些分布式服务框架,为了更高的可靠性,他们使用的是本地计算。比如接口限流1000TPS,一共有20台应用服务器,框架就会把计算出每台机器是50个TPS,下发给所有的应用服务器,在服务器上线、下线过程中,可能会有一段时间是不准确的。所以可以使用这种redis方案。且精度更高,不受应用服务器的上、下线影响。
在Redis中,可以使用列表或有序集合来模拟这种滑动窗口。
下面使用有序集合(sorted set)来实现了滑动时间窗口算法:
/**
* redis限流操作类
* 每个请求都以其发生的时间戳作为分数(SCORE)存储在集合中。通过移除旧于当前时间窗口的请求来维护滑动窗口。通过检查集合中的元素数量,以确定是否超过了设定的最大请求数。
*/
@Component
public class RedisLimitUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 滑动时间窗口大小
private static final long WINDOW_SIZE_IN_SECONDS = 1000;
/**
* 判断是否限流
* 这里不考虑超过long最大值的情况,系统在达到long最大值前就奔溃了。
* reuqestId 请求id,可以是uuid不重复即可
*/
public boolean isLimited(String key, String reuqestId, long countLimit) {
// 使用Redis的多个命令来实现滑动窗口
// 移除窗口之外的旧请求。
redisTemplate.zremrangeByScore(key, 0, currentTimeMillis - WINDOW_SIZE_IN_SECONDS);
// 获取当前窗口内的请求数量
long count = redisTemplate.zcard(key);
if (countLimit >= count) {
// 将新请求添加到集合中
redisTemplate.zadd(key, currentTimeMillis, reuqestId);
return true;
} else {
return false;
}
}
}
业务使用示例:
public class DemoService {
@Autowired
private RedisLimitUtil redisLimitUtil;
@Override
public Result doSomething(RequestCommand request) {
if (isLimited(request)) {
throw new RequestLimitedException(buildExceptionMessage(request));
}
// 其它业务处理
... ...
}
/*
* 限流判断
*/
private boolean isLimited(RequestCommand request) {
// 限流KEY,这里以[业务类型 + 渠道]举例
String key = request.getBizType() + request.getChannel();
// 限流值
Long countLimit = countLimitMap.get(key);
// 如果key对应的限流值没有配置,或配置为-1,说明不限流
if (null == countLimit || -1 == countLimit) {
return false;
}
return redisLimitUtil.isLimited(key, uuid(), countLimit);
}
}
网友评论