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;
}
}