接口限流实现

发布时间 2023-04-02 22:24:04作者: 帅气的涛啊

限流

限流是什么?韩国首尔梨泰院踩踏事件,一时刻大量人聚集在一个狭窄路口,最后导致事故的发生。假如果,进去的时候限流,出去的时候限流,严格管理,那么悲剧发生的概率是不是会小一点。

先问俩件事:
你的接口能支持多少qps?
假如1000个请求同时打在你的接口上,你的服务会发生什么事?

接口限流就是做力所能及的事,保证接口不被冲烂,超过阈值的请求,存在队列或者丢弃返回。
image
上图水龙头可以自己轻松设置水流的速度。

限流算法有哪些?

  1. 固定窗口
  2. 滑动窗口
  3. 漏桶
  4. 令牌桶

限流算法实战

单机限流

Google Guava 利用令牌桶算法实现了限流工具类 RateLimiter。

  1. 引入依赖
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-jre</version>
        </dependency>
  1. 创建限流注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimit {

    // 接口每秒多少qps
    @AliasFor("qps")
    double value() default 0;
    @AliasFor("value")
    double qps() default 0;

    // 获取令牌的超时时间
    int timeout() default 0;
    // 获取令牌的超时时间单位
    TimeUnit timeUnit() default TimeUnit.MICROSECONDS;
}
  1. 使用aop实现拦截
@Aspect
@Component
public class RateLimitAspect {

    /**
     * RateLimiter缓存
     * different interfaces have different rate limiters
     */
    private ConcurrentHashMap<String, RateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap<>();

    @Pointcut("@annotation(com.alibaba.anno.RateLimit)")
    public void rateLimit() {
    }

    @Around("rateLimit()")
    public Object pointcut(ProceedingJoinPoint point) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        // rate limiter cache key -> package name + method name (eg:"com.alibaba.comtroller.getUser")
        String key = methodSignature.getDeclaringTypeName() + "." + method.getName();
        RateLimit rateLimit = AnnotationUtils.findAnnotation(method, RateLimit.class);
        if (rateLimit != null && rateLimit.value() > 0) {
            double value = rateLimit.value();
            int timeout = rateLimit.timeout();
            TimeUnit timeUnit = rateLimit.timeUnit();
            if (RATE_LIMITER_CACHE.get(key) == null) {
                RATE_LIMITER_CACHE.put(key, RateLimiter.create(value));
            } else {
                RateLimiter rateLimiter = RATE_LIMITER_CACHE.get(key);
                if (rateLimiter == null || !rateLimiter.tryAcquire(timeout, timeUnit)) {
                    // Custom processing logic, such as throwing exceptions
                    System.out.println(method.getName() + "接口速率限制");
                }
            }
        }
        return point.proceed();
    }
}

  1. 使用
@RestController
@RequestMapping("/user")
public class RateLimitController {

    @RateLimit(value = 1, timeout = 10, timeUnit = TimeUnit.MILLISECONDS)
    @GetMapping("/limit1")
    public String test1() {
        return "Hello,world!";
    }

    @RateLimit(qps = 10, timeout = 10, timeUnit = TimeUnit.MILLISECONDS)
    @GetMapping("/limit2")
    public String test2() {
        return "Hello,world!";
    }
}
  1. 测试
    接口请求速度过快,便会出现提示
    image