redis做客户端访问限流
Redis中zset数据类型介绍
这里主要使用了redis的set数据类型,即:有序集合
语法规则:
zadd key score member [score member…]
zrange test:1 0 -1 withscores //查看点赞(分数)与成员名
zscore test:2 james //查看james的点赞数(分数)
zrank user:3 james //返回名次:第3名返回2,从0开始到2,共3名
zrevrank user:3 james //返回0, 反排序,点赞数越高,排名越前
zrem user:3 jame mike 删除成员
zincrby user:3 10 lee //成员lee的分数加10
lee | mike | james |
---|---|---|
100 | 120 | 200 |
zremrangebyscore user:5 100 300 //删除分数在100与300范围的成员
zremrangebyscore user:5 (100 +inf //删除分数大于100(不包括100)
有序集合交集:
格式:zinterstore destination numkeys key … [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
destination:交集产生新的元素存储键名称
numkeys: 要做交集计算的键个数
key :元素键值
weights:每个被选中的键对应值乘weight, 默认为1
解决方法
对用户所发出的请求进行计算,得出结果。在设定的访问次数内是符合规则的,否则视为违规操作(提示用户当前访问过快或流量已达到最大限度)。
具体可以定义拦截器拦截用户请求加以处理,在设定时间内,请求数量不大于限制请求数。定义两个zset集合,用于存储用户访问当前时间和计数量。
1,配置一个拦截器:
public class LimitRaterInterceptor extends HandlerInterceptorAdapter {
/**
* 预处理回调方法,实现处理器的预处理(如登录检查) 第三个参数为响应的处理器,即controller 返回true,表示继续流程,调用下一个拦截器或者处理器
* 返回false,表示流程中断,通过response产生响应
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// IP限流 在线Demo所需 一秒限10个请求
String token1 = redisRaterLimiter.acquireTokenFromBucket("XMALL" + IPInfoUtil.getIpAddr(request), 10, 1000);
if (StrUtil.isBlank(token1)) {
throw new MallException("你手速怎么这么快,请点慢一点");
}
return true;
}
}
public class RedisRaterLimiter {
private final static Logger log= LoggerFactory.getLogger(RedisRaterLimiter.class);
@Autowired
private JedisPool jedisPool;
private static final String BUCKET = "BUCKET_";
private static final String BUCKET_COUNT = "BUCKET_COUNT";
private static final String BUCKET_MONITOR = "BUCKET_MONITOR_";
public String acquireTokenFromBucket(String point, int limit, long timeout) {
Jedis jedis = jedisPool.getResource();
try{
//UUID令牌
String token = UUID.randomUUID().toString();
long now = System.currentTimeMillis();
//开启事务
Transaction transaction = jedis.multi();
//删除信号量 移除有序集中指定区间(score)内的所有成员 ZREMRANGEBYSCORE key min max
transaction.zremrangeByScore((BUCKET_MONITOR + point).getBytes(), "-inf".getBytes(), String.valueOf(now - timeout).getBytes());
//为每个有序集分别指定一个乘法因子(默认设置为 1) 每个成员的score值在传递给聚合函数之前都要先乘以该因子
ZParams params = new ZParams();
params.weightsByDouble(1.0, 0.0);
//计算给定的一个或多个有序集的交集
transaction.zinterstore(BUCKET + point, params, BUCKET + point, BUCKET_MONITOR + point);
//计数器自增
transaction.incr(BUCKET_COUNT);
List<Object> results = transaction.exec();
long counter = (Long) results.get(results.size() - 1);
transaction = jedis.multi();
//Zadd 将一个或多个成员元素及其分数值(score)加入到有序集当中
transaction.zadd(BUCKET_MONITOR + point, now, token);
transaction.zadd(BUCKET + point, counter, token);
transaction.zrank(BUCKET + point, token);
results = transaction.exec();
//获取排名,判断请求是否取得了信号量
long rank = (Long) results.get(results.size() - 1);
if (rank < limit) {
return token;
} else {
//没有获取到信号量,清理之前放入redis中垃圾数据
transaction = jedis.multi();
//Zrem移除有序集中的一个或多个成员
transaction.zrem(BUCKET_MONITOR + point, token);
transaction.zrem(BUCKET + point, token);
transaction.exec();
}
}catch (Exception e){
log.error("限流出错"+e.toString());
}finally {
if(jedis!=null){
jedis.close();
}
}
return null;
}
}