第八章🚦自定义注解-Redis限流配置

218 阅读2分钟

🧑‍🎓 个人主页:花棉袄

📖 本章内容:【自定义注解-Redis限流配置

在这里插入图片描述

🌳Gitee仓库地址:👉🏽SpringBoot-Redis限流配置


🕰️Redis配置

🥗限流脚本

/**
 * @Author: 花棉袄
 * @Date: 2022年08月17日
 * @Describe: redis脚本常量
 */
public class RedisCacheConstant {
 	@ApiModelProperty("加锁脚本")
    public static final String LOCK_LUA = "if redis.call('setNx',KEYS[1],ARGV[1]) == 1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";

    @ApiModelProperty("解锁脚本")
    public static final String RELEASE_LOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @ApiModelProperty("限流脚本")
    public static final String LIMITER_LUA =  "local key = KEYS[1] local count = tonumber(ARGV[1]) local time = tonumber(ARGV[2]) local current = redis.call('get', key); if current and tonumber(current) > count then return tonumber(current); end current = redis.call('incr', key) if tonumber(current) == 1 then redis.call('expire', key, time) end return tonumber(current)";
}
/**
 * @Author: 花棉袄
 * @CreateDate: 2022年08月28日
 * @Describe: 脚本配置
 */
@Component
public class RedisScript {

    /**
     * 限流脚本
     *
     * @return
     */
    @Bean
    public DefaultRedisScript<Long> limitScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(RedisCacheConstant.LIMITER_LUA);
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    /**
     * 加锁脚本
     *
     * @return
     */
    @Bean
    public DefaultRedisScript<Long> lockScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(RedisCacheConstant.LOCK_LUA);
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    /**
     * 解锁脚本
     */
    @Bean
    public DefaultRedisScript<Long> ReleaseLockScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(RedisCacheConstant.RELEASE_LOCK_LUA);
        redisScript.setResultType(Long.class);
        return redisScript;
    }
}

🥙限流注解

/**
 * @Author: 花棉袄
 * @CreateDate: 2022年08月23日
 * @Describe: 限流注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {

    @ApiModelProperty("限流key")
    String key() default RedisCacheConstant.RATE_LIMIT_KEY;

    @ApiModelProperty("限流时间,单位秒")
    int time() default 60;

    @ApiModelProperty("限流次数")
    int count() default 100;

    @ApiModelProperty("限流类型")
    LimitType limitType() default LimitType.DEFAULT;
}
/**
 * @Author: 花棉袄
 * @CreateDate: 2022年08月23日
 * @Describe: 限流类型
 */
public enum LimitType {

    @ApiModelProperty("默认策略全局限流")
    DEFAULT,

    @ApiModelProperty("根据请求者IP进行限流")
    IP
}

🍣限流切面类

/**
 * @Author: 花棉袄
 * @CreateDate: 2022年08月23日
 * @Describe: 限流处理处理 切面类
 */
@Aspect
@Component
public class RateLimiterAspect {
    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);

    private RedisTemplate<Object, Object> redisTemplate;

    private RedisScript<Long> limitScript;

    @Autowired
    public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Autowired
    public void setLimitScript(RedisScript<Long> limitScript) {
        this.limitScript = limitScript;
    }

    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
        String key = rateLimiter.key();
        int time = rateLimiter.time();
        int count = rateLimiter.count();

        String combineKey = getCombineKey(rateLimiter, point);
        List<Object> keys = Collections.singletonList(combineKey);
        try {
            /* 判断redis中的缓存key的值是否存在且大于限制的次数 */
            Long number = redisTemplate.execute(limitScript, keys, count, time);
            if (StringUtils.isNull(number) || number.intValue() > count) {
                /* 缓存过于频繁 */
                throw new ServiceException("Redis限流业务","访问过于频繁,请稍候再试");
            }
            log.info("限制请求最大次数'{}',当前请求次数'{}',缓存的key'{}'", count, number.intValue(), key);
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("服务器限流异常,请稍候再试");
        }
    }

    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
        if (rateLimiter.limitType() == LimitType.IP) {
            stringBuffer.append(IpUtils.getIpAddress(ServletUtils.getRequest())).append("-");
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
        return stringBuffer.toString();
    }
}

🍖限流测试

/**
 * @Author: 花棉袄
 * @CreateDate: 2022年08月28日
 * @Describe: 限流测试
 */
@RestController
@RequestMapping("/test")
public class LimitController {

    @RateLimiter
    @GetMapping("/limit")
    @ApiOperation(value = "", notes = "")
    public AjaxResult list() {
        return AjaxResult.success();
    }
}

在这里插入图片描述


扫码_搜索联合传播样式-标准色版.png

📢💨如果文章对你有帮助【关注👍点赞❤️收藏⭐】