时间窗口算法基于redis -zset 实现

1,110 阅读1分钟

阿里云1折优惠链接:www.aliyun.com/minisite/go…

时间窗口算法

又名滑动时间算法,所谓的滑动时间算法指的是以当前时间为截止时间,往前取一定的时间,比如取1s的时间,在这1s时间内最大的访问数为1000。把这1秒分为1000格,每格是1毫秒。

滑动时间窗口如下图所示:

 

其中每一个小格子代表1ms,比如1s允许200次请求,那么就分成1000个小格。

如何实现?

借助Redis的有序集合ZSet来实现时间窗口算法限流,实现的过程是:

第一步:先使用ZSet的key存储限流的ID,score用来存储请求的时间。

第二步:每次有请求访问来了之后,先判断当前时间(以ms为单位如:System.currentTimeMillis())往前推1000ms的时间,获取这两个值的数量,如果超过限流值,就实行限流。

代码如下:

 

package com.bxm.warcar.cache.test.fetcher;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;

import java.util.ArrayList;
import java.util.List;


/**
 * <h3>
 * 基于redis的时间滑动窗口
 * </h3>
 *
 * @author hcmony
 * @since V1.0.0, 2020/08/11 22:00
 */
public class Test {

    private final static JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), "10.10.1.34", 6379,
            Protocol.DEFAULT_TIMEOUT, "redis_pwd123", 0);
    private static List<String> list = new ArrayList<>();

    /**
     * 设定 有1000个请求进来,但是每秒只能接受200个请求
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {

        int a = 1000;
        int limit =200;
        final long l = System.currentTimeMillis();

        for (int i = 0; i < a; i++) {

            add("LIMIT:TEST", "TEST" + i, limit);
            Thread.sleep(1);
        }
        System.out.println(System.currentTimeMillis()-l);
        System.out.println(list);
    }

    /**
     *
     * @param key 固定的
     * @param value 变化的,同一个value只能存入一次
     * @param limit  限流参数,可调整
     */
    private static void add(String key, String value, int limit) {

        final long timeMillis = System.currentTimeMillis();
        if (get(key, timeMillis) > limit) {
            System.out.println(value+"超过限制了");
            list.add(value);
            return;
        }

        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.zadd(key, timeMillis, value);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }

    }

    /**
     * 获取当前 timeMillis 往前退1秒的数据
     * @param key  固定的
     * @param timeMillis 动态的,以毫秒为单位统计
     * @return
     */
    private static long get(String key, long timeMillis) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zcount(key, timeMillis - 1000, timeMillis);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}

www.aliyun.com/activity/da…