RateLimiter限流及源码分析

什么是Guava?

Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。 所有这些工具每天都在被Google的工程师应用在产品服务中。

接下来开始正文

首先来看看关系图,这里我们可以看到,本文所讲解的SmoothBursty继承自SmoothRateLimiter,而SmoothRateLimiter又继承自RateLimiter,看一下即可,记住这三者的关系
关系图

<!-- guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.0.1-jre</version>
</dependency>
如何使用RateLimiter实现限流?
/**
 * 演示用例1 
 * 涉及方法————
 * RateLimiter.create(double permitsPerSecond)创建一个SmoothBursty,并指定每秒生成令牌数
 * rateLimiter.acquire() 消费一个令牌
 */
public class RateLimiterDemo {
    public static void main(String[] args) {
        //每秒钟生产2个令牌,也就是0.5s生成一个令牌
        RateLimiter rateLimiter = RateLimiter.create(2);
        //消费一个令牌 * 3次,将返回等待的时间
        System.out.println(rateLimiter.acquire());
        System.out.println(rateLimiter.acquire());
        System.out.println(rateLimiter.acquire());
    }
}

运行结果如下——
0.49这个很容易理解,中间会经过代码的执行会损失部分精度,但第一次为什么是0?
在这里插入图片描述

为什么第一次申请令牌时等待时间为0(也就是不用进行等待)?

这里先使用白话文解释,后面将对源码进行讲解
——这是因为每次申请令牌,会计算需要等待的时间,但该时间并非本次申请需要等待的时间,而是下一次申请需要等待的时间。

/**
 * 演示用例2
 * 涉及方法————
 * RateLimiter.create(double permitsPerSecond)创建一个SmoothBursty,并指定每秒生成令牌数
 * rateLimiter.acquire() 消费一个令牌
 * rateLimiter.acquire(int permits) 消费指定令牌数
 */
public class RateLimiterDemo {
    public static void main(String[] args) {
        //每秒钟生产2个令牌,也就是0.5s生成一个令牌
        RateLimiter rateLimiter = RateLimiter.create(2);
        //消费10个令牌,本次消费等待0s,下一次消费将等待5s
        System.out.println(rateLimiter.acquire(10));
        //消费1个令牌,本次消费等待5s,下一次消费将等待0.5s
        System.out.println(rateLimiter.acquire());
        //消费2个令牌,本次消费等待0.5s,下一次消费将等待1s
        System.out.println(rateLimiter.acquire(2));
        //消费1个令牌,本次消费等待1s,
        System.out.println(rateLimiter.acquire());
    }
}

在这里插入图片描述
看完这段代码相信大家已经明白了为什么第一次申请令牌时等待时间为0

SmoothBursty允许一次性消费大的令牌数,如果突然间来了很大的流量,系统可能扛不住这种突发,可以避免消费大的令牌数,或者使用SmoothWarmingUp,使用它会在启动时以一个比较大的速率慢慢达到平均速率,这里并不会对SmoothWarmingUp进行探讨。

RateLimiter还提供了另一个申请令牌方法,可以无阻塞申请,设置超时申请,使用非常简单,这里不介绍了
在这里插入图片描述
在这里插入图片描述

下面将对RateLimiter的源码进行探讨

属性

public abstract class RateLimiter {
	//用于获取时间和停止线程的秒表
	private final RateLimiter.SleepingStopwatch stopwatch;
	//用于上锁的互斥量
	private volatile Object mutexDoNotUseDirectly; 
	...
}

先来看看如何创建一个RateLimiter

public abstract class RateLimiter {
	/**
	 * 创建RateLimiter步骤1
	 * @param permitsPerSecond 每秒生成令牌数
	 */
	public static RateLimiter create(double permitsPerSecond) {
	    return create(permitsPerSecond,
	        RateLimiter.SleepingStopwatch.createFromSystemTimer());
	}
	...
}
这里我们先来看看第二个参数RateLimiter.SleepingStopwatch.createFromSystemTimer()

SleepingStopwatch是RateLimiter内部的一个抽象类,定义了两个抽象方法和一个具体方法

抽象方法

  • readMicros():获取计时器经过时间
  • sleepMicrosUninterruptibly(long var1):让线程等待指定毫秒数

具体方法

  • createFromSystemTimer():用于创建并启动一个计时器,重写两个抽象方法
abstract static class SleepingStopwatch {
    protected SleepingStopwatch() {
    }

    protected abstract long readMicros();

    protected abstract void sleepMicrosUninterruptibly(long var1);

    public static RateLimiter.SleepingStopwatch createFromSystemTimer() {
        return new RateLimiter.SleepingStopwatch() {
        	//创建一个自动倒计时的计时器
            final Stopwatch stopwatch = Stopwatch.createStarted();
			//自开始经过的时间
            protected long readMicros() {
                return this.stopwatch.elapsed(TimeUnit.MICROSECONDS);
            }
			//将线程等待指定的毫秒时间
			//执行的是TimeUnit.NANOSECONDS.sleep()
            protected void sleepMicrosUninterruptibly(long micros) {
                if (micros > 0L) {
                    Uninterruptibles.sleepUninterruptibly(micros,
                    TimeUnit.MICROSECONDS);
                }

            }
        };
    }
}

返回去看之前的create方法(下面这个),这是我们已经知道传入了两个参数,一个是我们指定的每秒申请请求数,另一个是SleepingStopwatch(获取秒表经过毫秒时间、线程睡眠指定毫秒时间)

public abstract class RateLimiter {
	/**
	 * 创建RateLimiter步骤1
	 * @param permitsPerSecond 每秒生成令牌数
	 */
	public static RateLimiter create(double permitsPerSecond) {
	    return create(permitsPerSecond,
	        RateLimiter.SleepingStopwatch.createFromSystemTimer());
	}
	...
}

通过下面这段代码可以看到调用RateLimiter.create(double permitsPerSecond)方法最终将创建一个SmoothBursty的实例对象(上面的关系图没忘吧,SmoothBursty是其子类的子类)

public abstract class RateLimiter {
	/**
	 * 创建RateLimiter步骤2
	 * @param permitsPerSecond 每秒生成令牌数
	 * @param stopwatch 用于获取经过时间和线程等待
	 */
	static RateLimiter create(double permitsPerSecond,
	                          RateLimiter.SleepingStopwatch stopwatch) {
	    //创建一个SmoothBursty对象
	    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0D);
	    rateLimiter.setRate(permitsPerSecond);
	    return rateLimiter;
	}
	...
}

在上面的new SmoothBursty(stopwatch, 1.0D)会初始化两个参数
maxBurstSeconds = 0 该参数来自SmoothBursty,这里不用理会是什么意思
nextFreeTicketMicros = 0

并将stopwatch设置到RateLimiter中
这里来看一下SmoothRateLimiter中的四个参数

/**
 * SmoothRateLimiter是RateLimiter的抽象子类
 */
abstract class SmoothRateLimiter extends RateLimiter {
    double storedPermits; 	           //剩余令牌数
    double maxPermits;                 //最大令牌数
    double stableIntervalMicros;       //生成令牌时间间隔
    private long nextFreeTicketMicros; //下一次可获取令牌的时间(子类初始化时设为0)
}

继续创建RateLimiter步骤2的下一步
由于SmoothBursty并没有重写setRate()方法,将调用RateLimiter的setRate()

public abstract class RateLimiter {
	/**
	 * 创建RateLimiter步骤3
	 * @param permitsPerSecond 每秒生成令牌数
	 */
	public final void setRate(double permitsPerSecond) {
		//判断参数合法性
	    Preconditions.checkArgument(permitsPerSecond > 0.0D &&
	                        !Double.isNaN(permitsPerSecond),
	                         "rate must be positive");
	    //this.mutex()其实就是返回上面那个互斥量
	    synchronized(this.mutex()) {
	    	//将每秒生成令牌数与秒表经过时间作为参数传递
	        this.doSetRate(permitsPerSecond, this.stopwatch.readMicros());
	    }
	}
}

接下来将会调用RateLimiter中的doSetRate()

public abstract class RateLimiter {
	/**
	 * 创建RateLimiter步骤4
	 * 初始化操作,添加令牌,设置令牌生成周期
	 * @param permitsPerSecond 每秒生成令牌数
	 * @param nowMicros 秒表经过时间
	 */
	final void doSetRate(double permitsPerSecond, long nowMicros) {
		//添加令牌,并设置下次获取令牌时间nextFreeTicketMicros;
	    this.resync(nowMicros);
	    //stableIntervalMicros(每多少微秒生成一个令牌)
	    double stableIntervalMicros = (double)TimeUnit.SECONDS.toMicros(1L) / 
	    	permitsPerSecond;
	    this.stableIntervalMicros = stableIntervalMicros; //初始化
	    this.doSetRate(permitsPerSecond, stableIntervalMicros);
	}
}

下面具体看一下生成令牌的resync()方法

abstract class SmoothRateLimiter extends RateLimiter {
	/**
	 * 通过(当前时间秒表经过时间 - 上一次秒表经过时间) / 令牌生成周期 计算需添加的令牌数
	 * 同时设置nextFreeTicketMicros为当前时间
	 */
	void resync(long nowMicros) {
		//如果秒表经过时间大于下一次可获取令牌的时间
	    if (nowMicros > this.nextFreeTicketMicros) {
	    	//计算新增令牌数
	    	//t1(nowMicros):本次添加令牌时秒表经过的时间
	    	//t2(nextFreeTicketMicros):上一次添加令牌秒表时秒表经过的时间
	    	//n(stableIntervalMicros):令牌生成周期
	    	//本次需要添加的令牌 = (t2 - t1) / n
	        double newPermits = (double)(nowMicros - this.nextFreeTicketMicros) / 
	            this.coolDownIntervalMicros(); //该方法返回this.stableIntervalMicros
	        //更新剩余令牌数
	        this.storedPermits = Math.min(this.maxPermits, 
	        	this.storedPermits + newPermits);
	        //设置下一次请求可以获取令牌的起始时间为当前秒表经过时间
	        this.nextFreeTicketMicros = nowMicros;
	    }
	}
}

这里可能有点不懂,nextFreeTicketMicros关系到下一次申请需要等待的时间
RateLimiter的doSetRate(double, long)将继续调用子类的doSetRate(double, double)

final double maxBurstSeconds; //最大突发秒数,初始化时默认为1
/**
 * 初始化操作,设置最大令牌数和剩余令牌数
 * @param permitsPerSecond 每秒生成令牌数
 * @param stableIntervalMicros 令牌生成周期
 */
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
	//this.maxPermits默认为0,所以旧最大令牌数为0
    double oldMaxPermits = this.maxPermits; 
    //如果每秒生成100个令牌,this.maxPermits为100
    this.maxPermits = this.maxBurstSeconds * permitsPerSecond;
    //当旧最大令牌数为无穷大,设置剩余令牌数也为无穷大
    //否则,由于旧最大令牌数为0,设置剩余令牌数也为0
    if (oldMaxPermits == 1.0D / 0.0) {
        this.storedPermits = this.maxPermits;
    } else {
        this.storedPermits = oldMaxPermits ==
        	 0.0D ? 0.0D : this.storedPermits * this.maxPermits / oldMaxPermits;
    }

}

接下来看看消费令牌的一系列相关源码,也可以先看最后面的总结再看源码,或者不看

/**
 * 消费1个令牌
 */
@CanIgnoreReturnValue
public double acquire() {
    return this.acquire(1);
}

/**
 * 消费多个令牌
 */
@CanIgnoreReturnValue
public double acquire(int permits) {
	//获取还需等待的时间
    long microsToWait = this.reserve(permits);
    //让线程等待
    this.stopwatch.sleepMicrosUninterruptibly(microsToWait);
    //返回等待时间
    return 1.0D * (double)microsToWait / (double)TimeUnit.SECONDS.toMicros(1L);
}
/**
 * @param permits 申请令牌数
 * @return 如果未到下一次申请令牌时间将返回还需等待的时间,否则返回0
 */
final long reserve(int permits) {
    checkPermits(permits);
    synchronized(this.mutex()) {
        return this.reserveAndGetWaitLength(permits, this.stopwatch.readMicros());
    }
}
/**
 * @param permits 申请令牌数
 * @param nowMicros 秒表经过时间
 * @return 如果未到下一次申请令牌时间将返回还需等待的时间,否则返回0
 */
final long reserveAndGetWaitLength(int permits, long nowMicros) {
    long momentAvailable = this.reserveEarliestAvailable(permits, nowMicros);
    return Math.max(momentAvailable - nowMicros, 0L);
}
/**
 * 1.判断是否已到下一次添加令牌时间,是的话添加令牌,并设置下一次添加令牌时间为当前秒表经过时间
 * 2.通过计算还需等待时间,设置下一次添加令牌时间加上等待时间
 * 3.更新令牌剩余数
 * @param requiredPermits 申请令牌数
 * @param nowMicros 秒表经过时间
 * @return 如果未到下一次申请令牌时间,返回值为下一次申请令牌时间,否则返回值为当前值
 */
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
	//添加令牌,设置nextFreeTicketMicros = nowMicros
    this.resync(nowMicros);
    //设置返回值为nowMicros
    long returnValue = this.nextFreeTicketMicros;
    //能够花费的令牌数
    double storedPermitsToSpend = Math.min((double)requiredPermits, 						
    											this.storedPermits);
    //还差多少令牌数
    double freshPermits = (double)requiredPermits 
    	- storedPermitsToSpend;
    //SmoothBursty的storedPermitsToWaitTime()方法返回0
    //也就是等待时间waitMicros = (long)(freshPermits * this.stableIntervalMicros);
    long waitMicros = this.storedPermitsToWaitTime(this.storedPermits,
    												storedPermitsToSpend) 
    	+ (long)(freshPermits * this.stableIntervalMicros);
    //将nextFreeTicketMicros加上等待时间
    this.nextFreeTicketMicros = LongMath.saturatedAdd(this.nextFreeTicketMicros,
    	 waitMicros);
    //设置剩余令牌数
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
}
总结上面的一系列方法——

如果已到下一次申请令牌时间,则不需要等待,设置下一次申请令牌时间为(当前秒表经过时间加上本次申请令牌需花费时间)
如果未到下一次申请令牌时间,则等待到下一次申请令牌时间,同时计算新的下次申请令牌时间

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章