系统限流

46 阅读3分钟

系统限流主要有两张方式 一个就是令牌桶算法,另一个就是滑动窗口,

1,令牌桶算法。
这个原理说起来也比较简单,凡是访问系统的用户都需要一个令牌,这个令牌存放在一个固定的地方,系统会自动按照一定的速率往里面发放令牌,比如每秒钟100个。也就限制了每秒钟的访问量在100个左右。

a,这个算法在google的guava工具包里面已经实现了,demo代码也比较简单:

RateLimiter smoothBurstyRateLimiter = RateLimiter.create(2);// 平滑限流器,每秒生成令牌数量 2

RateLimiter smoothWarmingUpRateLimiter = RateLimiter.create(2, 10, TimeUnit.SECONDS);// 预热限流器,每秒生成令牌数量 2,预热时间 10s

smoothBurstyRateLimiter.tryAcquire();// 尝试获取令牌,获取不到就立即返回false

smoothBurstyRateLimiter.acquire();// 获取令牌,获取不到就一直阻塞等待

smoothBurstyRateLimiter.setRate(10);//还可以手动调整速率

经过测试 该算法会缓存速率两倍左右的令牌,即 每秒钟生成100个令牌的话,最多可以缓存200个左右的令牌。

b,实际应用,
对于系统限流可以限制整个系统的流量,利用AOP实现也比较简单。代码如下

@Aspect
@Component
public class GuavaRateLimitAspect {

/**

  • 定义全局的 rateLimiter 每秒钟最多1000 请求
    */
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);

/**

  • 定义切点 增强所有的controller方法
    /
    @Pointcut("execution(public String com.kuafu.controller.
    .*(..))") // 修饰符 返回值 方法名 参数
    public void globalLimiter() { }

@Around(value = "globalLimiter()")
public Object aroundGlobalLimiter(ProceedingJoinPoint pjp) throws Throwable {
if(!RATE_LIMITER.tryAcquire()){
return "服务器繁忙,请稍后再试! 全局限流器执行限制";
}
return pjp.proceed();
}
}

还可以限制某个方法的流量,代码如下:
定义一个注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GuavaRateLimit {

/**
*

  • @return
    */
    String value() default "";

/**

  • 每秒向桶中放入令牌的数量 默认最大即不做限流
    */
    double permitsPerSecond() default Double.MAX_VALUE;

/**

  • 获取令牌的等待时间 默认0
    */
    int timeOut() default 0;

/**

  • 超时时间单位
    */
    TimeUnit timeOutUnit() default TimeUnit.MILLISECONDS;

RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE);
}

定义AOP切面:

@Aspect
@Component
public class GuavaRateLimitAspect {

/**

  • 定义切点 带有指定注解切入单个方法限流
    */
    @Pointcut("@annotation(com.kuafu.rateLimiter.GuavaRateLimit)")
    public void methodLimiter() { }

/**

  • 环绕增强
    */
    @Around(value = "methodLimiter()")
    public String aroundMethodLimiter(ProceedingJoinPoint pjp) throws Throwable {
    MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
    //获取目标方法
    Method targetMethod = methodSignature.getMethod();
    if (targetMethod.isAnnotationPresent(GuavaRateLimit.class)) {
    //获取目标方法的@GuavaRateLimit注解
    GuavaRateLimit lxRateLimit = targetMethod.getAnnotation(GuavaRateLimit.class);
    lxRateLimit.rateLimiter.setRate(lxRateLimit.permitsPerSecond());
    if (!lxRateLimit.rateLimiter.tryAcquire(lxRateLimit.timeOut(), lxRateLimit.timeOutUnit()))
    return "服务器繁忙,请稍后再试!";
    }
    return (String) pjp.proceed();
    }
    }

之后将注解添加到需要限流的方法上就可以了。
以上是对guava令牌桶算法的整理,但是还没有结束,这个只支持单节点系统的限流,如果是集群分布式的话,就失去全局效果了。接下里利用redisson来改造一下。
demo:
RedissonClient redisson = Redisson.create(new Config());
RRateLimiter limiter = redisson.getRateLimiter("myLimiter");
// Initialization required only once.
// 5 permits per 2 seconds
limiter.trySetRate(RateType.OVERALL, 5, 1, RateIntervalUnit.SECONDS);

// acquire 3 permits or block until they became available
limiter.acquire(1);
limiter.tryAcquire();

全局:

@Aspect
@Component
public class RedissonRateLimitAspect {

private final static Logger logger = LoggerFactory.getLogger(RedissonRateLimitAspect.class);

/**

  • 定义全局的 rateLimiter 每秒钟最多1000 请求
    */
    @Resource
    private RedissonClient redissonClient;
    private static final String KEY = "k1:k1:k1";

/**

  • 定义切点 增强所有的controller方法
    /
    @Pointcut("execution(public String com.kuafu.controller.
    .*(..))") // 修饰符 返回值 方法名 参数
    public void globalLimiter() {}

@Around(value = "globalLimiter()")
public Object aroundGlobalLimiter(ProceedingJoinPoint pjp) throws Throwable {
logger.info("全局限流器执行限制");
final RRateLimiter rateLimiter = redissonClient.getRateLimiter(KEY);
rateLimiter.setRate(RateType.PER_CLIENT,1000,1, RateIntervalUnit.SECONDS);
if(!rateLimiter.tryAcquire()){
return "服务器繁忙,请稍后再试! 全局限流器执行限制";
}
return pjp.proceed();
}
}

单个方法:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedissonRateLimit {
/**
*

  • @return
    */
    String key() default "";

/**

  • 每秒向桶中放入令牌的数量 默认最大即不做限流
    */
    long permitsPerSecond() default Long.MAX_VALUE;

/**

  • 获取令牌的等待时间 默认0
    */
    int timeOut() default 0;

/**

  • 超时时间单位
    */
    TimeUnit timeOutUnit() default TimeUnit.MILLISECONDS;
    }

@Aspect
@Component
public class RedissonRateLimitAspect {

/**

  • 定义切点 带有指定注解切入单个方法限流
    */
    @Pointcut("@annotation(com.kuafu.rateLimiter.RedissonRateLimit)")
    public void methodLimiter() { }

@Around(value = "methodLimiter()")
public String aroundMethodLimiter(ProceedingJoinPoint pjp) throws Throwable {
logger.info("Limiter,method:{}...", pjp.getSignature().getName());
MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
//获取目标方法
Method targetMethod = methodSignature.getMethod();
if (targetMethod.isAnnotationPresent(RedissonRateLimit.class)) {
//获取目标方法的@LxRateLimit注解
RedissonRateLimit lxRateLimit = targetMethod.getAnnotation(RedissonRateLimit.class);
final RRateLimiter rateLimiter = redissonClient.getRateLimiter(lxRateLimit.key());
rateLimiter.setRate(RateType.PER_CLIENT,lxRateLimit.permitsPerSecond(),1, RateIntervalUnit.SECONDS);
if (!rateLimiter.tryAcquire(lxRateLimit.timeOut(), lxRateLimit.timeOutUnit()))
return "服务器繁忙,请稍后再试!";
}
return (String) pjp.proceed();
}
}