controller接口
/**
* Redis+Lua脚本+AOP+反射+自定义注解实现限流
*
* @return
*/
@GetMapping("/redis/limit/test")
@RedisLimitAnnotation(key = "redisLimit",permitsPerSecond = 3,expire = 10,msg = "当前访问人多,请稍后再试!!!")
public String redisLimitTest() {
return "正常业务返回" + IdUtil.fastUUID();
}
Lua脚本(rateLimiter.lua)
--获取key,针对那个接口进行限流,LUA脚本中的数组索引默认是从1开始的而不是从零开始。
local key=KEYS[1]
--获取注解上标注的限流次数
local limit=tonumber(ARGV[1])
local curentLimit=tonumber(redis.call('get',key) or "0" )
if curentLimit+1>limit
then return 0
--首次直接进入
else
--自增长 1
redis.call('INCRBY',key,1)
--设置过期时间
redis.call('EXPIRE',key,ARGV[2])
return curentLimit+1
end
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RedisLimitAnnotation {
/**
* 资源的key,唯一、
* 作用: 不同的接口,不同的流量控制
*
* @return
*/
String key() default "";
/**
* 最多的访问限制次数
*
* @return
*/
long permitsPerSecond() default 3;
/**
* 过期时间(计算窗口时间),单位秒默认30
*
* @return
*/
long expire() default 30;
/**
* 默认温馨提示语
*
* @return
*/
String msg() default "default message:系统繁忙or你点击太快,请稍后再试,谢谢";
}
AOP
@Slf4j
@Aspect
@Component
public class RedisLimitAop {
Object result = null;
@Resource
private StringRedisTemplate stringRedisTemplate;
private DefaultRedisScript<Long> redisLuaScript;
@PostConstruct
public void init() {
redisLuaScript = new DefaultRedisScript<>();
redisLuaScript.setResultType(Long.class);
redisLuaScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));
}
@Around("@annotation(com.deloitte.services.duty.config.RedisLimitAnnotation)")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("========环绕通知=======");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RedisLimitAnnotation redisLimitAnnotation = method.getAnnotation(RedisLimitAnnotation.class);
if (redisLimitAnnotation != null) {
//获取redis的key
String key = redisLimitAnnotation.key();
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
String limitKey = key + "\t" + className + "\t" + methodName;
log.info(limitKey);
if (null == key) {
throw new RuntimeException("it's danger,limitKey cannot be null");
}
long limit = redisLimitAnnotation.permitsPerSecond();
long expire = redisLimitAnnotation.expire();
List<String> keys = new ArrayList<>();
keys.add(key);
Long count = stringRedisTemplate.execute(redisLuaScript, keys, String.valueOf(limit), String.valueOf(expire));
if (null != count && count == 0) {
System.out.println("启动限流功能key:" + key);
return redisLimitAnnotation.msg();
}
}
try {
result = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
return result;
}
}