美文网首页
滑动时间窗口+Redis模拟限流

滑动时间窗口+Redis模拟限流

作者: 莫失莫忘X3 | 来源:发表于2024-03-05 11:19 被阅读0次

本篇重点讲清楚分布式环境下【滑动时间窗口算法】原理和应用场景,以及使用reids实现的核心代码。

滑动时间窗口原理

滑动窗口算法是一种更为灵活的流量控制方案,它比固定窗口算法能更平滑地处理突发流量。在滑动窗口中,时间窗口是重叠的,这意味着流量的计算是基于过去的一段连续时间内发生的事件。


image.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);
    }
}

相关文章

网友评论

      本文标题:滑动时间窗口+Redis模拟限流

      本文链接:https://www.haomeiwen.com/subject/eezkzdtx.html