Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

SmoothBursty:令牌生成速度恒定

  1. @Test
  2. public void testAcquire() {
  3. // acquire(i); 获取令牌,返回阻塞的时间,支持预消费.
  4. RateLimiter limiter = RateLimiter.create(1);
  5.  
  6. for (int i = 1; i < 10; i++) {
  7. double waitTime = limiter.acquire();
  8. System.out.println("cutTime=" + longToDate(System.currentTimeMillis()) + " acq:" + i + " waitTime:" + waitTime);
  9. }
  10. }
  11.  
  12. public static String longToDate(long lo){
  13. Date date = new Date(lo);
  14. SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  15. return sd.format(date);
  16. }

输出结果:

cutTime=2019-03-29 09:31:42 acq:1 waitTime:0.0
cutTime=2019-03-29 09:31:43 acq:2 waitTime:0.989135
cutTime=2019-03-29 09:31:44 acq:3 waitTime:0.998023
cutTime=2019-03-29 09:31:45 acq:4 waitTime:0.999573
cutTime=2019-03-29 09:31:46 acq:5 waitTime:0.999359
cutTime=2019-03-29 09:31:47 acq:6 waitTime:0.999566
cutTime=2019-03-29 09:31:48 acq:7 waitTime:0.998763
cutTime=2019-03-29 09:31:49 acq:8 waitTime:0.999163
cutTime=2019-03-29 09:31:50 acq:9 waitTime:1.000036

说明:每秒1个令牌生成一个令牌,从输出可看出很平滑,这种实现将突发请求速率平均成固定请求速率。

下面demo是突发请求:

  1. @Test
  2. public void testAcquire2() {
  3. // 请求突发
  4. RateLimiter limiter = RateLimiter.create(5);
  5.  
  6. for (int i = 1; i < 5; i++) {
  7. double waitTime = 0;
  8. if(i == 2){
  9. waitTime = limiter.acquire(10);
  10. }else{
  11. waitTime = limiter.acquire(1);
  12. }
  13.  
  14. System.out.println("cutTime=" + longToDate(System.currentTimeMillis()) + " acq:" + i + " waitTime:" + waitTime);
  15. }
  16. }

输出:

cutTime=2019-03-29 09:53:55 acq:1 waitTime:0.0
cutTime=2019-03-29 09:53:56 acq:2 waitTime:0.188901
cutTime=2019-03-29 09:53:58 acq:3 waitTime:1.99789
cutTime=2019-03-29 09:53:58 acq:4 waitTime:0.198832

说明:

i=1,消费i个令牌,此时还剩4个令牌;

i=2,突发10个请求,令牌桶算法也允许了这种突发(允许消费未来的令牌);

i=3,上次请求消费了,所以需要等待2s;

下面看源码:


简单介绍下:Stopwatch

  1. public final class Stopwatch {
  2. private final Ticker ticker;//计时器,用于获取当前时间
  3. private boolean isRunning;//计时器是否运行中的状态标记
  4. private long elapsedNanos;//用于标记从计时器开启到调用统计的方法时过去的时间
  5. private long startTick;//计时器开启的时刻时间
  6.  
  7. private long elapsedNanos() {
  8. return this.isRunning ? this.ticker.read() - this.startTick + this.elapsedNanos : this.elapsedNanos;
  9. }
  10. public long elapsed(TimeUnit desiredUnit) {
  11. return desiredUnit.convert(this.elapsedNanos(), TimeUnit.NANOSECONDS);
  12. }
  13. }

TimeUnit:

  1. MILLISECONDS {
  2. public long toNanos(long d) { return x(d, C2/C0, MAX/(C2/C0)); }
  3. public long toMicros(long d) { return x(d, C2/C1, MAX/(C2/C1)); }
  4. public long toMillis(long d) { return d; }
  5. public long toSeconds(long d) { return d/(C3/C2); }
  6. public long toMinutes(long d) { return d/(C4/C2); }
  7. public long toHours(long d) { return d/(C5/C2); }
  8. public long toDays(long d) { return d/(C6/C2); }
  9. public long convert(long d, TimeUnit u) { return u.toMillis(d); }
  10. int excessNanos(long d, long m) { return 0; }
  11. },
  12.  
  13. MICROSECONDS {
  14. public long toNanos(long d) { return x(d, C1/C0, MAX/(C1/C0)); }
  15. public long toMicros(long d) { return d; }
  16. public long toMillis(long d) { return d/(C2/C1); }
  17. public long toSeconds(long d) { return d/(C3/C1); }
  18. public long toMinutes(long d) { return d/(C4/C1); }
  19. public long toHours(long d) { return d/(C5/C1); }
  20. public long toDays(long d) { return d/(C6/C1); }
  21. public long convert(long d, TimeUnit u) { return u.toMicros(d); }
  22. int excessNanos(long d, long m) { return (int)((d*C1) - (m*C2)); }
  23. },
  24.  
  25. NANOSECONDS {
  26. public long toNanos(long d) { return d; }
  27. public long toMicros(long d) { return d/(C1/C0); }
  28. public long toMillis(long d) { return d/(C2/C0); }
  29. public long toSeconds(long d) { return d/(C3/C0); }
  30. public long toMinutes(long d) { return d/(C4/C0); }
  31. public long toHours(long d) { return d/(C5/C0); }
  32. public long toDays(long d) { return d/(C6/C0); }
  33. public long convert(long d, TimeUnit u) { return u.toNanos(d); }
  34. int excessNanos(long d, long m) { return (int)(d - (m*C2)); }
  35. },

其中:

  1. static final long C0 = 1L;
  2. static final long C1 = C0 * 1000L;
  3. static final long C2 = C1 * 1000L;
  4. static final long C3 = C2 * 1000L;
  5. static final long C4 = C3 * 60L;
  6. static final long C5 = C4 * 60L;
  7. static final long C6 = C5 * 24L;
  1. @Test
  2. public void stopwatch1() {
  3. Stopwatch stopwatch = Stopwatch.createStarted();
  4.  
  5. doSomething();
  6. stopwatch.stop(); // optional
  7. long millis = stopwatch.elapsed(MILLISECONDS);
  8. System.out.println("time: " + stopwatch);
  9. }
  10.  
  11. @Test
  12. public void stopwatch2() {
  13. Stopwatch stopwatch = Stopwatch.createStarted();
  14. //doSomething();
  15. stopwatch.stop();
  16. long millis = stopwatch.elapsed(MILLISECONDS);
  17. System.out.println("time: " + stopwatch);
  18.  
  19. stopwatch.reset().start();
  20. //doSomething();
  21. stopwatch.stop();
  22. millis = stopwatch.elapsed(MILLISECONDS);
  23. System.out.println("time: " + stopwatch);
  24. }
  25.  
  26. public static void doSomething(){
  27. try {
  28. Thread.sleep(100);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }

stopwatch1结果:

time: 100.8 ms

执行过程:

使用stopwatch对程序运行时间进行调试,首先调用StopWatch.createStarted()创建并启动一个stopwatch实例,调用stopwatch.stop()停止计时,此时会更新stopwatch的elapsedNanos时间,为stopwatch开始启动到结束计时的时间,再次调用stopwatch.elapsed(),获取stopwatch在start-stop时间段,时间流逝的长度。

RateLimiter.class

  1. public static RateLimiter create(double permitsPerSecond) {
  2. return create(permitsPerSecond, RateLimiter.SleepingStopwatch.createFromSystemTimer());//Stopwatch类稍后
  3. }
  4.  
  5. @VisibleForTesting
  6. static RateLimiter create(double permitsPerSecond, RateLimiter.SleepingStopwatch stopwatch) {
  7. RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0D);
  8. rateLimiter.setRate(permitsPerSecond);
  9. return rateLimiter;
  10. }
  11.  
  12. public final void setRate(double permitsPerSecond) {
  13. Preconditions.checkArgument(permitsPerSecond > 0.0D && !Double.isNaN(permitsPerSecond), "rate must be positive");
  14. synchronized(this.mutex()) {
  15. this.doSetRate(permitsPerSecond, this.stopwatch.readMicros());
  16. }
  17. }
  18.  
  19. abstract void doSetRate(double var1, long var3);
  1. 说明:this.stopwatch.readMicros());源码最终调用的是
  1. NANOSECONDS {
    public long toNanos(long d) { return d; }
    public long toMicros(long d) { return d/(C1/C0); } //return (stopwatch中的elapsedNanos,表示时间差)/(1L * 1000L/1L)
    }

SmoothRateLimiter

  1. final void doSetRate(double permitsPerSecond, long nowMicros) {
  2. this.resync(nowMicros);
  3. double stableIntervalMicros = (double)TimeUnit.SECONDS.toMicros(1L) / permitsPerSecond;
  4. this.stableIntervalMicros = stableIntervalMicros;
  5. this.doSetRate(permitsPerSecond, stableIntervalMicros);
  6. }
  7. abstract void doSetRate(double var1, double var3);
  8.  
  9. void resync(long nowMicros) {
  10. if (nowMicros > this.nextFreeTicketMicros) {
  11. //相当于(double)(nowMicros - this.nextFreeTicketMicros) * (permitsPerSecond double)TimeUnit.SECONDS.toMicros(1L)) //令牌生成速率:xx/单位时间
  12. double newPermits = (double)(nowMicros - this.nextFreeTicketMicros) / this.coolDownIntervalMicros();
  13. this.storedPermits = Math.min(this.maxPermits, this.storedPermits + newPermits);
  14. this.nextFreeTicketMicros = nowMicros;
  15. }
  16. }

说明:

nowMicros:表示用于标记从计时器开启到调用统计的方法时过去的时间
coolDownIntervalMicros:添加令牌时间间隔
stableIntervalMicros:添加令牌时间间隔 = (double)TimeUnit.SECONDS.toMicros(1L) / permitsPerSecond;(1秒/每秒的令牌数)
newPermits:时间段内新生令牌数
storedPermits:当前令牌数

nextFreeTicketMicros:

下一次请求可以获取令牌的起始时间,由于RateLimiter允许预消费,上次请求预消费令牌后,下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌

SmoothBursty

  1. static final class SmoothBursty extends SmoothRateLimiter {
  2. final double maxBurstSeconds;
  3.  
  4. SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
  5. super(stopwatch, null);
  6. this.maxBurstSeconds = maxBurstSeconds;//在RateLimiter未使用时,最多存储几秒的令牌
  7. }
  8.  
  9. void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
  10. double oldMaxPermits = this.maxPermits;
  11. this.maxPermits = this.maxBurstSeconds * permitsPerSecond;
  12. if (oldMaxPermits == 1.0D / 0.0) { //相当于oldMaxPermits ==Double.POSITIVE_INFINITY ,Double.POSITIVE_INFINITY 表示无穷大
  13.  
  14. this.storedPermits = this.maxPermits;
  15. } else {
  16. this.storedPermits = oldMaxPermits == 0.0D ? 0.0D : this.storedPermits * this.maxPermits / oldMaxPermits;
  17. }
  18.  
  19. }
  20.  
  21. long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
  22. return 0L;
  23. }
  24.  
  25. double coolDownIntervalMicros() {
  26. return this.stableIntervalMicros;
  27. }
  28. }

参数说明:

maxBurstSeconds:在RateLimiter未使用时,最多存储几秒的令牌
permitsPerSecond: 速率=令牌数/每秒
maxPermits :最大存储令牌数 = maxBurstSeconds * permitsPerSecond
storedPermits: 当前存储令牌数

RateLimiter几个常用接口分析

1、acquire() 函数主要用于获取permits个令牌,并计算需要等待多长时间,进而挂起等待,并将该值返回

RateLimiter.calss

  1. @CanIgnoreReturnValue
  2. public double acquire() {
  3. return acquire(1);
  4. }
  5.  
  6. /**
  7. * 获取令牌,返回阻塞的时间
  8. **/
  9. @CanIgnoreReturnValue
  10. public double acquire(int permits) {
  11. long microsToWait = reserve(permits);
  12.  
  13. //获取等待时间后,阻塞线程
  14. stopwatch.sleepMicrosUninterruptibly(microsToWait);
  15. return 1.0 * microsToWait / SECONDS.toMicros(1L);
  16. }
  17.  
  18. final long reserve(int permits) {
  19. checkPermits(permits);
  20. synchronized (mutex()) {
  21. return reserveAndGetWaitLength(permits, stopwatch.readMicros());
  22. }
  23. }
  24.  
  25. final long reserveAndGetWaitLength(int permits, long nowMicros) {
  26. long momentAvailable = this.reserveEarliestAvailable(permits, nowMicros);
  27. return Math.max(momentAvailable - nowMicros, 0L);
  28. }
  29. abstract long reserveEarliestAvailable(int var1, long var2);

SmoothRateLimiter.class

  1. final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  2. this.resync(nowMicros);
  3. long returnValue = this.nextFreeTicketMicros;//resync()方法后,如果nowMicros > this.nextFreeTicketMicros,等于nowMicros
  4.  
  5. double storedPermitsToSpend = Math.min((double)requiredPermits, this.storedPermits);
  6. //freshPermits从令牌桶中获取令牌后还需要的令牌数量
  7. double freshPermits = (double)requiredPermits - storedPermitsToSpend;
  8.  
  9. //平滑这里this.storedPermitsToWaitTime()直接返回0L + 还需要令牌数量/速率(需要的时间)
  10. long waitMicros = this.storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long)(freshPermits * this.stableIntervalMicros);
  11.  
  12. //如果超前消费,将导致下次请求等待时间=LongMath.saturatedAdd(this.nextFreeTicketMicros, waitMicros);
  13. this.nextFreeTicketMicros = LongMath.saturatedAdd(this.nextFreeTicketMicros, waitMicros);
  14. this.storedPermits -= storedPermitsToSpend;
  15. return returnValue;
  16. }

2、tryAcquire()

函数可以尝试在timeout时间内获取令牌,如果可以则挂起等待相应时间并返回true,否则立即返回false

  1. public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
  2. long timeoutMicros = Math.max(unit.toMicros(timeout), 0L);//超时时间
  3. checkPermits(permits);
  4. long microsToWait;
  5. synchronized(this.mutex()) {
  6. long nowMicros = this.stopwatch.readMicros();
  7. if (!this.canAcquire(nowMicros, timeoutMicros)) {
  8. return false;
  9. }
  10. //获取需要阻塞时间
  11. microsToWait = this.reserveAndGetWaitLength(permits, nowMicros);
  12. }
  13.  
  14. this.stopwatch.sleepMicrosUninterruptibly(microsToWait);
  15. return true;
  16. }
  17.  
  18. private boolean canAcquire(long nowMicros, long timeoutMicros) {
  19. //下一次请求可以获取令牌的起始时间
  20. return this.queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
  21. }
canAcquire用于判断timeout时间内是否可以获取令牌,通过判断当前时间+超时时间是否大于nextFreeTicketMicros 来决定是否能够拿到足够的令牌数,如果可以获取到,则过程同acquire,线程sleep等待,如果通过canAcquire在此超时时间内不能回去到令牌,则可以快速返回,不需要等待timeout后才知道能否获取到令牌。

SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值

SmoothWarmingUp创建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)

permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。

  1. @Test
  2. public void acquire1() {
  3. RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);
  4. for (int i = 1; i < 6; i++) {
  5. System.out.println(limiter.acquire());
  6. }
  7.  
  8. try {
  9. Thread.sleep(1000L);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13.  
  14. for (int i = 1; i < 6; i++) {
  15. System.out.println(limiter.acquire());
  16. }
  17. }

结果:

0.0
0.518741
0.357811
0.219877
0.199584
0.0
0.361189
0.220761
0.19938
0.199856

速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率。

参考:

https://www.cnblogs.com/xuwc/p/9123078.html

https://www.cnblogs.com/xuwc/p/9123078.html

高并发之限流RateLimiter(二)的更多相关文章

  1. SpringCloud之Zuul高并发情况下接口限流(十二)

    高并发下接口限流技术gauva(谷歌的框架) MySql最大连接数3000: 原理:框架每秒向桶里放100个令牌,接口请求来了先去拿令牌,拿到令牌后才能继续向后走,否则不允许向后执行:当接口请求太频繁 ...

  2. coding++:高并发解决方案限流技术-使用RateLimiter实现令牌桶限流-Demo

    RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率. 通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位时 ...

  3. 高并发解决方案限流技术-----使用RateLimiter实现令牌桶限流

    1,RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率.通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位 ...

  4. coding++:高并发解决方案限流技术---漏桶算法限流--demo

    1.漏桶算法 漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolici ...

  5. coding++:高并发解决方案限流技术--计数器--demo

    1.它是限流算法中最简单最容易的一种算法 计数器实现限流 每分钟只允许10个请求 第一个请求进去的时间为startTime,在startTime + 60s内只允许10个请求 当60s内超过十个请求后 ...

  6. 面试官:来谈谈限流-RateLimiter源码分析

    RateLimiter有两个实现类:SmoothBursty和SmoothWarmingUp,其都是令牌桶算法的变种实现,区别在于SmoothBursty加令牌的速度是恒定的,而SmoothWarmi ...

  7. 实例:接口并发限流RateLimiter

    需求:接口每秒最多只能相应1个请求 1.创建 全局类对象 import com.google.common.util.concurrent.RateLimiter; import org.spring ...

  8. 超详细的Guava RateLimiter限流原理解析

    超详细的Guava RateLimiter限流原理解析  mp.weixin.qq.com 点击上方“方志朋”,选择“置顶或者星标” 你的关注意义重大! 限流是保护高并发系统的三把利器之一,另外两个是 ...

  9. SpringBoot进阶教程(六十七)RateLimiter限流

    在上一篇文章nginx限流配置中,我们介绍了如何使用nginx限流,这篇文章介绍另外一种限流方式---RateLimiter. v限流背景 在早期的计算机领域,限流技术(time limiting)被 ...

随机推荐

  1. 牛客网校招全国统一模拟笔试(三月场)- Java方向

    1.若二叉树采用二叉链表存储结构,要交换其所有分支结点左.右子树的位置,利用()遍历方法最合适 A 前序 B 中序 C 后序 D 按层次 解析:显然后序遍历比较合理.正常的逻辑应该就是:做好当前结点子 ...

  2. python脚本解析json文件

    python脚本解析json文件 没写完.但是有效果.初次尝试,写的比较不简洁... 比较烦的地方在于: 1,中文编码: pSpecs.decode('raw_unicode_escape') 2,花 ...

  3. topcoder srm 663 div1

    problem1 link 每次枚举$S$的两种变化,并判断新的串是否是$T$的子串.不是的话停止搜索. problem2 link 首先考慮增加1个面值为1的硬币后,$ways$数组有什么变化.设原 ...

  4. Oracle 11g 导出数据报 “ORA-01455: 转换列溢出整数数据类型”的问题

    最近发现云服务器上的Oracle 11g在导出时报错,如下: ... . 正在导出后期表活动. 正在导出实体化视图. 正在导出快照日志EXP-00008: 遇到 ORACLE 错误 1455ORA-0 ...

  5. Python3基础 dict clear 清空一个字典

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  6. Python3基础 dict setdefault 根据键查找值,找不到键会添加

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  7. img的基线对齐问题

    http://blog.csdn.net/u011997156/article/details/44806523

  8. 题解——洛谷P1550 [USACO08OCT]打井Watering Hole(最小生成树,建图)

    题面 题目背景 John的农场缺水了!!! 题目描述 Farmer John has decided to bring water to his N (1 <= N <= 300) pas ...

  9. 题解——洛谷P3128 [USACO15DEC]最大流Max Flow

    裸的树上差分 因为要求点权所以在点上差分即可 #include <cstdio> #include <algorithm> #include <cstring> u ...

  10. log4j2打印Mybatis执行的SQL语句及SQL语句的执行时间

    http://blog.csdn.net/zjq852533445/article/details/78320012