限流算法之漏桶算法、令牌桶算法

昨天CodeReview的时候看到同时使用RateLimiter这个类用作QPS访问限制.学习一下这个类.

RateLimiter是Guava的concurrent包下的一个用于限制访问频率的类.

1.限流

每个API接口都是有访问上限的,当访问频率或者并发量超过其承受范围时候,我们就必须考虑限流来保证接口的可用性或者降级可用性.即接口也需要安装上保险丝,以防止非预期的请求对系统压力过大而引起的系统瘫痪.

通常的策略就是拒绝多余的访问,或者让多余的访问排队等待服务,或者引流.

如果要准确的控制QPS,简单的做法是维护一个单位时间内的Counter,如判断单位时间已经过去,则将Counter重置零.此做法被认为没有很好的处理单位时间的边界,比如在前一秒的最后一毫秒里和下一秒的第一毫秒都触发了最大的请求数,将目光移动一下,就看到在两毫秒内发生了两倍的QPS.

2.限流算法

常用的更平滑的限流算法有两种:漏桶算法和令牌桶算法.

很多传统的服务提供商如华为中兴都有类似的专利,参考: http://www.google.com/patents/CN1536815A?cl=zh

2.1 漏桶算法

漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:

可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate),伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  1. double rate; // leak rate in calls/s

  2.  

    double burst; // bucket size in calls

  3.  

  4.  

    long refreshTime; // time for last water refresh

  5.  

    double water; // water count at refreshTime

  6.  

  7.  

    refreshWater() {

  8.  

    long now = getTimeOfDay();

  9.  

  10.  

    //水随着时间流逝,不断流走,最多就流干到0.

  11.  

    water = max(0, water- (now - refreshTime)*rate);

  12.  

    refreshTime = now;

  13.  

    }

  14.  

  15.  

    bool permissionGranted() {

  16.  

    refreshWater();

  17.  

    if (water < burst) { // 水桶还没满,继续加1

  18.  

    water ++;

  19.  

    return true;

  20.  

    } else {

  21.  

    return false;

  22.  

    }

  23.  

    }

因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率.因此,漏桶算法对于存在突发特性的流量来说缺乏效率.

2.2 令牌桶算法

令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.

令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.

3.RateLimiter简介

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法(Token Bucket)来完成限流,非常易于使用.RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率.它支持两种获取permits接口,一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到.

RateLimiter和Java中的信号量(java.util.concurrent.Semaphore)类似,Semaphore通常用于限制并发量.

源码注释中的一个例子,比如我们有很多任务需要执行,但是我们不希望每秒超过两个任务执行,那么我们就可以使用RateLimiter:

1
2
3
4
5
6
7
  1. final RateLimiter rateLimiter = RateLimiter.create(2.0);

  2.  

    void submitTasks(List<Runnable> tasks, Executor executor) {

  3.  

    for (Runnable task : tasks) {

  4.  

    rateLimiter.acquire(); // may wait

  5.  

    executor.execute(task);

  6.  

    }

  7.  

    }

另外一个例子,假如我们会产生一个数据流,然后我们想以每秒5kb的速度发送出去.我们可以每获取一个令牌(permit)就发送一个byte的数据,这样我们就可以通过一个每秒5000个令牌的RateLimiter来实现:

1
2
3
4
5
  1. final RateLimiter rateLimiter = RateLimiter.create(5000.0);

  2.  

    void submitPacket(byte[] packet) {

  3.  

    rateLimiter.acquire(packet.length);

  4.  

    networkService.send(packet);

  5.  

    }

另外,我们也可以使用非阻塞的形式达到降级运行的目的,即使用非阻塞的tryAcquire()方法:

1
2
3
4
5
  1. if(limiter.tryAcquire()) { //未请求到limiter则立即返回false

  2.  

    doSomething();

  3.  

    }else{

  4.  

    doSomethingElse();

  5.  

    }

4.RateLimiter主要接口

RateLimiter其实是一个abstract类,但是它提供了几个static方法用于创建RateLimiter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. /**

  2.  

    * 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求

  3.  

    * 当请求到来的速度超过了permitsPerSecond,保证每秒只处理permitsPerSecond个请求

  4.  

    * 当这个RateLimiter使用不足(即请求到来速度小于permitsPerSecond),会囤积最多permitsPerSecond个请求

  5.  

    */

  6.  

    public static RateLimiter create(double permitsPerSecond);

  7.  

  8.  

    /**

  9.  

    * 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求

  10.  

    * 还包含一个热身期(warmup period),热身期内,RateLimiter会平滑的将其释放令牌的速率加大,直到起达到最大速率

  11.  

    * 同样,如果RateLimiter在热身期没有足够的请求(unused),则起速率会逐渐降低到冷却状态

  12.  

    *

  13.  

    * 设计这个的意图是为了满足那种资源提供方需要热身时间,而不是每次访问都能提供稳定速率的服务的情况(比如带缓存服务,需要定期刷新缓存的)

  14.  

    * 参数warmupPeriod和unit决定了其从冷却状态到达最大速率的时间

  15.  

    */

  16.  

    public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit);

提供了两个获取令牌的方法,不带参数表示获取一个令牌.如果没有令牌则一直等待,返回等待的时间(单位为秒),没有被限流则直接返回0.0:

1
2
3
  1. public double acquire();

  2.  

  3.  

    public double acquire(int permits);

尝试获取令牌,分为待超时时间和不带超时时间两种:

1
2
3
4
5
6
  1. public boolean tryAcquire();

  2.  

    //尝试获取一个令牌,立即返回

  3.  

    public boolean tryAcquire(int permits);

  4.  

    public boolean tryAcquire(long timeout, TimeUnit unit);

  5.  

    //尝试获取permits个令牌,带超时时间

  6.  

    public boolean tryAcquire(int permits, long timeout, TimeUnit unit);

5.RateLimiter设计

考虑一下RateLimiter是如何设计的,并且为什么要这样设计.

RateLimiter的主要功能就是提供一个稳定的速率,实现方式就是通过限制请求流入的速度,比如计算请求等待合适的时间阈值.

实现QPS速率的最简单的方式就是记住上一次请求的最后授权时间,然后保证1/QPS秒内不允许请求进入.比如QPS=5,如果我们保证最后一个被授权请求之后的200ms的时间内没有请求被授权,那么我们就达到了预期的速率.如果一个请求现在过来但是最后一个被授权请求是在100ms之前,那么我们就要求当前这个请求等待100ms.按照这个思路,请求15个新令牌(许可证)就需要3秒.

有一点很重要:上面这个设计思路的RateLimiter记忆非常的浅,它的脑容量非常的小,只记得上一次被授权的请求的时间.如果RateLimiter的一个被授权请求q之前很长一段时间没有被使用会怎么样?这个RateLimiter会立马忘记过去这一段时间的利用不足,而只记得刚刚的请求q.

过去一段时间的利用不足意味着有过剩的资源是可以利用的.这种情况下,RateLimiter应该加把劲(speed up for a while)将这些过剩的资源利用起来.比如在向网络中发生数据的场景(限流),过去一段时间的利用不足可能意味着网卡缓冲区是空的,这种场景下,我们是可以加速发送来将这些过程的资源利用起来.

另一方面,过去一段时间的利用不足可能意味着处理请求的服务器对即将到来的请求是准备不足的(less ready for future requests),比如因为很长一段时间没有请求当前服务器的cache是陈旧的,进而导致即将到来的请求会触发一个昂贵的操作(比如重新刷新全量的缓存).

为了处理这种情况,RateLimiter中增加了一个维度的信息,就是过去一段时间的利用不足(past underutilization),代码中使用storedPermits变量表示.当没有利用不足这个变量为0,最大能达到maxStoredPermits(maxStoredPermits表示完全没有利用).因此,请求的令牌可能从两个地方来:

  1. 1.过去剩余的令牌(stored permits, 可能没有)

  2.  

    2.现有的令牌(fresh permits,当前这段时间还没用完的令牌)

我们将通过一个例子来解释它是如何工作的:

对一个每秒产生一个令牌的RateLimiter,每有一个没有使用令牌的一秒,我们就将storedPermits加1,如果RateLimiter在10秒都没有使用,则storedPermits变成10.0.这个时候,一个请求到来并请求三个令牌(acquire(3)),我们将从storedPermits中的令牌为其服务,storedPermits变为7.0.这个请求之后立马又有一个请求到来并请求10个令牌,我们将从storedPermits剩余的7个令牌给这个请求,剩下还需要三个令牌,我们将从RateLimiter新产生的令牌中获取.我们已经知道,RateLimiter每秒新产生1个令牌,就是说上面这个请求还需要的3个请求就要求其等待3秒.

想象一个RateLimiter每秒产生一个令牌,现在完全没有使用(处于初始状态),限制一个昂贵的请求acquire(100)过来.如果我们选择让这个请求等待100秒再允许其执行,这显然很荒谬.我们为什么什么也不做而只是傻傻的等待100秒,一个更好的做法是允许这个请求立即执行(和acquire(1)没有区别),然后将随后到来的请求推迟到正确的时间点.这种策略,我们允许这个昂贵的任务立即执行,并将随后到来的请求推迟100秒.这种策略就是让任务的执行和等待同时进行.

一个重要的结论:RateLimiter不会记最后一个请求,而是即下一个请求允许执行的时间.这也可以很直白的告诉我们到达下一个调度时间点的时间间隔.然后定一个一段时间未使用的Ratelimiter也很简单:下一个调度时间点已经过去,这个时间点和现在时间的差就是Ratelimiter多久没有被使用,我们会将这一段时间翻译成storedPermits.所有,如果每秒钟产生一个令牌(rate==1),并且正好每秒来一个请求,那么storedPermits就不会增长.

6.RateLimiter主要源码

RateLimiter定义了两个create函数用于构建不同形式的RateLimiter:

  1. 1.public static RateLimiter create(double permitsPerSecond)

  2.  

    用于创建SmoothBursty类型的RateLimiter

  3.  

    2.public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)

  4.  

    用于创建

  5.  

源码下面以acquire为例子,分析一下RateLimiter如何实现限流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. public double acquire() {

  2.  

    return acquire(1);

  3.  

    }

  4.  

    public double acquire(int permits) {

  5.  

    long microsToWait = reserve(permits);

  6.  

    stopwatch.sleepMicrosUninterruptibly(microsToWait);

  7.  

    return 1.0 * microsToWait / SECONDS.toMicros(1L);

  8.  

    }

  9.  

    final long reserve(int permits) {

  10.  

    checkPermits(permits);

  11.  

    synchronized (mutex()) { //应对并发情况需要同步

  12.  

    return reserveAndGetWaitLength(permits, stopwatch.readMicros());

  13.  

    }

  14.  

    }

  15.  

    final long reserveAndGetWaitLength(int permits, long nowMicros) {

  16.  

    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);

  17.  

    return max(momentAvailable - nowMicros, 0);

  18.  

    }

下面方法来自RateLimiter的具体实现类SmoothRateLimiter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {

  2.  

    resync(nowMicros); //补充令牌

  3.  

    long returnValue = nextFreeTicketMicros;

  4.  

    //这次请求消耗的令牌数目

  5.  

    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);

  6.  

    double freshPermits = requiredPermits - storedPermitsToSpend;

  7.  

  8.  

    long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)

  9.  

    + (long) (freshPermits * stableIntervalMicros);

  10.  

  11.  

    this.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;

  12.  

    this.storedPermits -= storedPermitsToSpend;

  13.  

    return returnValue;

  14.  

    }

  15.  

    private void resync(long nowMicros) {

  16.  

    // if nextFreeTicket is in the past, resync to now

  17.  

    if (nowMicros > nextFreeTicketMicros) {

  18.  

    storedPermits = min(maxPermits,

  19.  

    storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);

  20.  

    nextFreeTicketMicros = nowMicros;

  21.  

    }

  22.  

    }

另外,对于storedPermits的使用,RateLimiter存在两种策略,二者区别主要体现在使用storedPermits时候需要等待的时间。这个逻辑由storedPermitsToWaitTime函数实现:

1
2
3
4
5
6
7
8
9
  1. /**

  2.  

    * Translates a specified portion of our currently stored permits which we want to

  3.  

    * spend/acquire, into a throttling time. Conceptually, this evaluates the integral

  4.  

    * of the underlying function we use, for the range of

  5.  

    * [(storedPermits - permitsToTake), storedPermits].

  6.  

    *

  7.  

    * <p>This always holds: {@code 0 <= permitsToTake <= storedPermits}

  8.  

    */

  9.  

    abstract long storedPermitsToWaitTime(double storedPermits, double permitsToTake);

存在两种策略就是为了应对我们上面讲到的,存在资源使用不足大致分为两种情况: (1).资源确实使用不足,这些剩余的资源我们私海可以使用的; (2).提供资源的服务过去还没准备好,比如服务刚启动等;

为此,RateLimiter实际上由两种实现策略,其实现分别见SmoothBursty和SmoothWarmingUp。二者主要的区别就是storedPermitsToWaitTime实现以及maxPermits数量的计算。

6.1 SmoothBursty

SmoothBursty使用storedPermits不需要额外等待时间。并且默认maxBurstSeconds未1,因此maxPermits为permitsPerSecond,即最多可以存储1秒的剩余令牌,比如QPS=5,则maxPermits=5.

下面这个RateLimiter的入口就是用来创建SmoothBursty类型的RateLimiter,

1
public static RateLimiter create(double permitsPerSecond)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  1. /**

  2.  

    * This implements a "bursty" RateLimiter, where storedPermits are translated to

  3.  

    * zero throttling. The maximum number of permits that can be saved (when the RateLimiter is

  4.  

    * unused) is defined in terms of time, in this sense: if a RateLimiter is 2qps, and this

  5.  

    * time is specified as 10 seconds, we can save up to 2 * 10 = 20 permits.

  6.  

    */

  7.  

    static final class SmoothBursty extends SmoothRateLimiter {

  8.  

    /** The work (permits) of how many seconds can be saved up if this RateLimiter is unused? */

  9.  

    final double maxBurstSeconds;

  10.  

  11.  

    SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {

  12.  

    super(stopwatch);

  13.  

    this.maxBurstSeconds = maxBurstSeconds;

  14.  

    }

  15.  

  16.  

    void doSetRate(double permitsPerSecond, double stableIntervalMicros) {

  17.  

    double oldMaxPermits = this.maxPermits;

  18.  

    maxPermits = maxBurstSeconds * permitsPerSecond;

  19.  

    System.out.println("maxPermits=" + maxPermits);

  20.  

    if (oldMaxPermits == Double.POSITIVE_INFINITY) {

  21.  

    // if we don‘t special-case this, we would get storedPermits == NaN, below

  22.  

    storedPermits = maxPermits;

  23.  

    } else {

  24.  

    storedPermits = (oldMaxPermits == 0.0)

  25.  

    ? 0.0 // initial state

  26.  

    : storedPermits * maxPermits / oldMaxPermits;

  27.  

    }

  28.  

    }

  29.  

  30.  

    long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {

  31.  

    return 0L;

  32.  

    }

  33.  

    }

一个简单的使用示意图及解释,下面私海一个QPS=4的SmoothBursty:

  1. (1).t=0,这时候storedPermits=0,请求1个令牌,等待时间=0;

  2.  

    (2).t=1,这时候storedPermits=3,请求3个令牌,等待时间=0;

  3.  

    (3).t=2,这时候storedPermits=4,请求10个令牌,等待时间=0,超前使用了2个令牌;

  4.  

    (4).t=3,这时候storedPermits=0,请求1个令牌,等待时间=0.5;

代码的输出:

1
2
3
4
5
6
7
8
9
10
11
  1. maxPermits=4.0, storedPermits=7.2E-4, stableIntervalMicros=250000.0, nextFreeTicketMicros=1472

  2.  

    acquire(1), sleepSecond=0.0

  3.  

  4.  

    maxPermits=4.0, storedPermits=3.012212, stableIntervalMicros=250000.0, nextFreeTicketMicros=1004345

  5.  

    acquire(3), sleepSecond=0.0

  6.  

  7.  

    maxPermits=4.0, storedPermits=4.0, stableIntervalMicros=250000.0, nextFreeTicketMicros=2004668

  8.  

    acquire(10), sleepSecond=0.0

  9.  

  10.  

    maxPermits=4.0, storedPermits=0.0, stableIntervalMicros=250000.0, nextFreeTicketMicros=3504668

  11.  

    acquire(1), sleepSecond=0.499591

6.2 SmoothWarmingUp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  1. static final class SmoothWarmingUp extends SmoothRateLimiter {

  2.  

    private final long warmupPeriodMicros;

  3.  

    /**

  4.  

    * The slope of the line from the stable interval (when permits == 0), to the cold interval

  5.  

    * (when permits == maxPermits)

  6.  

    */

  7.  

    private double slope;

  8.  

    private double halfPermits;

  9.  

  10.  

    SmoothWarmingUp(SleepingStopwatch stopwatch, long warmupPeriod, TimeUnit timeUnit) {

  11.  

    super(stopwatch);

  12.  

    this.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod);

  13.  

    }

  14.  

  15.  

    @Override

  16.  

    void doSetRate(double permitsPerSecond, double stableIntervalMicros) {

  17.  

    double oldMaxPermits = maxPermits;

  18.  

    maxPermits = warmupPeriodMicros / stableIntervalMicros;

  19.  

    halfPermits = maxPermits / 2.0;

  20.  

    // Stable interval is x, cold is 3x, so on average it‘s 2x. Double the time -> halve the rate

  21.  

    double coldIntervalMicros = stableIntervalMicros * 3.0;

  22.  

    slope = (coldIntervalMicros - stableIntervalMicros) / halfPermits;

  23.  

    if (oldMaxPermits == Double.POSITIVE_INFINITY) {

  24.  

    // if we don‘t special-case this, we would get storedPermits == NaN, below

  25.  

    storedPermits = 0.0;

  26.  

    } else {

  27.  

    storedPermits = (oldMaxPermits == 0.0)

  28.  

    ? maxPermits // initial state is cold

  29.  

    : storedPermits * maxPermits / oldMaxPermits;

  30.  

    }

  31.  

    }

  32.  

  33.  

    @Override

  34.  

    long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {

  35.  

    double availablePermitsAboveHalf = storedPermits - halfPermits;

  36.  

    long micros = 0;

  37.  

    // measuring the integral on the right part of the function (the climbing line)

  38.  

    if (availablePermitsAboveHalf > 0.0) {

  39.  

    double permitsAboveHalfToTake = min(availablePermitsAboveHalf, permitsToTake);

  40.  

    micros = (long) (permitsAboveHalfToTake * (permitsToTime(availablePermitsAboveHalf)

  41.  

    + permitsToTime(availablePermitsAboveHalf - permitsAboveHalfToTake)) / 2.0);

  42.  

    permitsToTake -= permitsAboveHalfToTake;

  43.  

    }

  44.  

    // measuring the integral on the left part of the function (the horizontal line)

  45.  

    micros += (stableIntervalMicros * permitsToTake);

  46.  

    return micros;

  47.  

    }

  48.  

  49.  

    private double permitsToTime(double permits) {

  50.  

    return stableIntervalMicros + permits * slope;

  51.  

    }

  52.  

    }

maxPermits等于热身(warmup)期间能产生的令牌数,比如QPS=4,warmup为2秒,则maxPermits=8.halfPermits为maxPermits的一半.

参考注释中的神图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. * ^ throttling

  2.  

    * |

  3.  

    * 3*stable + /

  4.  

    * interval | /.

  5.  

    * (cold) | / .

  6.  

    * | / . <-- "warmup period" is the area of the trapezoid between

  7.  

    * 2*stable + / . halfPermits and maxPermits

  8.  

    * interval | / .

  9.  

    * | / .

  10.  

    * | / .

  11.  

    * stable +----------/ WARM . }

  12.  

    * interval | . UP . } <-- this rectangle (from 0 to maxPermits, and

  13.  

    * | . PERIOD. } height == stableInterval) defines the cooldown period,

  14.  

    * | . . } and we want cooldownPeriod == warmupPeriod

  15.  

    * |---------------------------------> storedPermits

  16.  

    * (halfPermits) (maxPermits)

  17.  

    *

下面是我们QPS=4,warmup为2秒时候对应的图。 

maxPermits=8,halfPermits=4,和SmoothBursty相同的请求序列:

  1. (1).t=0,这时候storedPermits=8,请求1个令牌,使用1个storedPermits消耗时间=1×(0.75+0.625)/2=0.6875秒;

  2.  

    (2).t=1,这时候storedPermits=8,请求3个令牌,使用3个storedPermits消耗时间=3×(0.75+0.375)/2=1.6875秒(注意已经超过1秒了,意味着下次产生新Permit时间为2.6875);

  3.  

    (3).t=2,这时候storedPermits=5,请求10个令牌,使用5个storedPermits消耗时间=1×(0.375+0.25)/2+4*0.25=1.3125秒,再加上额外请求的5个新产生的Permit需要消耗=5*0.25=1.25秒,即总共需要耗时2.5625秒,则下一次产生新的Permit时间为2.6875+2.5625=5.25,注意当前请求私海2.6875才返回的,之前一直阻塞;

  4.  

    (4).t=3,因为前一个请求阻塞到2.6875,实际这个请求3.6875才到达RateLimiter,请求1个令牌,storedPermits=0,下一次产生新Permit时间为5.25,因此总共需要等待5.25-3.6875=1.5625秒;

实际执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. warmupPeriodMicros=2000000

  2.  

    stableIntervalMicros=250000.0, maxPermits=8.0, halfPermits=4.0, coldIntervalMicros=750000.0, slope=125000.0, storedPermits=8.0

  3.  

  4.  

    maxPermits=8.0, storedPermits=8.0, stableIntervalMicros=250000.0, nextFreeTicketMicros=1524

  5.  

    acquire(1), sleepSecond=0.0

  6.  

  7.  

    maxPermits=8.0, storedPermits=8.0, stableIntervalMicros=250000.0, nextFreeTicketMicros=1001946

  8.  

    acquire(3), sleepSecond=0.0

  9.  

  10.  

    maxPermits=8.0, storedPermits=5.0, stableIntervalMicros=250000.0, nextFreeTicketMicros=2689446

  11.  

    acquire(10), sleepSecond=0.687186

  12.  

  13.  

    maxPermits=8.0, storedPermits=0.0, stableIntervalMicros=250000.0, nextFreeTicketMicros=5251946

  14.  

    acquire(1), sleepSecond=1.55

原文地址:https://www.cnblogs.com/SUNSHINEC/p/9577682.html

时间: 2024-10-10 21:49:28

限流算法之漏桶算法、令牌桶算法的相关文章

程序收藏不看系列:一文轻松搞定系统限流

1. 我们为什么需要限流 为了"反脆弱",在微服务复杂拓扑的情况下,限流是保障服务弹性和拓扑健壮的重中之重. 想一想,如果业务推出了一个秒杀活动,而你没有任何的限流措施:当你搭建了一个账号平台,而完全没有对十几个业务方设定流量配额--这些很有可能在特定场合下给你的产品带来大量的业务损失和口碑影响. 我们通常重点关注产品业务层面正向和逆向功能的完成,而对于逆向技术保障,这一点则是企业发展过程中很容易忽视的,所以一旦业务快速增长,这将给你的产品带来很大的隐患. 当然,也不是所有的系统都需要

微服务架构下的分布式限流方案思考

1.微服务限流 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.缓存.降级和限流是保护微服务系统运行稳定性的三大利器.缓存的目的是提升系统访问速度和增大系统能处理的容量,而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开,而有些场景并不能用缓存和降级来解决,比如稀缺资源.数据库的写操作.频繁的复杂查询,因此需有一种手段来限制这些场景的请求量,即限流. 比如当我们设计了一个函数,准备上线,这时候这个函数会消耗一些资源,处理上限是1秒服务3000个QPS

Redis 实现限流的三种方式

项目中用到了限流,受限于一些实现方式上的东西,手撕了一个简单的服务端限流器. 服务端限流和客户端限流的区别,简单来说就是: 1)服务端限流 对接口请求进行限流,限制的是单位时间内请求的数量,目的是通过有损来换取高可用. 例如我们的场景是,有一个服务接收请求,处理之后,将数据bulk到Elasticsearch中进行索引存储,bulk索引是一个很耗费资源的操作,如果遭遇到请求流量激增,可能会压垮Elasticsearch(队列阻塞,内存激增),所以需要对流量的峰值做一个限制. 2)客户端限流 限制

使用springcloud gateway搭建网关(分流,限流,熔断)

Spring Cloud Gateway Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式. Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter

高并发限流算法

开篇 在高并发系统中,有很多手段来保护系统,如缓存.降级和限流等. 缓存:让数据尽早进入缓存,离程序近一点,不要大量频繁的访问DB,可提供系统访问速度和增大系统处理能力. 降级:当服务出问题或者影响到核心流程的性能,需要将服务暂时屏蔽掉,待高峰期过去或问题解决后再启用. 然后,有些场景不能用缓存和降级来解决.比如电商的双十一,用户的购买,下单等行为,是涉及到大量写操作,而且是核心链路,无法降级的. 限流:通过把并发访问/请求进行限速或者一定时间窗口内的请求限制在一定范围内来保护系统,一旦达到限制

coding++:Semaphore—RateLimiter-漏桶算法-令牌桶算法

java中对于生产者消费者模型,或者小米手机营销 1分钟卖多少台手机等都存在限流的思想在里面. 关于限流 目前存在两大类,从线程个数(jdk1.5 Semaphore)和RateLimiter速率(guava) Semaphore:从线程个数限流 RateLimiter:从速率限流  目前常见的算法是漏桶算法和令牌算法 令牌桶算法.相比漏桶算法而言区别在于,令牌桶是会去匀速的生成令牌,拿到令牌才能够进行处理,类似于匀速往桶里放令牌 漏桶算法是:生产者消费者模型,生产者往木桶里生产数据,消费者按照

理解流量监管和整形的关键算法—令牌桶

理解流量监管和整形的关键算法-令牌桶 无论是流量监管还是流量整形都提到一个超额流量的问题,而前面已经描述了监管和整形对超额流量的处理方式不同,监管丢弃或者重标记,流量整形是缓存,通过加大延迟的方式发送平滑的数据流量,那么网络设备怎么去确定这个超额流量,难道链路的带宽为512K,而此时用户以每秒768KB/s发送数据,使用768-512就256KB,难道超额的流量就是256KB吗?不是的,这样做是一种错误的理解,要确定用户的超额流量必须使用如下两种算法中的一种来确定,一种叫漏桶算法(leaky b

限流、消峰的三种办法

互联网服务赖以生存的根本是流量, 产品和运营会经常通过各种方式来为应用倒流,比如淘宝的双十一等,如何让系统在处理高并发的同时还是保证自身系统的稳定,通常在最短时间内提高并发的做法就是加机器,但是如果机器不够怎么办?那就需要做业务降级或系统限流. 流量控制中用的比较多的三个算法就是令牌桶.漏桶.计数器. 1.令牌桶限流(TokenBucket)令牌桶算法的基本过程如下: 每秒会有 r 个令牌放入桶中,或者说,每过 1/r 秒桶中增加一个令牌. 桶中最多存放 b 个令牌,如果桶满了,新放入的令牌会被

从构建分布式秒杀系统聊聊限流的多种实现

前言 俗话说的好,冰冻三尺非一日之寒,滴水穿石非一日之功,罗马也不是一天就建成的.两周前秒杀案例初步成型,分享到了中国最大的同×××友网站-码云.同时也收到了不少小伙伴的建议和投诉.我从不认为分布式.集群.秒杀这些就应该是大厂的专利,在互联网的今天无论什么时候都要时刻武装自己,只有这样,也许你的春天就在明天. 在开发秒杀系统案例的过程中,前面主要分享了队列.缓存.锁和分布式锁以及静态化等等.缓存的目的是为了提升系统访问速度和增强系统的处理能力:分布式锁解决了集群下数据的安全一致性问题:静态化无疑