写于:2019-08-08 21:52:37

# 参考资料

ReateLimiter 官方Demo

ReateLimiter 相关文档中文版

《亿级流量网站架构核心技术》张开涛 限流详解

# 令牌桶限流

令牌桶图解

上图为令牌桶的原理图,根据原理图能够得知:

# 令牌桶的组成部分

1、令牌 - 生成 2、令牌 - 存放 3、令牌 - 获取

# 令牌桶的原理

1、令牌生成器:根据设定的令牌生成策略生成令牌,并将令牌存放至令牌存放地(令牌桶)中。

2、令牌获取器:用户请求到达,尝试从令牌桶中获取令牌,如果获取到令牌则放行,如果获取不到则根据限流规则进行友好提示。

# RateLimiter:令牌桶实现工具类

RateLimiter 是 Google 开源工具包 Guava 中针对 令牌桶算法的实现工具类。

# RateLimiter 简单使用案例

public class RateLimiterDemo {
	public static void main(String[] args) throws IOException {
        // 令牌获取
        // 1-阻塞获取
        block();
        // 2-非阻塞获取
        non_block();
        System.in.read();
    }

    public static void non_block() {
        // 令牌生成 并放入令牌桶中
        RateLimiter rateLimiter = RateLimiter.create(1.0);
        long startTime = System.currentTimeMillis();
        IntStream.rangeClosed(0,6).forEach(
                item ->{
                    boolean falg = rateLimiter.tryAcquire(1, TimeUnit.MILLISECONDS);
                    System.err.println("成功与否:" + falg);
                    System.err.println("消耗时间:" + (startTime - System.currentTimeMillis()));
                }
        );

    }

    public static void block() {

        // 令牌生成 并放入令牌桶中
        RateLimiter rateLimiter = RateLimiter.create(1.0);
        long startTime = System.currentTimeMillis();
        IntStream.rangeClosed(0,6).forEach(
                item ->{
                    double waiteTime = rateLimiter.acquire(1);
                    System.err.println("等待时间:" + waiteTime);
                    System.err.println("消耗时间:" + (startTime - System.currentTimeMillis()));
                }
        );
    }
}

在演示了 RateLimter 的简单使用之后,我们根据 令牌桶 算法的三个组成部分(令牌生成、令牌存放、令牌获取)对 RateLimter 简单进行源码剖析

# RateLimter 原理

令牌桶算法的理解很简单。令牌生成令牌获取令牌存储,这三部分组成了令牌桶限流算法。

对于令牌桶的实现,存在两种实现方式:

  • 1、定时线程生成令牌方式
  • 2、触发更新令牌方式

定时线程生成令牌方式实现思路

  • 开辟一个空间用来存放令牌,
  • 提供一个线程用来产生令牌并放入令牌存储空间,
  • 提供一个方法,用来尝试获取空间中的令牌。

下面重点来讲讲 RateLimter 通过触发更新的方式 如何实现令牌通算法。

RateLimter 的子类 SmoothBursty 进行原理分析

SmotthBurst工作原理图

图中重要信息如下:

  • 1、nowMicros : 记录代码运行时长

  • 2、nextFreeTicketMicros : nextFreeTicketMicros = 每个令牌生成耗时 * 已被获取的令牌数

SmoothBurstySmoothWarmingUp 类似,SmoothBursty 生成令牌的速度是恒定的,SmoothWarmingUp 在一段时间内的速度是动态的。

SmoothBursty 中:nextFreeTicketMicros = stableIntervalMicros * 已被获取的令牌数 。stableIntervalMicros 为:每个令牌生成平均耗时。

RateLimter 总体的实现方式:将令牌的生成换算成时间,通过时间轴进行划分。以程序运行作为起始,定义两个重要时间 nowMicros nextFreeTicketMicros。 nowMicros 指向当前程序运行时间点,从而根据令牌生成速率得到到目前为止,根据速率令牌桶中应该存放有的令牌数。 nextFreeTicketMicros 表示:已被使用令牌数 换算之后的时间长度。

RateLimter 使用触发更新的方式进行令牌更新,也就是当调用 RateLimter#acquire 进行令牌获取时才会进行令牌桶的相关令牌数的计算更新。而更新的比对条件:nowMicros > nextFreeTicketMicros。 而该更新条件也造就了 RateLimter 所谓的预消费能力。假设两次请求间隔1s,第一次请求就能够预消费 1s 生成的令牌数,因为在第二次请求抵达的时候所被预消费的1s的令牌数因为时间间隔被补齐,第二次请求也无需等待。

# RateLimter 源码解析

以令牌的初始化,和令牌消费 作为入口进行分析。

# 令牌初始化(解析 SmoothBursty)

在 RateLimiter 中有4个重载的令牌生成器构造方法 RateLimiter#create

令牌生成_create重载方法

我们再来看看源码,比对一下这 4 个 RateLimiter#create 方法

令牌生成_create重载方法归类图解

通过源码的比对,能够得到,这四个令牌生成的方法,最终构造的 RateLimter 对象,分为两种。

  • SmoothWarmingUp 平滑预热限流
  • SmoothBursty 平滑突发限流

先来看看 SmoothWarmingUpSmoothBurstyRateLimter 的关系

Ratelimiter类结构

通过查看类结构关系,能够直观的知道,RateLimter 有一个直属的子类 SmoothRteLimiter ,而 SmoothWarmingUpSmoothBursty 继承自 SmoothRteLimiter (同时也是SmoothRteLimiter 内部类)

在对 SmoothWarmingUpSmoothBursty 这两个令牌生成类进行解析前,先来简单看看他们的父类 RateLimiterSmoothRteLimiter

# RateLimiter

RateLimiter 相关属性定义

  • private final SleepingStopwatch stopwatch; guava一个时钟,提供两种功能:获取代码运行时长 和 提供 sleep 功能。
  • private volatile Object mutexDoNotUseDirectly; 该对象是一个锁对象,用来作为相关操作的锁对象。 而获取锁的操作方法为 RateLimiter#mutex

再来看看 相关的方法调用

  • RateLimiter#create 令牌生成构造方法

  • RateLimiter#acquire 阻塞方式,获取令牌

  • RateLimiter#tryAcquire 非阻塞方式,获取令牌

RateLimter 提供了令牌桶相关的操作方法,具体的实现由其子类完成。

# SmoothRateLimiter

SmoothRateLimiter 相关属性

  • double storedPermits; 当前存储令牌数
  • double maxPermits; 存储最大令牌数
  • double stableIntervalMicros; 每个令牌的稳定生成速率。例如,每秒5个许可证的稳定速率的稳定间隔为200ms。也就是令牌以每200ms一个速率生成。
  • private long nextFreeTicketMicros = 0L; 已被消费的令牌换算的时间轴长度。

# 构造令牌生成器,以 RateLimiter#create 为入口 (解析:SmoothBursty )

RateLimiter#create 是构造令牌生成器的方法,而 RateLimiter#create 必定执行的操作为 RateLimiter#setRate 进行令牌生成速率的设定。

RateLimiter#setRate

public abstract class RateLimiter {
	public final void setRate(double permitsPerSecond) {
    // 校验参数 permitsPerSecond(每秒令牌生成数) 是否合法
    checkArgument( permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
    // 锁对象获取
    synchronized (mutex()) {
      // 真正执行 permitsPerSecond 设定的方法
      // 传递两个参数: 
	  //       permitsPerSecond 令牌生成速率(生成令牌数/s)
      //       stopwatch.readMicros() 当前代码运行时长
      doSetRate(permitsPerSecond, stopwatch.readMicros());
    }
  }
  // 执行 令牌生成速率设置的操作,委派给子类完成
  abstract void doSetRate(double permitsPerSecond, long nowMicros);
}

通过代码能够知道,真正实现的逻辑 委派给了其子类进行 doSetRate 实现。

下面来看看其子类 SmoothRateLimiter#doSetRate 方法实现

abstract class SmoothRateLimiter extends RateLimiter {

  /**
	* @param permitsPerSecond 每秒生成令牌数
	* @param nowMicros 代码运行时长
 	**/
  @Override
  final void doSetRate(double permitsPerSecond, long nowMicros) {
    // 更新 storedPermits(当前存储令牌数) nextFreeTicketMicros(下次能够获取令牌的时间)
    resync(nowMicros);
    // 每个令牌生成消耗时间
    double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
    this.stableIntervalMicros = stableIntervalMicros;
    // 设定令牌生成速率,委派给其子类 SmoothBursty 和 SmoothWarmingUp 实现
    doSetRate(permitsPerSecond, stableIntervalMicros);
  }
  
  // 更新 storedPermits(当前存储令牌数) 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;
    }
  }
}

主要执行逻辑:

  • SmoothRateLimiter#resync 更新令牌桶信息,设置 storedPermits 和 nextFreeTicketMicros。
  • 计算每个令牌评论生成速率 stableIntervalMicros

之后由 SmoothBurstySmoothWarmingUp 根据各自特点进行特殊值的设定 (SmoothBursty#doSetRate)

在查看 SmoothBursty#doSetRate 相关源码前,先来看看 SmoothBursty 相关属性

  • final double maxBurstSeconds; 该值为时间,设定令牌桶最多保存多上时长的令牌数,写死为 1s。 例如:设定令牌生成速率 2/s 。RateLimter 空闲保存 maxBurstSeconds = 3 秒的令牌数。那么当 RateLimter 空闲时,RateLimter 最多存储 2 * 3 = 6 个令牌。 该值的设定,让原本指定 RateLimter 的使用有原本应用,如 50次请求 / 每秒 等 qps 的设定,变得更加灵活。如 50次请求 / 每2秒 。不过在 RateLimiter 中该值写死为 1

下面来看看 SmoothBursty#doSetRate 的执行代码

abstract class SmoothRateLimiter extends RateLimiter {
    static final class SmoothBursty extends SmoothRateLimiter {
        @Override
        void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
          // 保存当前 RateLimter 运行存储的最大令牌数
          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(当前令牌桶的令牌数) = 0
		   // 非初始化时:storedPermits(当前令牌桶的令牌数) = storedPermits * (maxPermits / oldMaxPermits) 当最大令牌数变更,此时令牌桶存放的令牌数,需要根据变更的比率,进行相应变更
            storedPermits =
                (oldMaxPermits == 0.0)
                    ? 0.0 // initial state
                    : storedPermits * maxPermits / oldMaxPermits;
          }
        }
    }
}

代码执行逻辑: 主要设定两个值:

  • maxPermits(最大令牌数) = maxBurstSeconds(令牌保留秒数) * permitsPerSecond(令牌每秒速率
  • storedPermits 令牌数(初始为0.0)

# 令牌消费,以 RateLimiter#acquire为入口 (解析:SmoothBursty )

直接上代码 查看 RateLimiter#acquire

public abstract class RateLimiter {
	public double acquire() { return acquire(1); }
    
    // 参数 permits 为当前需要获取到的令牌数
	public double acquire(int permits) {
         // 计算获取到指定令牌数,需要等待的时长
		long microsToWait = reserve(permits);
         // 进行睡眠等待(最终调用 TimeUnit.sleep 进行睡眠等待)
		stopwatch.sleepMicrosUninterruptibly(microsToWait);
		// 返回等待时长,单位:秒
		return 1.0 * microsToWait / SECONDS.toMicros(1L);
	}
}

RateLimiter#acquire 中最重要的方法就是 RateLimiter#reserve 获取等待时长的方法。该等待时长,指定了本次令牌的消费需要等待的时长。

public abstract class RateLimiter {
    // 参数 permits 为当前需要获取到的令牌数
	final long reserve(int permits) {
		// 要获取的令牌数需要 > 0
		checkPermits(permits);
         // 锁对象
		synchronized (mutex()) {
			return reserveAndGetWaitLength(permits, stopwatch.readMicros());
		}
	}
    
    // 获取指定令牌数,从代码运行开始进行计算,能够获取到令牌数的时长。
	final long reserveAndGetWaitLength(int permits, long nowMicros) {
        // 委派给相关实现子类 SmoothRateLimter 实现
		// 返回 nextFreeTicketMicros
		long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
        // 获取等待时间
		return max(momentAvailable - nowMicros, 0);
	}
}

在 RateLimiter#reserve 中并不实现获取等待时长的具体方法,具体实现有其子类实现 查看 SmoothRateLimter#reserveEarliestAvailable

abstract class SmoothRateLimiter extends RateLimiter {
	@Override
    // 参数:requiredPermits 本次请求需要获取的令牌数
    // 参数: nowMicros ,当前代码运行时长
  	final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
        // 更新 storedPermits(当前存储令牌数) nextFreeTicketMicros(下次能够获取令牌的时间)
        // 与 RateLimter#setRate 中的 resync 方法同
        resync(nowMicros);
		long returnValue = nextFreeTicketMicros;
        // 对比令牌桶中令牌数和此次请求需要获取的令牌数,storedPermitsToSpend 为两个中的最小值
    	double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
        //当令牌桶中的令牌数不够本次请求获取,freshPermits 该值为负数
    	double freshPermits = requiredPermits - storedPermitsToSpend;
        // 计算获取令牌桶需要等待的时长,委派给其子类 SmoothBursty 和 SmoothWarmingUp 实现
    	long waitMicros =
        	storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            	+ (long) (freshPermits * stableIntervalMicros);

    	this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
        // 重新计算令牌桶中的令牌数
    	this.storedPermits -= storedPermitsToSpend;
    	return returnValue;
	}    
}

主线逻辑:

  • SmoothRateLimiter#resync 当满足条件 nowMicros > nextFreeTicketMicros,表示从上一次到此次消费令牌,令牌桶中还存在令牌。执行:刷新令牌桶数据。此时令牌消费等待时间为 0 。

  • 执行完 SmoothRateLimiter#resync 之后,本次消费令牌需要等待的时长,更新 nextFreeTicketMicros

# 令牌获取,以 RateLimiter#tryAcquire为入口 (解析:SmoothBursty )

public abstract class RateLimiter {
	public boolean tryAcquire() { return tryAcquire(1, 0, MICROSECONDS); }
    
	public boolean tryAcquire(int permits) { return tryAcquire(permits, 0, MICROSECONDS); }
    
    public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
         // 是否自定义等待时间
    	long timeoutMicros = max(unit.toMicros(timeout), 0);
    	checkPermits(permits);
    	long microsToWait;
    	synchronized (mutex()) {
             // 代码运行时长
			long nowMicros = stopwatch.readMicros();
			// 令牌是否获取成功
			if (!canAcquire(nowMicros, timeoutMicros)) {
				return false;
			} else {
				microsToWait = reserveAndGetWaitLength(permits, nowMicros);
			}
		}
		stopwatch.sleepMicrosUninterruptibly(microsToWait);
		return true;
	}
    
    // 令牌是否获取成功
    private boolean canAcquire(long nowMicros, long timeoutMicros) {
		return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
	}
}

关键代码 RateLimiter#queryEarliestAvailable 不过该逻辑有其子类 SmoothRateLimiter 完成 。

abstract class SmoothRateLimiter extends RateLimiter {
    @Override
	final long queryEarliestAvailable(long nowMicros) {
		return nextFreeTicketMicros;
	}
}

能够发现 SmoothRateLimiter#queryEarliestAvailable 返回的是 nextFreeTicketMicros。 能够得出 RateLimiter#canAcquire 的计算公式为:nextFreeTicketMicros - timeoutMicros <= nowMicros。 当此时令牌桶中的令牌不足以满足本次消费请求,则直接返回 false 表式令牌获取失败。 否则返回 true ,并进行相关的令牌桶数据更新操作

# SmoothRateLimter 预消费能力

先通过一个简单的计算例子,来看看 SmoothRateLimter 相关获取令牌的计算方式

public class RateLimiterMathDemo {

    public static void main(String[] args) throws InterruptedException {
        smoothBurstyDemo();
    }
    public static void smoothBurstyDemo() throws InterruptedException {
        // 指定 QPS = 5 的令牌桶 , stableIntervalMicros = 200ms = 0.2s
        // 允许突发流量 QPS = 10
        RateLimiter rateLimiter = RateLimiter.create(5.0);
        TimeUnit.SECONDS.sleep(1);

        // 请求 1 个令牌
        double waitMite_1 = rateLimiter.acquire(1);
        System.err.println("请求 1 个令牌,等待时间:" + waitMite_1);
        TimeUnit.SECONDS.sleep(1);

        // 请求 8 个令牌
        double waitMite_2 = rateLimiter.acquire(8);
        System.err.println("请求 8 个令牌,等待时间:" + waitMite_2);
        TimeUnit.SECONDS.sleep(1);

        // 请求 11 个令牌
        double waitMite_3 = rateLimiter.acquire(11);
        System.err.println("请求 11 个令牌,等待时间:" + waitMite_3);
        TimeUnit.SECONDS.sleep(1);

        // 请求 1 个令牌
        double waitMite4 = rateLimiter.acquire(1);
        System.err.println("请求 1 个令牌,等待时间:" + waitMite4);
        TimeUnit.SECONDS.sleep(1);

        // 请求 1 个令牌
        double waitMite5 = rateLimiter.acquire(1);
        System.err.println("请求 1 个令牌,等待时间:" + waitMite5);
        TimeUnit.SECONDS.sleep(1);
    }
}

SmoothBursty计算测试运行结果

相关计算公式:

设定 permitsPerSecond = 5.0,此时: stableIntervalMicros = 200ms = 0.2s

起始状态:
storedPermits = 0.0
maxPermits = 5
nextFreeTicketMicros= 0
nowMicros = 0【模拟整点时间:1s,2s,3s但是在程序真实运行过程中,nowMicros 都会更大一点,如:1.001s 等。】

sleep 1s
==============================================================================================================================
请求一:获取一个令牌 requiredPermits = 1
触发令牌桶更新: ( nowMicros = 1 ) > (nextFreeTicketMicros = 0)
  storedPermits = min( maxPermits ,storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros ) = 5.0
  nextFreeTicketMicros = nowMicros = 1
等待时间:
  等待时间 = returnValue =  nextFreeTicketMicros = max(nextFreeTicketMicros - nowMicros,0) = 0

获取完令牌,令牌桶相关数据更新:
  nextFreeTicketMicros = nextFreeTicketMicros/nowMicros + ( requiredPermits - min(requiredPermits, storedPermits)  * stableIntervalMicros ) = nowMicros - 1
  storedPermits = storedPermits - min(requiredPermits, storedPermits) = 4.0

sleep 1 s

==============================================================================================================================
请求二:获取 8 个令牌 requiredPermits = 8
触发令牌桶更新:( nowMicros = 2 ) > ( nextFreeTicketMicros = 1 )
  storedPermits = min( maxPermits , storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros ) = 5.0
  nextFreeTicketMicros = nowMicros = 2

等待时间:
  等待时间 = returnValue =  nextFreeTicketMicros = max(nextFreeTicketMicros - nowMicros,0) = 0

获取完令牌,令牌桶相关数据更新:
  nextFreeTicketMicros = nextFreeTicketMicros/nowMicros + ( requiredPermits - min(requiredPermits, storedPermits)  * stableIntervalMicros ) = 2 + 0.6 = 2.6
  storedPermits = storedPermits - min(requiredPermits, storedPermits) =  0.0
sleep 1 s

==============================================================================================================================
请求三:获取 11 个令牌 requiredPermits = 11
触发令牌桶更新:( nowMicros = 3 ) > ( nextFreeTicketMicros = 2.6 )
  storedPermits = min( maxPermits , storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros ) = 2.0
  nextFreeTicketMicros = nowMicros = 3

等待时间:
  等待时间 = returnValue =  nextFreeTicketMicros = max(nextFreeTicketMicros - nowMicros,0) = 0

获取完令牌,令牌桶相关数据更新:
  nextFreeTicketMicros = nextFreeTicketMicros/nowMicros + ( requiredPermits - min(requiredPermits, storedPermits)  * stableIntervalMicros ) = 3 + 1.8 = 4.8
  storedPermits = storedPermits - min(requiredPermits, storedPermits) =  0.0

sleep 1s

==============================================================================================================================
请求四:获取 1 个令牌 requiredPermits = 1
( nowMicros = 4 ) < ( nextFreeTicketMicros = 4.8 ) 所以无法触发更新:
所以此时:
  storedPermits = 0.0
  nextFreeTicketMicros = 上一次计算的值 = 4.8

等待时间:
  等待时间 = returnValue =  nextFreeTicketMicros = max(nextFreeTicketMicros - nowMicros,0) = 0.8

获取完令牌,令牌桶相关数据更新:
  nextFreeTicketMicros = nextFreeTicketMicros + ( requiredPermits - min(requiredPermits, storedPermits)  * stableIntervalMicros ) = 4.8 + 0.2 = 5
  storedPermits = storedPermits - min(requiredPermits, storedPermits) =  0.0

==============================================================================================================================

请求五:获取 1 个令牌 requiredPermits = 1
触发令牌桶更新:( nowMicros = 5.001 ) > ( nextFreeTicketMicros = 5 )
  storedPermits = min( maxPermits , storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros ) 接近于 0.0
  nextFreeTicketMicros = nowMicros = 5.001

等待时间:
  等待时间 = returnValue =  nextFreeTicketMicros = max(nextFreeTicketMicros - nowMicros,0) = 0

获取完令牌,令牌桶相关数据更新:
  nextFreeTicketMicros = nextFreeTicketMicros + ( requiredPermits - min(requiredPermits, storedPermits)  * stableIntervalMicros ) 接近于 5.2
  storedPermits = storedPermits - min(requiredPermits, storedPermits) =  0.0

下面是关键计算代码

abstract class SmoothRateLimiter extends RateLimiter {
  @Override
  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    resync(nowMicros);
    long returnValue = nextFreeTicketMicros;
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    double freshPermits = requiredPermits - storedPermitsToSpend;
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);

    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
  }

  void resync(long nowMicros) {
    // 主要判定条件: 如果上一次预消费的令牌数,直到本次调用时仍然没有抵扣,则不进入该分支
    if (nowMicros > nextFreeTicketMicros) {
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
  }
}

假设存在三次获取令牌的请求,第一次获取1个令牌,并且与第二次请求间隔1分钟。 第二次和第三次请求之间间隔 1s。QPS = 5。

  • 场景一:预消费,但是预消费小于QPS。 第二次请求获取 8 个令牌。 nowMicros > nextFreeTicketMicros 所以等待时间为 0 。 此时令牌桶中只有 5 个令牌,而此时请求的是 8 个令牌,预消费了 3 个令牌,相当于预消费了 0.6 s。nextFreeTicketMicros = nowMicros + 0.6

1s过后,第三次请求到来,获取 1 个令牌。 ( nowMicros = nowMicros + 1 ) > (nextFreeTicketMicros = nowMicros + 0.6)。所以等待时间为 0

  • 场景二:预消费,但是预消费大于 QPS。 第二次请求获取 11 个令牌。 nowMicros > nextFreeTicketMicros 所以等待时间为 0 。 此时令牌桶中只有 5 个令牌,而此时请求的是 11 个令牌,预消费了 6 个令牌,相当于预消费了 1.2 s。nextFreeTicketMicros = nowMicros + 1.2

1s过后,第三次请求到来,获取 1 个令牌。 ( nowMicros = nowMicros + 1 ) < (nextFreeTicketMicros = nowMicros + 1.2)。所以等待时间为 0.2s

# SmoothWarmingUp 简介

SmoothBursty 令牌的生成速率是恒定的。 SmoothWarmingUp 令牌的生成速率是变速的。

SmoothWarmup计算图解

# SmoothWarmingUp 区别于 SmoothBursty

在执行 RateLimiter#create 方法时不同的方法

  • SmoothBursty#coolDownIntervalMicros 和 SmoothWarmingUp#coolDownIntervalMicros
  • SmoothBursty#doSetRate 和 SmoothWarmingUp#doSetRate

SmoothBursty:初始令牌数 storedPermits = 0,每个令牌的生成时长 = 1s / permitPerSecond SmoothWarmingUp:初始令牌数 maxPermits,可以指定预热时间 warmUpPeriod,每个令牌的生成时长根据 warmUpPeriod 不同而不同,在预热时间过后速率恢复平稳每个令牌的生成时长 = 1s/ permitPerSecond

在执行 RateLimiter#acquire 方法时不同的方法

  • SmoothBursty#storedPermitsToWaitTime 和 SmoothWarmingUp#storedPermitsToWaitTime

SmoothBursty 总是返回 0 SmoothWarmingUp 返回消耗 storedPermitsToSpend 个令牌所需要耗费的时间

# 总结

RateLimiter 提供了两种令牌桶的实现方式:SmoothBursty 和 SmoothWarmingUp

SmoothBursty 初始化令牌:0,令牌生成速率恒定。

优劣:SmoothBursty 由于其设计方式的原因,其允许预消费的能力。假设:RateLimiter 设定的QPS = 5。则 SmoothBursty 允许突发 QPS = 10 的流量进入到系统中。 由于突发的双倍的流量的涌入可能导致系统的崩溃。

SmoothWarmingUp:初始化令牌:maxPermits ,令牌生成速率较小,后期趋于平滑,直到恒定速率。

优劣:SmoothWarmingUp 首先进行令牌预热,在前期进行令牌的生成速率较小,后期趋于恒定速率。

精彩内容推送,请关注公众号!
最近更新时间: 3/24/2020, 9:44:42 PM