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

本篇重点讲清楚分布式环境下【滑动时间窗口算法】原理和应用场景,以及使用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);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、问题场景 在日常开发中,针对于有大量请求流量的接口,我们通常会做一系列处理,来减轻数据库的压力。大多数情况会在...
    SJLin阅读 450评论 1 2
  • 一、 前言 微服务架构下,会引入很多服务问题,所以少不了需要做服务治理,包括:服务注册与发现、服务配置、服务限流、...
    梅西爱骑车阅读 8,039评论 0 12
  • 限流简介 现在说到高可用系统,都会说到高可用的保护手段:缓存、降级和限流,本博文就主要说说限流。限流是流量限速(R...
    陈二狗想吃肉阅读 2,071评论 0 26
  • 微服务引言 项目组之间相互依赖,代码冲突,上线测试等问题,我们需要对业务进行拆分。 最开始时,业务实践是利用ngi...
    一只阿木木阅读 208评论 0 0
  • 1、引言 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC)。为了保证其高可用,单...
    冰河winner阅读 6,194评论 0 1