redis + lua脚本 + AOP实现接口对客户端限流

 接口每秒限制客户端访问200次

基于应用层限流,目的是:防止用户恶意刷新接口,保证程序稳定,高可用

Lua优点:
减少网络开销:这个脚本只要执行一次,能减少网络传输
原子性:Redis将这个脚本作为原子执行要么全部成功或者失败,不担心并发问题,不需要事务,(PS:LUA脚本保证原子性,执行lua脚本时不会同时执行其它脚本或redis命令, 这种语义类似于MULTI/EXEC,这个lua脚本要么执行成功,要么执行失败
复用性:lua一旦执行就能永久性保存Redis的数据,可以供其它客户端使用 

创建lua,放到resource下myLimit_ip.lua 

-- 为某个接口的请求IP设置计数器,比如:127.0.0.1请求查询用户接口
-- KEYS[1] = 127.0.0.1 也就是用户的IP
-- ARGV[1] = 过期时间 1s
-- ARGV[2] = 限制的次数
local count = redis.call('incr',KEYS[1]);
if count == 1 then
    redis.call("expire",KEYS[1],ARGV[2])
end
-- 如果时间还没有过期,并且还在规定的次数内,则请求同一接口
if count > tonumber(ARGV[1]) then
    return false
end

return true
MyLuaConfiguration读取lua
@Configuration
public class MyLuaConfiguration {

    /** 
     * 将lua脚本的内容加载出来放入到DefaultRedisScript
     * @author fan 
     * @date 2022/5/7 2:35 
     * @return org.springframework.data.redis.core.script.DefaultRedisScript 
    */
    @Bean
    public DefaultRedisScript ipLimitLua() {
        DefaultRedisScript defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("myLimit_ip.lua")));
        defaultRedisScript.setResultType(Boolean.class);
        return defaultRedisScript;
    }
}
 
  
MyAcessLimter
/**创建自定义的限流注解在Controller使用
 * @author fan
 * @date 2022年05月07日 1:08
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAcessLimter {

    

    /**
     * 每timeout限制请求的个数
     * @author fan
     * @date 2022/5/7 1:54
     * @return int
    */
    int count() default 5;

    /**
     * 超时时间,单位默认是秒
     * @author fan
     * @date 2022/5/7 1:54
     * @return int
    */
    int timeout() default 10;

    /**
     * 访问间隔
     * @author fan
     * @date 2022/5/7 1:54
     * @return int
    */
    int waits () default 20;
}
MyAcessLimiterAspect
/**
 * @author fan
 * @date 2022年05月07日 1:07
 */
@Component
@Aspect
@Slf4j
public class MyAcessLimiterAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private DefaultRedisScript myClientIPLimitLua;

    // 1: 切入点   创建的注解类
    @Pointcut("@annotation(com.fan.li.limit.MyAcessLimter)")
    public void myLimiterPonicut() {
    }

    @Before("myLimiterPonicut()")
    public void limiter(JoinPoint joinPoint) {
        log.info("限流开始......." + LocalDate.now());
        
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String classname = methodSignature.getMethod().getDeclaringClass().getName();
        String packageName = methodSignature.getMethod().getDeclaringClass().getPackage().getName();
        log.info("method:{},classname:{},packageName:{}",method,classname,packageName);
       
        MyAcessLimter annotation = method.getAnnotation(MyAcessLimter.class);
        
        String methodNameKey = method.getName();
        log.info("获取注解方法名:{}",methodNameKey);
        
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        String userClientIp = MyClientIPUtils.getIpAddr(request);
        log.info("当前用户IP:{}", userClientIp );
        
        Integer count = annotation.count();
        Integer timeout = annotation.timeout();

        /*Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            log.info("参数id--> " + request.getParameter("userId") + "---" + args[i]);
        }*/
        String key = request.getParameter("userId") + ":" + userClientIp ;//这里用userId + userClientIp 作为key
        log.info("当前的key-->" + key);
        
        Boolean b =  stringRedisTemplate.execute(myClientIPLimitLua, Lists.newArrayList(key), count.toString(), timeout.toString());//读取lua
        
        if (!b) {
            
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=UTF-8");
            try (PrintWriter writer = response.getWriter();) {
                writer.print("

操作频繁,请稍后在试

"); } catch (Exception ex) { throw new RuntimeException("操作频繁,请稍后在试"); } } } }

MyClientIPUtils

/**获取真实访问的IP
 * @author fan
 * @date 2022年05月07日 1:06
 */
public class MyClientIPUtils{

    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }
}
MyLimiterController
/**
 * 描述: 测试限流
 *
 * @author fan
 * @create 2022年05月07日 1:59
 **/
@RestController
@RequestMapping(value = "limit")
public class MyLimiterController {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserService userService;


    /**
     * 接口每秒限制访问200次
     * @author fan
     * @date 2022/5/7 2:00
     * @return list
    */
    @GetMapping("/index")
    @MyAcessLimter(count = 200,timeout = 1)
    public List index(String userId) {//对需要做限流的接口
        List list =  userService.selectUserById(Integer.parseInt(userId));
        //list.get(0);
        return list;
    }
}

效果图:把次数200改成5测试

redis + lua脚本 + AOP实现接口对客户端限流_第1张图片

1S内操作次数超过5次就会提示: 

redis + lua脚本 + AOP实现接口对客户端限流_第2张图片

你可能感兴趣的:(Ajax实现查询表中所有数据,lua,redis,开发语言)