浅析限流算法

计数器

计数器算法的思想很简单,每当一个请求到来时,我们就将计数器加一,当计数器数值超过阈值后,就拒绝余下请求。一秒钟后,我们将计数器清零,开始新一轮的计数。计数器算法简单粗暴,易于实现。但是缺点也是有的,也就是所谓的"突刺现象"。举例说明一下,假如我们给计数器设置的阈值为100。系统瞬间内(比如10毫秒内)有200个请求到来,这个时候计数器只能放过其中的100个请求,余下的100个请求全部被拒绝掉。如果第二秒内没有请求到来,那么系统就处于空闲状态。也就是上一秒忙的要死,这一秒又闲的要死。如果我们能用一个容器将剩余的100个请求缓存起来,待计数器重置后再将这些请求放出来。这样系统在这两秒内的吞吐量就由100变成了200,提升了一倍

漏桶算法

漏桶算法由流量容器、流量入口和出口组成。其中流量出口流速即为我们期望的限速值,比如 100 QPS。漏桶算法除了具备限流能力,还具备流量整型功能。下面我们通过一张图来了解漏桶算法。
在这里插入图片描述
如上图,流入漏桶流量的流速是不恒定的,经过漏桶限速后,流出流量的速度是恒定的。需要说明的是,漏桶的容量是有限的,一旦流入流量超出漏桶容量,这部分流量只能被丢弃了。

令牌桶算法

令牌桶和漏桶颇有几分相似,只不过令牌通里存放的是令牌。它的运行过程是这样的,一个令牌工厂按照设定值定期向令牌桶发放令牌。当令牌桶满了后,多出的令牌会被丢弃掉。每当一个请求到来时,该请求对应的线程会从令牌桶中取令牌。初期由于令牌桶中存放了很多个令牌,因此允许多个请求同时取令牌。当桶中没有令牌后,无法获取到令牌的请求可以丢弃,或者重试。下面我们来看一下的令牌桶示意图:
在这里插入图片描述

guava的RateLimiter

guava中主要有两种限流模式,通过两个工厂方法来创建出两个子类。

  • SmoothBursty稳定模式,主要表现为令牌生成速度恒定
  static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }
  • SmoothWarmingUp渐进式模式,主要表现为生成令牌的速度逐步提升并维持在一个速度
  static RateLimiter create(
      double permitsPerSecond,
      long warmupPeriod,
      TimeUnit unit,
      double coldFactor,
      SleepingStopwatch stopwatch) {
    RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
    rateLimiter.setRate(permitsPerSecond);
    return rateLimiter;
  }
  • 本文主要以SmoothBursty为例,聊一聊源码的实现,首先我们看一下SmoothBursty的参数
  /** 当前令牌桶个数. */
  double storedPermits;

  /** 最大令牌桶数量 */
  double maxPermits;

  /**
   * 添加令牌间隔时间
   */
  double stableIntervalMicros;

  /**
   	* 下一次请求可以获取令牌的起始时间
 	* 由于RateLimiter允许预消费,上次请求预消费令牌后
 	* 下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌
   */
  private long nextFreeTicketMicros = 0L;
  • resync函数

resync函数用于增加存储令牌,核心逻辑就是(nowMicros - nextFreeTicketMicros) / stableIntervalMicros。当前时间大于nextFreeTicketMicros时进行刷新,否则直接返回。

  void resync(long nowMicros) {
    // if nextFreeTicket is in the past, resync to now
    if (nowMicros > nextFreeTicketMicros) {
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
  }
  • reserveEarliestAvailable函数

这个函数的主要作用是增加令牌,另外一个功能主要是刷新令牌数和下次获取令牌时间。这里涉及RateLimiter的一个特性,也就是可以预先支付令牌。主要逻辑在下面注释中

  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  	// 刷新令牌数,相当于每次acquire时在根据时间进行令牌的刷新
    resync(nowMicros);
    long returnValue = nextFreeTicketMicros;
    // 获取当前已有的令牌数和需要获取的目标令牌数进行比较,计算出可以目前即可得到的令牌数。
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    // freshPermits是需要预先支付的令牌,也就是目标令牌数减去目前即可得到的令牌数
    double freshPermits = requiredPermits - storedPermitsToSpend;
    // 因为会突然涌入大量请求,而现有令牌数又不够用,因此会预先支付一定的令牌数
    // waitMicros即是产生预先支付令牌的数量时间,则将下次要添加令牌的时间应该计算时间加上watiMicros
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);

    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    // 更新令牌数,最低数量为0
    this.storedPermits -= storedPermitsToSpend;
    // 返回旧的nextFreeTicketMicros数值,无需为预支付的令牌多加等待时间。
    return returnValue;
  }
  • 构造函数
    SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
      super(stopwatch);
      this.maxBurstSeconds = maxBurstSeconds;
    }

    @Override
    void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
      double oldMaxPermits = this.maxPermits;
      maxPermits = maxBurstSeconds * permitsPerSecond;
      if (oldMaxPermits == Double.POSITIVE_INFINITY) {
        // if we don't special-case this, we would get storedPermits == NaN, below
        storedPermits = maxPermits;
      } else {
        storedPermits =
            (oldMaxPermits == 0.0)
                ? 0.0 // initial state
                : storedPermits * maxPermits / oldMaxPermits;
      }
    }

虽然RateLimiter很好用,但是只适合单机。如果想要集群限流,则需要引入阿里开源的sentinel中间件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值