Spring Boot(24)集成Redis lua实现限流
细节
- lua脚本
- 注解
- 切面
- spel解析
- 全局异常处理器
流程
1. 注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Limiters.class)
public @interface Limiter {
String key();
String time();
String count();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiters {
Limiter[] value();
}
2. 切面
@Aspect
@Component
public class LimitAspect {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
RedisScript<String> limitScript;
ExpressionParser parser = new SpelExpressionParser();
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Before("@annotation(limiters)")
public void doBefore(JoinPoint joinPoint, Limiters limiters) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
String[] paraNameArr = discoverer.getParameterNames(method);
Object[] args = joinPoint.getArgs();
StandardEvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < paraNameArr.length; i++) {
context.setVariable(paraNameArr[i], args[i]);
}
boolean acquire = true;
for (Limiter limiter : limiters.value()) {
String key = limiter.key();
String realKey = parser.parseExpression(key)
.getValue(context, String.class);
String res = stringRedisTemplate.execute(limitScript, Collections.singletonList(realKey), limiter.count(), limiter.time());
if (StringUtils.equals(res, "0")) {
acquire = false;
}
System.out.println("key:" + realKey + "访问通过");
}
if (!acquire) {
throw new LimiterException("限流");
}
}
}
说明:before被限流,抛异常,由全局异常处理器解决
3. RedisScript
@Configuration
public class LimitConfig {
@Bean
public DefaultRedisScript<String> limitScript() {
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("Limit.lua")));
redisScript.setResultType(String.class);
return redisScript;
}
}
local key = KEYS[1]
local count = ARGV[1]
local time = ARGV[2]
local cur = redis.call('get', key)
if cur ~= false and tonumber(cur) < tonumber(count) then
redis.call('incr', key)
return "1"
elseif cur ~= false then
return "0"
else
redis.call('incr', key)
redis.call('expire', key, time)
return "1"
end
4. 接口和异常
略