接口限流
背景:在做网站的时候经常会遇到恶意访问或者被攻击的安全问题,从而导致服务器宕机或者影响网站正常运营。所以接口限流就应运而生了。
基于redis的接口限流
我们了解到redis中有着5种常见的数据类型,String、Hash、List、Set、ZSet,今天我们就要用到其中的ZSet来做接口限流这是比较简单的一种方式。
其中ZSet中有一个方法,zcount
zcount用法
zcount 集合的名字 开始区间 结束区间 (作用:统计个区间内值的个数)
思路
我们将ZSet中集合的名字 设置为 limit:ip地址 当前时间 随机值
这样 在用限定规则去调用 zcount方法 就能得到单位时间内 一个ip地址访问的次数。从而做出限流操作。
具体实现
我是基于Springboot做的测试,接口限流过滤器 (基于滑动窗口)
Service层
@Service
public class RedisService{
//添加进Zset
public void addZSet(String key, double score, String member) {
// 集合名字key 存放的时间member 随机值score
redisTemplate.opsForZSet().add(key,member,score);
}
//添加进Zset
public double getZSetCount(String key, double min, double max) {
// 集合名字key 开始区间min 结束区间max
return redisTemplate.opsForZSet().count(key,min,max);
}
}
Filter层
@Component
public class LimitFilter{
//自动注入
@Autowired
private IRedisService redisService;
//使能过滤器
public boolean dealRequest(HttpServletRequest request) {
//获取当前时间
long currentTimeMillis = System.currentTimeMillis();
//获取当前时间一分钟的时间
long aMinuteAgo = currentTimeMillis - (60*1000);
//获取一小时前的事件
long aHourAgo = currentTimeMillis - (60*1000*60);
//获取一天的时间
long aDayAgo = currentTimeMillis - (60*1000*60*24);
//获取IP地址
String ipAddress=getIpAddress(request);
//如果条件成立 说明 一分钟之内的请求次数已经越界了
if(redisService.getZSetCount("limit:"+ipAddress,aMinuteAgo,currentTimeMillis)>30){
return false;
}
//如果条件成立 说明 一小时之内的请求次数已经越界了
if(redisService.getZSetCount("limit:"+ipAddress,aHourAgo,currentTimeMillis)>60){
return false;
}
//如果条件成立 说明 一天之内的请求次数已经越界了
if(redisService.getZSetCount("limit:"+ipAddress,aDayAgo,currentTimeMillis)>100){
return false;
}
//添加数据到ZSet中
redisService.addZSet(
"limit:"+ipAddress, //以这种格式存放便于使用 limit:ip地址
System.currentTimeMillis(), // 存放当前时间
UUID.randomUUID().toString() //随机值都行 没啥用
);
return true;
}
/**
* 这个方法是专门用来获取IP地址的
* @param request
* @return
*/
private String getIpAddress(HttpServletRequest request) {
//如果部署的方式是集群部署 或者使用 apache服务器来部署 下面的方式都是有缺陷的
return request.getRemoteAddr();
}
}
总结具体逻辑
这边由于有其他的过滤器,所以用了责任链的设计模式,遵循了单一职责原则。
比如:在第一次请求 进入 过滤器时 不会被拦截 直接到
//添加数据到ZSet中
redisService.addZSet(
"limit:"+ipAddress, //以这种格式存放便于使用 limit:ip地址
System.currentTimeMillis(), // 存放当前时间
UUID.randomUUID().toString() //随机值都行 没啥用
);
在第二次也不会被拦截,如果在1分钟以内访问了 第31次 就会被拦截
//如果条件成立 说明 一分钟之内的请求次数已经越界了
if(redisService.getZSetCount("limit:"+ipAddress,aMinuteAgo,currentTimeMillis)>30){
return false;
}