JavaDemo——使用Guava的RateLimiter进行限流

x33g5p2x  于2021-12-22 转载在 Java  
字(3.5k)|赞(0)|评价(0)|浏览(289)

Guava是Google的一个库,提供了很多有用的功能,其中的RateLimiter可以很方便的实现限流功能,使用的是定时向令牌桶里放令牌的方式实现的。

maven导入:

<dependency>
		    <groupId>com.google.guava</groupId>
		    <artifactId>guava</artifactId>
		    <version>31.0.1-jre</version>
		</dependency>

RateLimiter是一个抽象类,其下面有两种实现,SmoothBursty和SmoothWarmingUp:

  • SmoothBursty

可以应对突发流量的限流器;空闲时,最大可以保留1s(该保留时间写死在源码里)的令牌数,突发流量时可以迅速获取这1s保留的令牌而无需等待;

  • SmoothWarmingUp

带有预热效果的限流器;空闲时,最大也会保留1s的令牌数,但从空闲时开始获取令牌会存在预热效果,即使保留了1s的令牌数,也会等待一段比较长的时间获取令牌(具体等待时间可根据函数求得),会在设定的预热时间期间,等待时间逐渐加速到正常限流速率;

创建RateLimiter有3个方法可以调用(RateLimiter类中):

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

第一个方法生成的是SmoothBursty,第二三个方法生成的是SmoothWarmingUp(其中第二个方法转换了下参数直接调用了第三个方法),permitsPerSecond指定每秒生成的令牌数(相当于设定QPS);

获取令牌的方法总体分为2类:阻塞获取和非阻塞获取;

  • 阻塞获取的方法(返回等待时间秒,返回0.0表示无等待):

public double acquire()        获得一个令牌,实际调用acquire(1);

public double acquire(int permits)        获得指定个数令牌;

  • 非阻塞获取的方法(返回true表示获得令牌成功,否则返回false,还可以设置等待时间,在等待时间内获得令牌也返回true):

public boolean tryAcquire()        尝试获得一个令牌;

public boolean tryAcquire(Duration timeout)        尝试获得一个令牌并设置等待时间;

public boolean tryAcquire(int permits)        尝试获得多个令牌;

public boolean tryAcquire(int permits, Duration timeout)        尝试获得定数令牌并设置等待时间;

public boolean tryAcquire(long timeout, TimeUnit unit)        尝试获得一个令牌并设置等待时间;

public boolean tryAcquire(int permits, long timeout, TimeUnit unit)        尝试获得指定数量令牌并设置等待时间;(上面5个try方法最终都会调整参数调用该方法)

RateLimiter还有一个可以重新设置令牌生成速率的方法:public final void setRate(double permitsPerSecond)

测试Demo:

/**
 * 2021年12月20日下午4:41:26
 */
package testGuava;

import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.RateLimiter;

/**
 * @author XWF
 *
 */
public class TestGuavaRateLimiter {

	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		SimpleDateFormat format = new SimpleDateFormat("hh:mm:ss.SSS");
		System.out.println("____test SmoothBursty____");
		//每秒放10个令牌(QPS=10,0.1s加一个令牌)
		RateLimiter limit = RateLimiter.create(10);
		Thread.sleep(2000);//等待令牌桶里存放10个令牌(默认最多缓存1s的令牌数)
		for (int i = 0; i < 20; i++) {
			double waitSeconds = limit.acquire();//等同于acquire(1);阻塞直到获得令牌,返回等待时间(秒),返回0s说明没有等待
			System.out.println(i + "_time:" + format.format(new Date()) + "_等待(秒):" + waitSeconds);
		}
		System.out.println("_________________________");
		for (int i = 0; i < 5; i++) {
			System.out.println(format.format(new Date()) + "____" + limit.tryAcquire());//等同于tryAcquire(1);不阻塞,获取成功返回true;
			//其他tryAcquire实际最终都是调用public boolean tryAcquire(int permits, long timeout, TimeUnit unit);带超时等待时间
			Thread.sleep(50);
		}
		
		System.out.println("____test SmoothWarmingUp____");
//		Duration duration = Duration.ofSeconds(2);
//		limit = RateLimiter.create(10, duration);//实际调用create(permitsPerSecond, toNanosSaturated(warmupPeriod), TimeUnit.NANOSECONDS);
		//带600ms的预热时间
		//(即使桶里有10个令牌也不会立马获得10个,需要600ms逐渐加速到可以每0.1s获得一个令牌)
		RateLimiter limit2 = RateLimiter.create(10, 600, TimeUnit.MILLISECONDS);
		Thread.sleep(2000);//等待令牌桶里存放10个令牌
		for (int i = 0; i < 20; i++) {
			if(i == 10) {
				Thread.sleep(600);//再次缓存6个令牌
			}
			double waitSeconds = limit2.acquire(2);//每次阻塞取2个令牌
			System.out.println(i + "_time:" + format.format(new Date()) + "_等待(秒):" + waitSeconds);
		}
		
		System.out.println("finished.");
	}

}

运行结果:

第一个测试可以看到缓存了1s的令牌数,直接无需等待就可以获得10个令牌,后面没有可用令牌时就会等待每0.1s生成一个令牌;

第二个测试可以看到也缓存了1s的令牌数,但有600ms的预热时间,在获取完第一个令牌后,第二个等待了0.46619s,第三个等待了0.233571s,第四个才到了0.199002s(设置的一次获取2令牌,0.1s增加一个,0.2s才能有2个令牌),后面一直是正常速率,中间又空闲了600ms缓存了一部分令牌,第十个也立即获得,但第十一个等待了0.238144,后面才正常速率,这就是预热效果;

相关文章