學習使用Guava RateLimiter


目錄

   一、引入

  二、快速上手

    2.1、導入依賴

    2.2、第一個示例

  三、獲取許可

    3.1、非阻塞式獲取

    3.2、阻塞式獲取

  四、存在的問題

    4.1、集群限流

  

 

 

 

 

 

一、引入

  在程序中,我們經常會用到限流,比如接口調用的頻率限制。

  server端提供api給clients進行調用,如果某個client調用api的頻率過高,造成server端的負載升高,超過server端的上限,那么很有可能導致server端不可用,從而影響所有的調用方。

  限制頻率,可以在client端做,也可以在server端做,但是目前一般都是在server端做,同時client一般也會調整調用頻率。

  至於怎么限流,網上很多的資料,這里就不闡述了,主要介紹一下使用Guava RateLimiter來實現限流。

 

二、快速上手

2.1、導入依賴

  Guava RateLimiter是Guava的一部分,所以直接導入Guava的依賴即可。

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

  

2.2、第一個示例

  簡單看一下下面的用法

package cn.ganlixin.guava;

import com.google.common.util.concurrent.RateLimiter;
import org.junit.Test;

import java.time.LocalTime;

public class UserRateLimiter {

    @Test
    public void testSimple() throws InterruptedException {
        // 創建一個限流器(每秒限制流量為5個)
        RateLimiter rateLimiter = RateLimiter.create(5.0);

        for (int i = 0; i < 10; i++) {
            if (rateLimiter.tryAcquire()) {
                System.out.println(LocalTime.now() + " 通過");
            } else {
                System.out.println(LocalTime.now() + " 被限流");
            }
            Thread.sleep(100L);
        }
    }
}

  上面的示例代碼中,創建了一個限流器,每秒最多允許5個流量,這個流量有個專業的稱呼,叫“許可”(准許調用),也就是5個許可。

  調用tryAcquire()會嘗試獲取1個許可,如果獲取到了,返回true,表示未被限流;否則返回false,表示沒有獲取到許可,被限流了。

  如此就可以實現流量控制了。

 

三、獲取許可

  獲取許可,有非阻塞和阻塞式獲取。

if (獲得許可) {
    執行代碼塊 1
} else {
    執行代碼塊 2
}

  非阻塞式:嘗試獲取許可,如果獲取到許可,則執行代碼塊1,如果沒有獲取到就認為被限流,則執行代碼塊2;

  阻塞式:嘗試獲取許可,獲取到則執行代碼塊1,沒有獲取到,則阻塞,等待獲取到許可后,執行代碼塊1,注意不會執行代碼塊2;

    

3.1、非阻塞獲取許可

  前面使用介紹了,可以使用RateLimiter類可以使用create方法,創建多個許可,每個許可就是一個令牌,拿到令牌,才可以執行操作(通過),否則就是被限流(阻止)。

  使用tryAcquire()方法,是不阻塞的嘗試獲取令牌,但是他有多個重載方法,有不同的參數。

// 嘗試獲取1個許可,如果獲取到,則返回true,否則返回false
boolean tryAcquire();

// 嘗試獲取多個許可,如果獲取到,則返回true,否則返回false
boolean tryAcquire(int permits)
// 示例tryAcquire(3)

// 在timeout時間內,嘗試獲取1個許可,如果獲取到,則返回true,否則返回false
tryAcquire(Duration timeout);
// 示例:tryAcquire(Duration.ofSeconds(3))
tryAcquire(long timeout, TimeUnit unit);
// 示例:tryAcquire(3, TimeUnit.SECONDS);

// 在timeout時間內,嘗試獲取多個許可,如果獲取到,則返回true,否則返回false
tryAcquire(int permits, Duration timeout)
tryAcquire(int permits, long timeout, TimeUnit unit)

  

3.2、阻塞式獲取許可

  阻塞式獲取,調用的方法是acquire

package cn.ganlixin.guava;

import com.google.common.util.concurrent.RateLimiter;
import org.junit.Test;

import java.time.LocalTime;

public class UserRateLimiter {

    @Test
    public void testAcquire() {
        RateLimiter rateLimiter = RateLimiter.create(2);

        for (int i = 0; i < 10; i++) {
            double sleep = rateLimiter.acquire();
            System.out.println("now: " + LocalTime.now() + "  sleep: " + sleep);
        }
    }
}

  運行輸出如下:

now: 16:59:51.879  sleep: 0.0
now: 16:59:52.289  sleep: 0.403329
now: 16:59:52.784  sleep: 0.492976
now: 16:59:53.285  sleep: 0.499409
now: 16:59:53.789  sleep: 0.498335
now: 16:59:54.285  sleep: 0.494628
now: 16:59:54.786  sleep: 0.49857
now: 16:59:55.289  sleep: 0.496816
now: 16:59:55.784  sleep: 0.494352
now: 16:59:56.288  sleep: 0.499635

  

  acquire也有重載方法:

// 嘗試阻塞獲取多個許可
acquire(int permits)

  

四、存在的問題

  Guava解決的問題,一般都是單機的,以限流為例,使用guava限流,只做到了單機限流,但是我們的服務一般都會由多台機器(集群),雖然我們可以通過計算單機和集群的比例,來設置限流數量,但是有幾個問題:

  1、機器增減時,要保證總流量保持不變,就需要修改每一台機器的流量限制,這個不是很方便;

  2、Guava的限流器,並不是公平的,至於什么是公平和非公平,可以參考:

4.1 集群限流

  guava的RateLimiter,限流是通過獲取“許可”來控制流量的,只不過是單機管理自己的許可。

  如果將所有機器的“許可”匯集到一個地方,所有機器都從這個地方獲取許可,不就可以實現集群限流嗎?

  可以使用redis來保存所有機器的“許可”。

  這樣做,可以實現集群限流,但不能保證單機的流量限制,其實對於現在的微服務來說,請求被平均分給所有機器,是服務平台的問題,可以不用關心這個問題。

 

 

 

  

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM