前言
当非法用户死命调用你的接口(机器攻击)时,怎么办?
正常情况下,用户是不会那么频繁的通过前端调用你的接口的。一般出现某个用户极其频繁的调用你的接口时,那就一定要小心了,可能是想搞你!!!所以,一定要在API调用前端加个限流策略,也就是将用户的一段时间的访问次数记下来,超过某个值的时候,拒绝其访问。这种限流,可以加在nginx里面,也可以加在项目的过滤器中。
但是这种高频数据放在哪呢?数据库?那你的数据库可能直接就炸了!!!ok,还是放在redis里吧!这时候就要考虑操作的原子性了。
今天用lua来实现这个功能!
实现
lua实现的限流脚本
-- ip限流脚本
-- 限定每个ip在expire_time时间段内只能访问limit次
-- 脚本返回0,说明已到达上限,返回1,说明没有到达上限
local ip = KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]
local exist = redis.call('exists', ip)
if exist == 1 then
if redis.call('incr', ip) > limit then
return 0
else
return 1
end
else
redis.call('set', ip, 1);
redis.call('expire', ip, expire_time)
return 1
end
实现一个限流处理器
package com.zyu.boot.demo.utils.limit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* redis实现IP限流器
*/
@Component
public class IPLimiter {
private RedisTemplate redisTemplate;
/**
* 每个流控周期允许访问的次数
*/
private final int limit = 5;
/**
* 流控的时间段:秒
*/
private final int expire = 2;
/**
* 限流的脚本
*/
private final String LIMITER_SCRIPT_PATH = "lua/ipLimiter.lua";
private DefaultRedisScript<Boolean> limiterScript;
public IPLimiter(@Autowired RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
limiterScript = new DefaultRedisScript();
limiterScript.setLocation(new ClassPathResource(LIMITER_SCRIPT_PATH));
limiterScript.setResultType(Boolean.class);
}
/**
* 返回true表示未触发流控,false表示触发流控
*
* @param ip
* @return
*/
public boolean limiterValidate(String ip)