美文网首页
redis+lua进行限流

redis+lua进行限流

作者: 归来_仍是少年 | 来源:发表于2021-12-24 11:08 被阅读0次

    lua脚本,确保原子性

    --获取KEY
    local key = KEYS[1]
    --获取ARGV内的参数
    local expire = tonumber(ARGV[1])
    local count = tonumber(ARGV[2])
    
    --获取key的次数
    local current = redis.call('get', key)
    
    --如果key的次数存在且大于预设值直接返回当前key的次数
    if current and tonumber(current) > count then
        return tonumber(current);
    end
    
    --进行自增
    current = redis.call('incr', key)
    --获取key的过期时间
    local ttl = redis.call('ttl', key)
    
    --如果是第一次自增,设置过期时间
    if tonumber(current) == 1 then
        redis.call('expire', key, expire)
    else
        --如果key过期时间是永久,重新设置过期时间
        if ttl and tonumber(ttl) == -1 then
            redis.call('expire', key, expire)
        end
    end
    
    --返回key的次数
    return tonumber(current)
    

    限流注解

    /**
     * @desc 限流注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RateLimiter {
    
        /**
         * 限流key
         */
        String key() default "rate:limiter:";
    
        /**
         * 单位时间限制通过请求数
         */
        long limit() default 1;
    
        /**
         * 过期时间,单位秒
         */
        long expire() default 5;
    
        /**
         * 限流提示语
         */
        String message() default "请勿重复提交";
    }
    

    限流处理器

    /**
     * 限流处理器
     */
    @Slf4j
    @Aspect
    @Component
    public class RateLimiterHandler {
    
        @Qualifier(value = "limitRedisTemplate")
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        @Autowired
        private Environment env;
    
        private DefaultRedisScript<Long> getRedisScript;
        
        public static final String SYS_ENVIRONMENT_DEV = "dev";
    
        @PostConstruct
        public void init() {
            getRedisScript = new DefaultRedisScript<>();
            getRedisScript.setResultType(Long.class);
            getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/rateLimiter.lua")));
            log.info("RateLimiterHandler[分布式限流处理器]脚本加载完成");
        }
    
        // a.b.c.RateLimiter根据自己项目实际包来设置
        @Pointcut("@annotation(a.b.c.RateLimiter)")
        public void rateLimiter() {
        }
    
        @Around("@annotation(rateLimiter)")
        public Object around(ProceedingJoinPoint proceedingJoinPoint, RateLimiter rateLimiter) throws Throwable {
            log.debug("RateLimiterHandler[分布式限流处理器]开始执行限流操作");
            // 限流模块key
            String ip = IPUtil.getIpAddress();
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = signature.getMethod();
            // 目标类、方法
            String className = method.getDeclaringClass().getName();
            String name = method.getName();
            String ipKey = String.format("%s#%s", className, name);
            int hashCode = Math.abs(ipKey.hashCode());
    
            String limitKey = rateLimiter.key() + name + ":" + String.format("%s_%d", ip, hashCode);
            if (env.acceptsProfiles(Profiles.of(SYS_ENVIRONMENT_DEV))) {
                limitKey = "dev:" + limitKey;
            }
            // 限流阈值
            long limitCount = rateLimiter.limit();
            // 限流超时时间
            long expireTime = rateLimiter.expire();
            log.debug("RateLimiterHandler[分布式限流处理器]参数值为:method={},limitKey={},limitCount={},limitTimeout={}", name, limitKey, limitCount, expireTime);
    
            // 执行Lua脚本
            List<String> keyList = new ArrayList<>();
            // 设置key值为注解中的值
            keyList.add(limitKey);
    
            // 调用脚本并执行
            Long result = redisTemplate.execute(getRedisScript, keyList, expireTime, limitCount);
            log.debug("RateLimiterHandler[分布式限流处理器]限流执行结果-result={}", result);
            if (null != result && result > limitCount) {
                log.debug("由于超过单位时间={};允许的请求次数={}[触发限流]", expireTime, limitCount);
                // 限流提示语
                String message = rateLimiter.message();
                throw new RuntimeException(message);
            }
            return proceedingJoinPoint.proceed();
        }
    }
    

    redis配置

        @Bean
        public RedisTemplate<String, Object> limitRedisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
            Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                    ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
            serializer.setObjectMapper(mapper);
    
            template.setValueSerializer(serializer);
            // 使用StringRedisSerializer来序列化和反序列化redis的key值
            template.setKeySerializer(new StringRedisSerializer());
            template.afterPropertiesSet();
            return template;
        }
    

    ip工具类

    public class IPUtil {
    
        private static final Logger log = LoggerFactory.getLogger(IPUtil.class);
    
        public static String getIpAddress() {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            String ip = null;
            String ipAddresses = request.getHeader("X-Forwarded-For");
            if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
                ipAddresses = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
                ipAddresses = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
                ipAddresses = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
                ipAddresses = request.getHeader("X-Real-IP");
            }
            if (ipAddresses != null && ipAddresses.length() != 0) {
                ip = ipAddresses.split(",")[0];
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
                ip = request.getRemoteAddr();
            }
            if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
                ipAddresses = request.getRemoteAddr();
                if (ipAddresses.equals("127.0.0.1") || ipAddresses.equals("0:0:0:0:0:0:0:1")) {
                    //根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        log.error("获取ip失败 {}", Arrays.asList(e.getStackTrace()));
                    }
                    if (inet != null) {
                        ip = inet.getHostAddress();
                    } else {
                        ip = "127.0.0.1";
                    }
                }
            }
            return ip;
        }
    }
    

    相关文章

      网友评论

          本文标题:redis+lua进行限流

          本文链接:https://www.haomeiwen.com/subject/emdlqrtx.html