本节基于Redis实现了系统的限流控制。
Lua脚本准备
local val =
redis.
call(
'incr', KEYS[
1])
local ttl =
redis.
call(
'ttl', KEYS[
1])
redis.
log(
redis.LOG_NOTICE
, "incr "..
KEYS[
1]..
" "..val)
;
if val ==
1 then
redis.
call(
'expire', KEYS[
1]
, tonumber(
ARGV[
1]))
else
if ttl == -
1 then
redis.
call(
'expire', KEYS[
1]
, tonumber(
ARGV[
1]))
end
end
if val >
tonumber(
ARGV[
2])
then
return 0
end
return 1
对传入的key做incr操作,如果key首次生成,设置超时时间ARGV[1];
ttl是为防止某些key在未设置超时时间并长时间已经存在的情况下做的保护的判断;
判断当前key的val是否超过限制次数ARGV[2]。
Redis客户端
public Long
limit(String key) {
return redisClient.eval(key
, expire, reqCount)
;
}
加载lua脚本,并对外提供Redis调用的接口。
AOP
@Before(
"@annotation(com.snatch.deal.shop.common.annotations.RateLimit)")
public void before(JoinPoint pjp)
throws Throwable {
Signature sig = pjp.getSignature()
;
if (!(sig
instanceof MethodSignature)) {
throw new IllegalArgumentException(
"该注解只能用在方法上")
;
}
MethodSignature msig = (MethodSignature) sig
;
String methodName = pjp.getTarget().getClass().getName() +
"." + msig.getName()
;
String limitKey = Md5Utils.
encrypt(methodName)
;
if (
rateLimiter.limit(limitKey) !=
1){
throw new OverLimitException(
"触发限流控制")
;
}
}
@Target({ElementType.
METHOD})
@Retention(RetentionPolicy.
RUNTIME)
@Documented
public @
interface RateLimit {
String
value()
default "";
}
利用AOP的before实现方法执行前的拦截,所有注解了RateLimit的方法都会被缓存限流。一旦触发限流,对外抛出异常。
统一异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OverLimitException.
class)
@ResponseBody
public String
OverLimitExceptionHandler(OverLimitException ole){
return ole.getMessage()
;
}
}
利用ControllerAdvice处理对应的异常的处理。
详细代码在 https://gitee.com/lawlet/shop