Redis的zset是有序集合,可以实现简单的限流功能,大致思路如下:
/**
* 1、先通过zadd方法把表示操作类型的key作为键插入有序集合,插入时用表示当前时间的currentTime作为值,以保证值的唯一性,同时用currentTime作为有序集合里元素的score值。
* 2、随后通过zremrangeByScore命令去除0到距当前时间limitTime时间范围内的数据,
* 那么通过zremrangeByScore命令就能在有序集合里去除score范围从0到距离当前限定时间的数据,这样就能确保有序集合内只存有指定时间内的元素。
* 3、用zcard命令统计有序集合内键为key的个数,判断集合大小是否已经超过指定限流次数,确定是否需要限流。
* 4、最后,使用expire语句设置有序集合里相关键的超时时间,这样就能确保在限流动作完成后这些键能自动删除,避免内存溢出。
*
* @param jedis redis 对象
* @param key redis---键(key)
* @param time 限制时间 秒(s)
* @param num 限制次数
*/
public static boolean canPass(ShardedJedis jedis, String key, int time, int num) {
long currentTime = System.currentTimeMillis();
//把请求放入zset(有序集合)
jedis.zadd(key, currentTime, String.valueOf(currentTime));
//去掉时间范围外(超时)的请求
jedis.zremrangeByScore(key, 0, currentTime - time * 1000);
//统计时间范围内总数
Long count = jedis.zcard(key);
//设置所有请求的超时时间
jedis.expire(key, time + 1);
boolean flag = num >= count;
return flag;
}
/**
* 获取token (使用redis的zset特性限流)
*
* @param username 用户名
* @param sign 签名
* @return
*/
@PostMapping("/getToken")
public StandardResult getToken(final ServerWebExchange exchange, String username, String sign) {
ShardedJedis jedis = shardedJedisPool.getResource();
//先判断该用户是否需要进行限流 (10秒内同一用户获取token次数不能超过三次)
boolean requestType = canPass(jedis, username, 10, 3);
if(requestType){
//获取token业务代码......
return StandardResult.ok(token);
}else {
return StandardResult.loginFailed("当前用户获取token次数过多,请稍后再试!");
}
}