因最近在做性能优化相关的工作,要对一部分接口做限流操作。故在此记录一下
@Retention(RetentionPolicy.RUNTIME)@Target(value = {ElementType.METHOD})public @interface MoatkonLimit { String uniqueKey() default "";}
@Beanpublic RedisScript<Long> limitRedisScript() { DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/limit.lua"))); redisScript.setResultType(Long.class); return redisScript;}
网上有很多类似脚本,选择并测试好即可
-- 下标从 1 开始 获取keylocal key = KEYS[1]-- 下标从 1 开始 获取参数local now = tonumber(ARGV[1]) -- 当前时间错local ttl = tonumber(ARGV[2]) -- 有效local expired = tonumber(ARGV[3]) --local max = tonumber(ARGV[4]) -- 清除过期的数据-- 移除指定分数区间内的所有元素,expired 即已经过期的 score-- 根据当前时间毫秒数 - 超时毫秒数,得到过期时间 expiredredis.call('zremrangebyscore', key, 0, expired) -- 获取 zset 中的当前元素个数local current = tonumber(redis.call('zcard', key))local next = current + 1 if next > max then -- 达到限流大小 返回 0 return 0;else -- 往 zset 中添加一个值、得分均为当前时间戳的元素,[value,score] redis.call("zadd", key, now, now) -- 每次访问均重新设置 zset 的过期时间,单位毫秒 redis.call("pexpire", key, ttl) return nextend
@Slf4j@Aspect@Componentpublic class RLimitAspect { @Resource private StringRedisTemplate stringRedisTemplate; @Around(value = "@annotation(com.moatkon.MoatkonLimit)") public Object limitAspect(ProceedingJoinPoint pjp) { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); MoatkonLimit moatkonLimit = method.getAnnotation(MoatkonLimit.class); // 获取到注解 String uniqueKey = moatkonLimit.uniqueKey(); // 获取到注解上的值 // 定义限流参数,这些参数也可以在注解上配置,也可以维护到配置文件,当然如果想做的更好用,可以开发相关的页面。 // 这里只做简单的演示 long max = 1; long timeout = 10; TimeUnit timeUnit = TimeUnit.SECONDS; if(limit(uniqueKey,max,timeout,timeUnit)){ throw new RuntimeException("触发限流"); } try { return pjp.proceed(); } catch (Throwable e) { throw e; } } //限流逻辑 private boolean limit(String key, long max, long timeout, TimeUnit timeUnit) { long ttl = timeUnit.toMillis(timeout); long now = Instant.now().toEpochMilli(); long expired = now - ttl; Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + ""); if (executeTimes != null) { if (executeTimes == 0) { log.error("触发限流"); return true; } else { return false; } } return false; }}
使用注解就很简单了
@MoatkonLimit(uniqueKey="moatkon.com") // 添加上注解即可public void limitExample(){ }
如果不想uniqueKey是写死的,需要根据请求参数中的值来限流,也很好做,使用spel就可以
@MoatkonLimit(uniqueKey="#request.key") // 添加上注解即可public void limitExample(MoatkonRequest request){ }
将参数中的request key解析出来
String uniqueKeySpel = moatkonLimit.uniqueKey(); ExpressionParser parser = new SpelExpressionParser();LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();String[] params = discoverer.getParameterNames(method);Object[] args = pjp.getArgs(); EvaluationContext context = new StandardEvaluationContext();for (int len = 0; len < params.length; len++) { context.setVariable(params[len], args[len]);}Expression expression = parser.parseExpression(uniqueKeySpel);String uniqueKey = expression.getValue(context, String.class); // 这里就将请求参数中的key取出了
网站当前构建日期: 2024.09.28