基于Redis的分布式限流实现

本节基于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

猜你喜欢

转载自blog.csdn.net/ghaohao/article/details/80361089