新手入门:基于Redis的限流方案

541 阅读2分钟

1.基于Redis的数据结构zset。将请求打造成一个zset数组,当每一次请求进来的时候,将当前时间戳作为score插入到zset中,然后使用zremrangeByScore命令删除时间窗口之外的数据,最后使用zcard命令获取当前时间窗口内的请求数量。

public Response limitFlow(){  
    Long currentTime = new Date().getTime();  
    System.out.println(currentTime);  
    if(redisTemplate.hasKey("limit")) {  
       Integer count = redisTemplate.opsForZSet().
       rangeByScore("limit", currentTime - intervalTime, 
       currentTime).size(); // intervalTime是限流的时间  
        System.out.println(count);  
        if (count != null && count > 5) {  
           return Response.ok("每分钟最多只能访问5次");  
}  
}  
      redisTemplate.opsForZSet().add("limit",UUID.randomUUID().toString(),currentTime);  
      return Response.ok("访问成功");  
}

2.漏桶限流:将请求放入一个漏桶中,每秒钟从漏桶中取出一定数量的请求进行处理,如果漏桶已空,则拒绝新的请求。可以使用Redis的有序集合来实现漏桶,使用ZREVRANGEBYSCORE命令来获取当前时间窗口内的请求,使用ZADD命令来将请求放入漏桶中。

import redis.clients.jedis.Jedis;

public class LeakyBucket {

    private Jedis jedis;
    private String bucketKey;
    private int capacity;
    private int rate;

    public LeakyBucket(String host, int port, String bucketKey, int capacity, int rate) {
        jedis = new Jedis(host, port);
        this.bucketKey = bucketKey;
        this.capacity = capacity;
        this.rate = rate;
    }

    public boolean allowRequest() {
        // 移除当前时间窗口之前的请求
        long now = System.currentTimeMillis();
        jedis.zremrangeByScore(bucketKey, 0, now - 1000);

        // 判断漏桶中是否可以放入新的请求
        if (jedis.zcard(bucketKey) < capacity) {
            // 将新的请求放入漏桶中
            jedis.zadd(bucketKey, now, String.valueOf(now));
            return true;
        } else {
            // 漏桶已满,拒绝新的请求
            return false;
        }
    }
}

3.令牌桶限流:使用令牌桶算法控制请求的处理速率,每秒钟生成一定数量的令牌,请求需要获取一个令牌才能被处理,如果没有令牌可用,则拒绝新的请求。可以使用Redis的有序集合来实现令牌桶,使用ZADD命令来生成令牌,使用ZREVRANGEBYSCORE命令来获取当前时间窗口内的令牌。

import redis.clients.jedis.Jedis;

public class TokenBucket {

    private Jedis jedis;
    private String bucketKey;
    private int capacity;
    private int rate;

    public TokenBucket(String host, int port, String bucketKey, int capacity, int rate) {
        jedis = new Jedis(host, port);
        this.bucketKey = bucketKey;
        this.capacity = capacity;
        this.rate = rate;
    }

    public boolean allowRequest() {
        // 获取当前时间戳
        long now = System.currentTimeMillis();

        // 生成一定数量的令牌并加入令牌桶中
        long tokens = jedis.zcount(bucketKey, "-inf", "+inf");
        long newTokens = Math.min(capacity, tokens + (now - jedis.zscore(bucketKey, "last")) / 1000 * rate);
        jedis.zadd(bucketKey, now, "last");
        jedis.zremrangeByScore(bucketKey, "-inf", String.valueOf(now - 1000));
        jedis.zremrangeByRank(bucketKey, 0, newTokens - capacity - 1);

        // 判断令牌桶中是否有可用的令牌
        return jedis.zcount(bucketKey, "-inf", "+inf") >= 1;
    }
}

4.基于时间窗口的限流:基于时间窗口的限流算法的实现步骤如下:

a.定义一个时间窗口,通常为一秒钟或者一分钟等固定的时间段。

b.统计当前时间窗口内已经处理的请求数量,如果超过了限制的数量,则拒绝新的请求。

c.每当有一个请求进入系统时,就将该请求的信息保存在一个队列或者集合中。

d.每秒钟清除队列或者集合中已经过期的请求,即时间戳小于当前时间窗口的请求。

import java.util.ArrayDeque;
import java.util.Queue;

public class TimeWindow {

    private Queue<Long> queue = new ArrayDeque<>();
    private int limit;
    private long interval;

    public TimeWindow(int limit, long interval) {
        this.limit = limit;
        this.interval = interval;
    }

    public synchronized boolean allowRequest() {
        long now = System.currentTimeMillis();
        queue.add(now);

        // 移除时间窗口外的请求
        while (!queue.isEmpty() && queue.peek() < now - interval) {
            queue.poll();
        }

        // 判断请求数量是否超过限制
        return queue.size() <= limit;
    }
}