【redis】redis滑动时间窗口算法实现限流

滑动时间窗口实现限流

依赖

首先创建一个Springboot项目

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.4</version>
        </dependency>

配置

server:
  port: 7777
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 你的redis密码,一般默认为空

实现

@Service
@Slf4j
public class RedisSlidingWindowDemo {
    @Resource
    RedisTemplate<Object, Object> redisTemplate;
    @Resource
    StringRedisTemplate stringRedisTemplate;
    String key = "redis_limiter";
    // 窗口大小, 单位:毫秒
    long windowTime = 1000;
    // 每 窗口大小时间 最多 多少个请求
    int limitCount = 5;
    long ttl = 10000;

    public void req() {
        // 当前时间
        long currentTime = System.currentTimeMillis();
        // 窗口起始时间
        long windowStartMs = currentTime - windowTime;
        ZSetOperations<Object, Object> zSetOperations = redisTemplate.opsForZSet();
        // 清除窗口过期成员
        zSetOperations.removeRangeByScore(key, 0, windowStartMs);
        // 添加当前时间 score=当前时间 value=当前时间+随机数,防止并发时重复
        zSetOperations.add(key, currentTime, currentTime + RandomUtil.randomInt());

    }

    public boolean canReq() {
        long currentTime = System.currentTimeMillis();
        int count = redisTemplate.opsForZSet().rangeByScore(key, currentTime - windowTime, currentTime).size();
        log.info("当前线程进入判断能否请求,当前时间={},窗口={}-{},数量={}", currentTime, (currentTime - windowTime), currentTime, count);
        if (count < limitCount) {
            req();
            return true;
        } else {
            return false;
        }
    }

    public boolean canReqByLua() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(lua());
        script.setResultType(Long.class);
        long currentTime = System.currentTimeMillis();
        Long execute = stringRedisTemplate.execute(script, Collections.singletonList(key), String.valueOf(currentTime),
                String.valueOf(ttl), String.valueOf(windowTime), String.valueOf(limitCount),
                String.valueOf(currentTime + RandomUtil.randomInt()));
        boolean result = execute != 0;
        log.info("{}线程进入判断能否请求,当前时间={},窗口={}-{},数量={},result={}", Thread.currentThread().getName(), currentTime,
                (currentTime - windowTime), currentTime, execute, result);
        return result;
    }

    public String lua() {
        return "local key = KEYS[1]\n" +
                "local currentTime = tonumber(ARGV[1])\n" +
                "local ttl = tonumber(ARGV[2])\n" +
                "local windowTime = tonumber(ARGV[3]) --\n" +
                "local limitCount = tonumber(ARGV[4])\n" +
                "local value = tonumber(ARGV[5])\n" +
                "redis.call('zremrangebyscore', key, 0, currentTime - windowTime)\n" +
                "local currentNum = tonumber(redis.call('zcard', key))\n" +
                "local next = currentNum + 1\n" +
                "if next > limitCount then\n" +
                "return 0;\n" +
                "else\n" +
                "redis.call(\"zadd\", key, currentTime, value)\n" +
                "redis.call(\"expire\", key, ttl)\n" +
                "return next\n" +
                "end";
    }
}

测试

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringBootDemoApplication.class)
public class RedisSlidingWindowDemoTest {

    @Resource
    RedisSlidingWindowDemo redisSlidingWindowDemo;

    @Test
    public void req() {
        Integer threadNum = 10;
        CountDownLatch downLatch = new CountDownLatch(threadNum);
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 20; j++) {
                    boolean access = redisSlidingWindowDemo.canReqByLua();
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                downLatch.countDown();
            }, "t" + i).start();
        }
        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis可以通过使用有序集合(zset)来实现滑动窗口限流算法滑动窗口限流算法是一种常见的限流算法,它通过滑动时间窗口来计数请求并进行限制。具体实现方法如下: 1. 创建一个有序集合(zset),用于存储请求的时间戳和对应的请求标识。 2. 每次收到一个请求时,将当前时间戳和请求标识作为元素添加到有序集合中。 3. 删除有序集合中时间戳小于当前时间减去限流时间窗口的元素,以保持窗口内的请求记录。 4. 统计有序集合中请求标识的数量,如果数量超过限流阈值,则拒绝该请求。 利用Redis的有序集合特性,我们可以方便地获取某个时间段内的请求数量,并且由于有序集合的自动排序特性,可以快速进行删除操作。 下面是一个示例代码,演示了如何在Redis实现滑动窗口限流: ```java import redis.clients.jedis.Jedis; import java.util.Set; public class RedisSlidingWindow { private Jedis jedis; private String key; public RedisSlidingWindow(Jedis jedis, String key) { this.jedis = jedis; this.key = key; } public boolean isActionAllowed(int windowSize, int limit) { long currentTime = System.currentTimeMillis(); long windowStart = currentTime - windowSize * 1000; // 删除窗口之外的记录 jedis.zremrangeByScore(key, 0, windowStart); // 统计窗口内的请求数量 long count = jedis.zcard(key); // 添加当前请求记录 jedis.zadd(key, currentTime, String.valueOf(currentTime)); // 设置过期时间,防止集合无限增长 jedis.expire(key, windowSize + 1); // 判断请求数是否超过限制 return count < limit; } public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); RedisSlidingWindow slidingWindow = new RedisSlidingWindow(jedis, "sliding_window"); for (int i = 1; i <= 15; i++) { boolean actionAllowed = slidingWindow.isActionAllowed(60, 5); System.out.println("第" + i + "次操作" + (actionAllowed ? "成功" : "失败")); } jedis.close(); } } ``` 以上代码使用Jedis库连接Redis,并实现了一个`RedisSlidingWindow`类,其中`isActionAllowed`方法用于判断当前请求是否允许通过。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值