处理类
@Slf4j
@Aspect
@Component
public class RateLimterHandler {
@Autowired
RedisTemplate redisTemplate;
@Autowired
private RedisUtils redisUtils;
@Autowired
private Jedis jedisConnection;
private DefaultRedisScript<Long> getRedisScript;
@PostConstruct
public void init() {
getRedisScript = new DefaultRedisScript<>();
getRedisScript.setResultType(Long.class);
getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limitToken.lua")));
log.info("RateLimterHandler[分布式限流处理器]脚本加载完成");
}
@Pointcut("@annotation(com.senlin.service.config.limiting.RateLimiter)")
public void rateLimiter() {}
@Around("@annotation(rateLimiter)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, RateLimiter rateLimiter) throws Throwable {
Signature signature = proceedingJoinPoint.getSignature();
if (!(signature instanceof MethodSignature)) {
throw new IllegalArgumentException("the Annotation @RateLimter must used on method!");
}
/**
* 获取注解参数
*/
assert Objects.nonNull(rateLimiter);
// 获取值
String key = rateLimiter.key();
String lastMillRequestKey = rateLimiter.last_mill_request_key();
String limit = rateLimiter.limit();
String permits = rateLimiter.permits();
String rate = rateLimiter.rate();
List<String> keys = Arrays.asList(key, lastMillRequestKey);
List<String> argvs = Arrays.asList(limit, permits, rate,
String.valueOf(System.currentTimeMillis() / 1000));
//调用脚本并执行
Long result = (Long) jedisConnection.eval(getRedisScript.getScriptAsString(), keys, argvs);
if (result == 0) {
throw new BizException(ResultCode.BIZ_ERROR.setMsg("当前抢购人数多,请稍后再试!"));
}
if (log.isDebugEnabled()) {
log.debug("RateLimterHandler[分布式限流处理器]限流执行结果-result={},请求[正常]响应", result);
}
return proceedingJoinPoint.proceed();
}
}
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {
/**
* 限流key
*
* @return
*/
String key() default "redis_rate_limiter";
/**
* 上一次请求时间key
* last_mill_request_key
*/
String last_mill_request_key() default "last_mill_request_key";
/**
* 令牌桶的容量
*
* @return
*/
String limit() default "300";
/**
* 请求令牌的数量
*
* @return
*/
String permits() default "1";
/**
* 令牌流入的速率
*/
String rate() default "5";
}
Lua脚本
local bucketKey = KEYS[1]
local last_mill_request_key = KEYS[2]
local limit = tonumber(ARGV[1])
local permits = tonumber(ARGV[2])
local rate = tonumber(ARGV[3])
local curr_mill_time = tonumber(ARGV[4])
local current_limit = tonumber(redis.call('GET',bucketKey) or '0')
local add_token_num = 0
local last_mill_request_time = tonumber(redis.call('GET',last_mill_request_key) or '0')
if last_mill_request_time == 0 then
redis.call('SET',last_mill_request_key,curr_mill_time)
return 0
else
add_token_num = math.floor((curr_mill_time - last_mill_request_time) * rate)
end
if current_limit + add_token_num > limit then
current_limit = limit
else
current_limit = current_limit + add_token_num
end
redis.call('SET',bucketKey,current_limit)
redis.call('EXPIRE',bucketKey,2)
if current_limit - permits < 1 then
return 0
else
current_limit = current_limit -permits
redis.call('SET',bucketKey,current_limit)
redis.call('EXPIRE',bucketKey,2)
redis.call('SET',last_mill_request_key,curr_mill_time)
return current_limit
end
刚开始使用的是RedisTemplate,结果执行脚本数据添加不到redis,也获取不到,还以为是脚本写错了,后来换成jedisConnection,执行就没有毛病,具体原因我也不知道为啥 哈哈哈哈