使用Guava平滑限流某個接口的請求數

Guava RateLimiter提供的令牌桶算法可用於平滑突發限流(SmoothBursty)和平滑預熱限流(SmoothWarmingUp)實現。

SmoothBursty

以指定的吞吐量(permits per second)創建一個限速器,如指定限速每秒5個請求

RateLimiter limiter = RateLimiter.create(5);

  • 返回的RateLimiter確保在給定的每秒內平均發出的令牌數不超過 permitsPerSecond,並且持續的請求平滑地分佈在每秒。
  • 當傳入的請求速率超過permitsPerSecond時,限速器將以每 (1.0/ permitsPerSecond) 秒的速率新增一個令牌放到令牌桶裏。
  • 當未使用速率限制器時,將允許突發最多permitsPerSecond請求,隨後的請求將以穩定的permitsPerSecond速率平穩地受到限制。

1、RateLimiter桶容量爲5且每秒新增5個令牌(最終換算成限速器以每200毫秒的速率新增一個令牌放到令牌桶裏)

// permits per second 每條允許的請求數
RateLimiter limiter = RateLimiter.create(5);
// limiter.acquire()返回值是睡眠時間(以強制執行速率,單位是秒)
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());

運行結果

0.0
0.196946
0.191905
0.199183
0.199237
0.199366

2、RateLimiter允許一定程度的突發請求,如一次性消費5個令牌,但是之後limiter.acquire(1)獲取令牌要等差不多1秒,桶中才能有令牌,且接下來的請求也整形爲固定速率。。

因爲突發的請求將這1秒允許的最大請求消費完了,因此這一秒鐘不再發放允許請求的令牌,需要等到下1秒。

// permits per second 每條允許的請求數
RateLimiter limiter = RateLimiter.create(5);
// limiter.acquire()返回值是睡眠時間(以強制執行速率,單位是秒)
System.out.println(limiter.acquire(5));
System.out.println(limiter.acquire(1));
System.out.println(limiter.acquire(1));
System.out.println(limiter.acquire(1));

運行結果

0.0
0.997591
0.193067
0.199397

3、RateLimiter也允許消費未來的令牌,但是接下來的limiter.acquire(1)要等待2秒左右的時間,桶中才能有令牌,且接下來的請求也整形爲固定速率。

// permits per second 每條允許的請求數
RateLimiter limiter = RateLimiter.create(5);
// limiter.acquire()返回值是睡眠時間(以強制執行速率,單位是秒)
System.out.println(limiter.acquire(10));
System.out.println(limiter.acquire(1));
System.out.println(limiter.acquire(1));
System.out.println(limiter.acquire(1));

運行結果

0.0
1.997116
0.193233
0.199582

4、RateLimiter允許將一段時間內沒有消費的令牌暫存到令牌桶中,保留待未來使用。

這裏用到了RateLimiter的實現類SmoothBursty的maxBurstSeconds,表示如果限速器未使用時,多少秒的令牌數可以存儲起來以備將來使用,默認值是1s。

// permits per second 每條允許的請求數
RateLimiter limiter = RateLimiter.create(2);
// limiter.acquire()返回值是睡眠時間(以強制執行速率,單位是秒)
System.out.println(limiter.acquire());
Thread.sleep(2000L);
// TimeUnit.SECONDS.sleep(2);
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());

運行結果

0.0
0.0
0.0
0.0
0.49986
0.49293

SmoothWarmingUp

RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)

        創建一個具有指定穩定吞吐量(permitsPerSecond)的RateLimiter,並提供一個預熱期 warmupPeriod,在此期間,RateLimiter平穩地提高其速率,直到該速率在週期結束時達到最大速率(只要存在足以使其飽和的請求)。

      同樣,如果RateLimiter在與warmupPeriod大小相同的時間內不使用,它將逐漸返回其“冷”狀態,即它將經歷與首次創建時相同的預熱過程。

		// 創建5r/s的限速器,預熱時間是1秒
		RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);
		// limiter.acquire()返回值是睡眠時間(以強制執行速率,單位是秒)

		// 不足夠的請求來預熱限速器
		System.out.println("不足夠的請求來預熱限速器");
		for (int i = 0; i < 3; i++) {
			System.out.println(limiter.acquire());
		}
		Thread.sleep(900L);
		System.out.println("不足夠的請求來預熱限速器");
		// 雖然前面有3個請求,但是沒在預熱期內達到足夠的請求
		for (int i = 0; i < 4; i++) {
			System.out.println(limiter.acquire());
		}
		Thread.sleep(1000L);
		// 足夠的請求來預熱限速器
		System.out.println("足夠的請求來預熱限速器");
		for (int i = 0; i < 5; i++) {
			System.out.println(limiter.acquire());
		}
		// 預熱之後進行平滑請求限速
		System.out.println("預熱之後進行平滑請求限速");
		for (int i = 0; i < 10; i++) {
			System.out.println(limiter.acquire());
		}
		// 睡眠,使限速器冷卻下來,之後需要進行重新預熱
		System.out.println("睡眠,使限速器冷卻下來,之後需要進行重新預熱");
		Thread.sleep(1500L);
		for (int i = 0; i < 10; i++) {
			System.out.println(limiter.acquire());
		}

運行結果

不足夠的請求來預熱限速器
0.0
0.517236
0.351765
不足夠的請求來預熱限速器
0.0
0.519959
0.359281
0.220235
足夠的請求來預熱限速器
0.0
0.51995
0.359494
0.220238
0.199851
預熱之後進行平滑請求限速
0.198823
0.199515
0.198837
0.198902
0.199822
0.199645
0.199435
0.199752
0.199524
0.199458
睡眠,使限速器冷卻下來,之後需要進行重新預熱
0.0
0.519914
0.359264
0.219483
0.20038
0.199957
0.199144
0.200049
0.199207
0.200137

 

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