Redis实现简单限流
使用Redis进行简单的限流
限流
限流的目的是当系统的处理能力有限时,阻止计划外的请求继续对系统施压,通过对并发/请求进行限速或者一个时间窗口内的请求进行限速来保护系统,达到限制速率则可以拒绝服务。还有一个应用目的是用于控制用户的行为,比如在论坛中的发帖,回复等。一般是要控制某行为在规定时间内允许的次数。
redis实现的限流
常见的限流算法有:计数器,令牌桶和漏桶算法
计数器算法是最简单粗暴的算法,系统限制在指定时间内只允许发生N次事件。使用时间窗口
使用到的redis数据结构
redis中有个很特色的数据结构:zset(有序集合)
有点儿类似于java的SortSet和HashMap的结合体,一方面是个set,保证内部的value的唯一性,另一方面可以给每个value赋予一个score,表示这个value的排序权重。
命令为:ZADD KEY SCORE VALUE
通过zset的score值,圈出一个时间窗口,将时间窗口之外的数据移除,时间窗口内的数据就是需要的数据。
因为value的值需要唯一,value中存放纳秒级的时间戳,存放毫秒级的可能会出现重复在高并发下。
每个用户使用一个zset记录其操作记录,因为zset为空时会自动从内存中移除,所以低活跃用户不会有自己的zset。降低了内存消耗。
具体实现
public void userReply () throws IOException {
String actionKey = "test";
String userId = "cjl";
for(int i=0;i<20;i++) {
System.out.println(isActionAllowed(userId, actionKey, 30, 5));
}
}
/**
* 查看用户操作是否正常
* @param userId 用户Id
* @param actionKey 行为名
* @param period 时间范围
* @param maxCount 最大次数
* @return 是否正常
* @throws IOException
*/
public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) throws IOException {
String key = String.format("hist:%s:%s", userId, actionKey);
long nowTs = System.nanoTime();
Pipeline pipe = jedis.pipelined();
pipe.multi();
// 记录行为
pipe.zadd(key, nowTs, "" + nowTs);
// 移除超时行为
pipe.zremrangeByScore(key, 0, nowTs - period * 1000*1000);
// 查找时间窗口中的行为数量
Response<Long> count = pipe.zcard(key);
// 重制所有行为的过期时间
pipe.expire(key, period + 1);
pipe.exec();
pipe.close();
return count.get() <= maxCount;
}
运行结果
true
true
true
true
true
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
因为操作都是针对同一个key,所以使用pipline提升redis的存取效率。
pipline是将多条命令一次性发送给redis,并在所有命令执行完后一次性返回,通过减少客户端和redis的通行次数来实现降低往返延时时间。
缺点
如果在时间窗口中需要记录的行为次数过多,将会有很大的消耗,比如60s内1000次操作。