RateLimiter实现原理

RateLimiter实现原理

简介
  • 维护 storedPermits、nextFreeTicketMicros 变量,其中 storedPermits代表当前的令牌数目,而 nextFreeTicketMicros 代表获取到下一个令牌的时间。
  • 是一种借用、惰性更新、计算睡眠时间的一种算法。其中的借用,是指对于第一个请求会 先借用令牌桶中没生成好的 若干个令牌直接返回 ,而第二个请求 不管当前令牌桶有多少令牌,也要以固定速率补充回被借用的令牌 后才可以执行,而第三个,也是需要以固定速率补充回第二个借用的令牌才可以执行。而惰性更新,是指不通过定时任务一直更新令牌桶的数目,而是每次请求来执行resync()来计算有多少个令牌准备好了。而计算睡眠时间,则是不通过自旋,或是任务队列挂起唤醒这些做法,而是计算每个请求需要的睡眠时间,做线程睡眠自动苏醒,相当于排队。
  • 优点: 不通过竞争令牌桶中的令牌实现,而是计算等待时间的方式,实现比较简单。同时避免频繁的线程切换和锁竞争,提高性能。
  • 缺点: 借用的思路有可能导致消耗令牌的速率不均匀。依赖系统时间,系统时间的调整可能会影响到正确性。
  • 代码整理

  • acquire / tryAcquire() / create()的doSetRate() 中,对 mutex() 加锁, 这个就是细粒度的一个锁。只阻塞this的这个acquire / tryAcquire()方法。
  • 在执行acquire / tryAcquire()中,同步块中
    1. resync()
      1. 惰性更新 落后的nextFreeTicketMicros 为当前时间, 也就是如果当前时间已经大于nextFreeTicketMicros,则更新 nextFreeTicketMicros 为当前时间
      2. 惰性更新 storedPermits 加上(当前时间-落后的nextFreeTicketMicros)过程中可以生成多少令牌,作为存储的令牌,不会超过 maxPermits 最大令牌数
    2. reserveAndGetWaitLength()
      1. 计算 nextFreeTicketMicros - 当前时间,代表这个请求需要等待的时间: microsToNextFreeTicket
      2. 根据当前令牌桶里的数目,计算还需要多少时间才能偿还 被借用的令牌数目 ,并叠加到下次请求的放行时间:nextFreeTicketMicros
      3. 返回 microsToNextFreeTicket
    3. 睡眠 waitMicros 这段时间 Thread.sleep()

  • 在执行create()中,
    1. doSetRate() 已经在同步代码中
      1. 执行resync(),惰性更新 nextFreeTicketMicros 和 storedPermits。
      2. maxPermits = maxBurstSeconds * permitsPerSecond; 即 最大许可为1秒钟生成多少个许可。作为最大令牌。
      3. 当每秒生成令牌小于1时,相当于系统不会存储令牌。

  • 结合源码

    demo

    
     public void contextLoad5() throws InterruptedException {
        //  每秒0.1个请求, 阻塞直到获取到令牌
        RateLimiter rateLimiter = RateLimiter.create(0.1);
    
        CountDownLatch countDownLatch = new CountDownLatch(10);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
          executorService.execute(() -> {
            int times = 0;
            while (true) {
              boolean b = rateLimiter.tryAcquire();
              if (b) {
                times++;
                System.out.println(Thread.currentThread().getName() + "当前请求i:【" + times + "】");
              }
              else {
                System.out.println(Thread.currentThread().getName() + "表示已达到限流阈值,拒绝请求..." + times);
              }
              try {
                Thread.sleep(10 * 1000);
              }
              catch (InterruptedException e) {
                e.printStackTrace();
              }
            }
          });
        }
        countDownLatch.await();
      }
    
    

    RateLimiter.create(0.1);

    
    // 当前令牌数目
    double storedPermits;
    // 最大令牌数目
    double maxPermits;
    // 每个请求需要多少
    volatile double stableIntervalMicros;
    // 锁 : acquire / tryAcquire / setRate 的锁
    private final Object mutex;
    // 下一个请求可以放行的时间
    private long nextFreeTicketMicros;
    
    // permitsPerSecond: 每秒钟生成多少个请求
    static RateLimiter create(RateLimiter.SleepingTicker ticker, double permitsPerSecond) {
      RateLimiter rateLimiter = new RateLimiter.Bursty(ticker, 1.0D);
      rateLimiter.setRate(permitsPerSecond);
      return rateLimiter;
    }
    
    public final void setRate(double permitsPerSecond) {
      Preconditions.checkArgument(permitsPerSecond > 0.0D && !Double.isNaN(permitsPerSecond), "rate must be positive");
    
      // 使用 mutex 作为锁
      synchronized(this.mutex) {
        // 落后的nextFreeTicketMicros ,意味着 nowMicros > this.nextFreeTicketMicros
        // 惰性更新 落后的nextFreeTicketMicros 为当前时间, 也就是如果当前时间已经大于nextFreeTicketMicros,则更新 nextFreeTicketMicros 为当前时间
        // 惰性更新 storedPermits 加上(当前时间-落后的nextFreeTicketMicros)过程中可以生成多少令牌,作为存储的令牌,不会超过 maxPermits 最大令牌数
        this.resync(this.readSafeMicros());
        // 生成一个令牌需要的时间(微秒): stableIntervalMicros = 1 * 1000 * 1000 / 每秒钟生成多少个请求
        double stableIntervalMicros = (double)TimeUnit.SECONDS.toMicros(1L) / permitsPerSecond;
        this.stableIntervalMicros = stableIntervalMicros;
        // 生成最大令牌数目: maxPermits = 1L * 每秒钟生成多少个请求
        this.doSetRate(permitsPerSecond, stableIntervalMicros);
      }
    }
    
    
    private void resync(long nowMicros) {
      if (nowMicros > this.nextFreeTicketMicros) {
        this.storedPermits = Math.min(this.maxPermits, this.storedPermits + (double)(nowMicros - this.nextFreeTicketMicros) / this.stableIntervalMicros);
        this.nextFreeTicketMicros = nowMicros;
      }
    }
    
    
    void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
      double oldMaxPermits = this.maxPermits;
      this.maxPermits = this.maxBurstSeconds * permitsPerSecond;
      this.storedPermits = oldMaxPermits == 0.0D ? 0.0D : this.storedPermits * this.maxPermits / oldMaxPermits;
    }
    

    boolean b = rateLimiter.tryAcquire();

    public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
      long timeoutMicros = unit.toMicros(timeout);
      // 校验一下permits是不是负数
      checkPermits(permits);
      long microsToWait;
      // 使用 mutex 作为锁
      synchronized(this.mutex) {
        long nowMicros = this.readSafeMicros();
        // 如果 下一个请求可以放行的时间 大于 当前时间+允许的偏移时间, 说明还没到时间,不能放行
        if (this.nextFreeTicketMicros > nowMicros + timeoutMicros) {
          return false;
        }
        // 否则就是已经可以放行了
        // 计算 nextFreeTicketMicros - 当前时间,代表这个请求需要等待的时间,作为结果返回microsToWait
        // 同时叠加下一个请求需要的时间 加上 偿还本次借用的令牌需要的时间
        microsToWait = this.reserveNextTicket((double)permits, nowMicros);
      }
    
      // 进行等待 microsToWait, Thread.sleep 一段时间
      this.ticker.sleepMicrosUninterruptibly(microsToWait);
      // 获取令牌成功
      return true;
    }
    
    
    
    // requiredPermits : 这次请求总共需要多少个令牌
    private long reserveNextTicket(double requiredPermits, long nowMicros) {
      // 落后的nextFreeTicketMicros ,意味着 nowMicros > this.nextFreeTicketMicros
      // 惰性更新 落后的nextFreeTicketMicros 为当前时间
      // 惰性更新 storedPermits 为 (当前时间-落后的nextFreeTicketMicros)过程中产生的 令牌数目
      this.resync(nowMicros);
      // microsToNextFreeTicket (需要等待的时间)  = 下一个请求可以放行的时间 - 当前时间
      long microsToNextFreeTicket = this.nextFreeTicketMicros - nowMicros;
      // storedPermitsToSpend (对于这次请求,当下可以立刻提供多少个令牌) = Math.min(requiredPermits, this.storedPermits);
      double storedPermitsToSpend = Math.min(requiredPermits, this.storedPermits);
      // freshPermits (对于这次请求,还需要提供多少个令牌) = requiredPermits - storedPermitsToSpend;
      double freshPermits = requiredPermits - storedPermitsToSpend;
      // 需要等待的时间 = 从 storedPermits 中借出 storedPermitsToSpend 需要的时间(默认为0) +  还需要多少个令牌 * 生成一个令牌需要的时间
      long waitMicros = this.storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long)(freshPermits * this.stableIntervalMicros);
      // 更新 下一个请求可以放行的时间 加上 重新生成借用的令牌需要的时间
      this.nextFreeTicketMicros += waitMicros;
      // 令牌消耗
      this.storedPermits -= storedPermitsToSpend;
      return microsToNextFreeTicket;
    }
    
    
    long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
      return 0L;
    }
    

    rateLimiter.acquire();

    public double acquire(int permits) {
      // 校验一下permits是不是负数
      checkPermits(permits);
      long microsToWait;
       // 使用 mutex 作为锁
      synchronized (mutex) {
         // 获取这个请求需要等待多长时间,更新下次请求需要等待的时间
        microsToWait = reserveNextTicket(permits, readSafeMicros());
      }
      // 进行等待 microsToWait, Thread.sleep 一段时间  
      ticker.sleepMicrosUninterruptibly(microsToWait);
      return 1.0 * microsToWait / TimeUnit.SECONDS.toMicros(1L);
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值