切換語言為:簡體

程式設計效率利器,Guava RateLimiter 優雅限流

  • 爱糖宝
  • 2024-09-27
  • 2043
  • 0
  • 0

分散式系統中,經常要對服務或資源的訪問頻率進行限制,以防止系統過載或應對突發的流量高峰。Google的Guava庫提供了RateLimiter這一強大且靈活的元件,用於實現速率限制。

  • 一、RateLimiter的原理與特性

  • 二、RateLimiter的功能與使用方法

  • 三、適用場景

  • 四、使用案例

  • 五、實現機制

  • 六、最佳實踐

一、RateLimiter的原理與特性

RateLimiter基於令牌桶演算法(Token Bucket Algorithm)實現。該演算法透過以恆定的速度向桶中新增令牌,並且每當有請求來時,需要從桶中取出一個或多個令牌才能繼續執行。如果桶中沒有足夠的令牌,請求將被限流,即延遲處理或拒絕服務。

Guava的RateLimiter主要特性:

  • 「平滑突發流量」:RateLimiter能夠平滑地處理突發流量,確保系統不會因為瞬間的請求高峰而崩潰。

  • 「可配置的速率」:可以很容易地配置RateLimiter的令牌產生速率,以適應不同的應用場景和需求。

  • 「支援預熱」:RateLimiter允許在啟動時進行預熱,即在系統剛開始執行時逐漸增加令牌產生的速率,以避免冷啟動問題。

  • 「執行緒安全」:RateLimiter是執行緒安全的,可以在多執行緒環境中安全使用。

二、RateLimiter的功能與使用方法

「使用RateLimiter的基本步驟:」

建立RateLimiter例項,並指定每秒生成的令牌數。在需要限流的地方呼叫acquire()或tryAcquire()方法獲取令牌。如果成功獲取到令牌,則繼續處理請求;否則,根據業務邏輯進行相應的處理(如延遲、降級或返回錯誤)。

RateLimiter的主要功能:

  • 「RateLimiter例項」:透過RateLimiter.create(double permitsPerSecond)方法建立RateLimiter例項,指定每秒生成的令牌數。

  • 「獲取令牌」:透過acquire()方法可以獲取一個令牌,如果桶中沒有令牌,該方法會阻塞直到有令牌可用。還提供tryAcquire()方法用於非阻塞地嘗試獲取令牌。

  • 「預熱」:透過RateLimiter.create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)方法可以建立一個帶有預熱期的RateLimiter例項。

三、適用場景

RateLimiter適用於多種場景,包括但不限於:

  • 「API限流」:保護後端服務免受惡意攻擊或過量請求的損害。

  • 「資料庫訪問限流」:控制對資料庫的併發訪問量,防止資料庫過載。

  • 「網路爬蟲控制」:限制爬蟲對目標網站的訪問頻率輕伺服器負擔。

四、用法

使用 RateLimiter限制API請求頻率和使用者登入次數:

import com.google.common.util.concurrent.RateLimiter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class AdvancedRateLimiterDemo {
    // 儲存每個使用者的API請求RateLimiter
    private static final Map<String, RateLimiter> apiRateLimiters = new HashMap<>();
    // 儲存每個使用者的登入嘗試RateLimiter
    private static final Map<String, RateLimiter> loginRateLimiters = new HashMap<>();
    // 用於建立API請求RateLimiter的工廠方法
    public static RateLimiter createApiRateLimiter(double permitsPerSecond) {
        return RateLimiter.create(permitsPerSecond); // 每秒生成的令牌數
    }
    // 用於建立登入嘗試RateLimiter的工廠方法
    public static RateLimiter createLoginRateLimiter(double permitsPerSecond) {
        return RateLimiter.create(permitsPerSecond);
    }
    // 獲取或建立使用者的API請求RateLimiter
    public static RateLimiter getApiRateLimiter(String userId) {
        return apiRateLimiters.computeIfAbsent(userId, k -> createApiRateLimiter(10.0)); // 每秒最多10個API請求
    }
    // 獲取或建立使用者的登入嘗試RateLimiter
    public static RateLimiter getLoginRateLimiter(String userId) {
        return loginRateLimiters.computeIfAbsent(userId, k -> createLoginRateLimiter(1.0)); // 每秒最多1次登入嘗試
    }
    // 模擬API請求
    public static boolean tryApiRequest(String userId) {
        RateLimiter rateLimiter = getApiRateLimiter(userId);
        if (!rateLimiter.tryAcquire()) {
            System.out.println("API請求過於頻繁,請稍後再試。使用者ID: " + userId);
            return false;
        }
        // 這裏可以執行實際的API請求邏輯
        System.out.println("API請求成功處理。使用者ID: " + userId);
        return true;
    }
    // 模擬使用者登入嘗試
    public static boolean tryLoginAttempt(String userId) {
        RateLimiter rateLimiter = getLoginRateLimiter(userId);
        if (!rateLimiter.tryAcquire()) {
            System.out.println("登入嘗試過於頻繁,請稍後再試。使用者ID: " + userId);
            return false;
        }
        // 這裏可以執行實際的登入驗證邏輯
        System.out.println("登入嘗試成功處理。使用者ID: " + userId);
        return true;
    }
    public static void main(String[] args) {
        // 模擬同一使用者連續傳送多個API請求
        String apiUserId = "api-user-123";
        for (int i = 0; i < 15; i++) {
            new Thread(() -> tryApiRequest(apiUserId)).start();
            try {
                TimeUnit.MILLISECONDS.sleep(500); // 每隔500毫秒傳送一個請求
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 模擬同一使用者連續嘗試登入
        String loginUserId = "login-user-456";
        for (int i = 0; i < 10; i++) {
            new Thread(() -> tryLoginAttempt(loginUserId)).start();
            try {
                TimeUnit.MILLISECONDS.sleep(200); // 每隔200毫秒嘗試一次登入
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

定義兩個Map來儲存使用者API請求和登入RateLimiter。tryApiRequest方法模擬API請求的限流邏輯。如果使用者請求過於頻繁(即RateLimiter沒有可用的令牌),則輸出提示資訊並返回false。否則,執行API請求的邏輯(在此處為列印語句)並返回true。

main方法模擬了同一使用者連續傳送多個API請求和連續嘗試登入的場景。由於RateLimiter的限制,部分請求和登入嘗試將會因為頻率過高而被拒絕。

五、實現機制

Guava的RateLimiter基於令牌桶演算法實現,但進行了最佳化以支援平滑的突發流量處理。它內部使用了一個穩定的令牌產生速率和一個可配置的桶容量。當請求到達時,RateLimiter會根據當前的令牌數量和產生速率來決定是否立即處理請求、延遲處理請求還是拒絕請求。這種機制確保了系統在處理突發流量時能夠保持穩定的效能。

六、最佳實踐

在實際專案中運用RateLimiter時:

  • 「合理設定令牌產生速率」:根據系統的實際處理能力和業務需求來設定合理的令牌產生速率。過高的速率可能導致系統過載,而過低的速率則可能限制系統的正常處理能力。

  • 「考慮預熱期」:對於需要快速響應的系統,可以設定一定的預熱期來避免冷啟動問題。預熱期可以確保系統在剛開始執行時就能夠以較高的速率處理請求。

  • 「結合降級策略使用」:當系統面臨過大的壓力時,可以考慮結合降級策略使用RateLimiter。例如,當某個服務的請求量超過限流閾值時,可以將部分請求降級到備用服務或返回快取結果。

  • 「注意執行緒安全」:雖然Guava的RateLimiter是執行緒安全的,但在使用過程中仍然需要注意執行緒安全的問題。特別是在多個執行緒共享同一個RateLimiter例項時,需要確保對令牌的獲取和釋放操作是原子的。

··········  END  ··············

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.