Guava限頻器RateLimiter的使用示例

文章目錄

    • 1. 背景說明
    • 2. API與方法
    • 3. 示例代碼
      • 3.1 基礎工具方法
      • 3.2 測試任務類
      • 3.3 測試和統計方法
      • 3.4 測試兩種模式的限頻器
      • 3.5 測試緩沖時間與等待耗時
    • 4. 完整的測試代碼
    • 5. 簡單小結

1. 背景說明

高并發應用場景有3大利器: 緩存、限流、熔斷。

也有說4利器的: 緩存、限流、熔斷、降級。

每一種技術都有自己的適用場景,也有很多使用細節和注意事項。

本文主要介紹 Guava 工具庫中的限頻器(RateLimiter), 也可以稱之為限流器。

限流技術可以簡單分為兩類:

  • 限制TPS, 也就是每秒的業務請求數。 有時候也可以用 QPS 來表示, 即每秒請求數。
  • 限制并發數, 也就是同一時刻處理的最大并發請求數。 常用的技術,包括線程池+等待隊列,或者簡單使用 信號量(Semaphore) 來進行控制。

限頻器(RateLimiter)的適用場景:

限制客戶端每秒訪問服務器的次數。

可以在單個接口使用,
也可以對多個接口使用,
甚至我們還可以使用注解與參數,通過AOP切面進行靈活的編程。(本文不介紹)

2. API與方法

guava工具庫的MAVEN依賴為:

<properties><guava.version>33.1.0-jre</guava.version>
</properties><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>${guava.version}</version>
</dependency>

主要的類結構和方法如下所示:

package com.google.common.util.concurrent;public abstract class RateLimiter {// 1. 內部實現創建的是 SmoothBursty 模式的限頻器// permitsPerSecond 參數就是每秒允許的授權數量public static RateLimiter create(double permitsPerSecond)//...// 2. 內部實現創建的是 SmoothWarmingUp 模式的限頻器// 傳入預熱的時間: 在預熱期間內, 每秒發放的許可數比 permitsPerSecond 少// 主要用于保護服務端, 避免剛啟動就被大量的請求打死。public static RateLimiter create(double permitsPerSecond,Duration warmupPeriod) // ...public static RateLimiter create(double permitsPerSecond,long warmupPeriod, TimeUnit unit) //...// 3. 使用過程中, 支持動態修改每秒限頻次數public final void setRate(double permitsPerSecond) // ...// 4. 獲取許可; 拿不到就死等;public double acquire()// ...public double acquire(int permits)//...// 5. 嘗試獲取許可  public boolean tryAcquire()//...public boolean tryAcquire(int permits)//...// 5.1 重點在這里; 嘗試獲取許可時, 可以設置一個容許的緩沖時間;// 使用場景是: 放過短時間內的, 聚簇的, 一定數量的請求;// 比如: n毫秒內, 接連來了m個請求; // 如果這m個請求都需要放過, 就需要設置一定的緩沖時間;// 參見下文的測試代碼;public boolean tryAcquire(Duration timeout)//...public boolean tryAcquire(long timeout, TimeUnit unit)//...public boolean tryAcquire(int permits, Duration timeout)//...public boolean tryAcquire(int permits, long timeout, TimeUnit unit)//...
}// 平滑限頻器
abstract class SmoothRateLimiter extends RateLimiter {static final class SmoothBursty extends SmoothRateLimiter {}// 平滑預熱: 顧名思義, 需要一個預熱時間才能到達static final class SmoothWarmingUp extends SmoothRateLimiter {}
}

3. 示例代碼

這部分依次介紹我們的示例代碼。

3.1 基礎工具方法

下面是一些基礎工具方法:

    // 睡眠一定的毫秒數private static void sleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {e.printStackTrace();}}// 打印控制臺日志private static void println(String msg) {System.out.println("[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "]" + msg);}

3.2 測試任務類

下面是一個測試任務類, 內部使用了 RateLimiter#tryAcquire 方法。

    private static class RateLimiterJob implements Runnable {//CountDownLatch latch;RateLimiter rateLimiter;// 結果StringBuilder resultBuilder = new StringBuilder();AtomicInteger passedCounter = new AtomicInteger();AtomicInteger rejectedCounter = new AtomicInteger();public RateLimiterJob(int taskCount, RateLimiter rateLimiter) {this.latch = new CountDownLatch(taskCount);this.rateLimiter = rateLimiter;}@Overridepublic void run() {//boolean passed = rateLimiter.tryAcquire(1, 5, TimeUnit.MILLISECONDS);if (passed) {passedCounter.incrementAndGet();resultBuilder.append("1");} else {rejectedCounter.incrementAndGet();resultBuilder.append("-");}//latch.countDown();}}

也加上了一些并發控制的手段和統計方法, 以方便我們進行測試:

3.3 測試和統計方法

真正的測試和統計方法為:

    private static ExecutorService executorService = Executors.newFixedThreadPool(8, new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setDaemon(true);t.setName("RateLimiter-1");return t;}});private static String metrics(RateLimiter rateLimiter, int taskCount) {long startMillis = System.currentTimeMillis();// 休息1SrateLimiter.tryAcquire();sleep(1_000);//RateLimiterJob job = new RateLimiterJob(taskCount, rateLimiter);for (int i = 0; i < taskCount; i++) {sleep(10);executorService.submit(job);}// 等待結果try {job.latch.await();} catch (InterruptedException e) {e.printStackTrace();}long costMillis = System.currentTimeMillis() - startMillis;//String result = job.resultBuilder.toString();result = result + "[passed=" + job.passedCounter.get() +", rejected=" + job.rejectedCounter.get() + "]"+ "[耗時=" + costMillis + "ms]";return result;}

這里創建了一個并發線程池, 用來模擬多個并發請求客戶端, 也保證了短時間內有一定的聚簇流量。

metrics 方法, 對 rateLimiter 進行一定數量的任務測試, 并返回統計結果;

3.4 測試兩種模式的限頻器

下面的代碼, 測試兩種模式的限頻器:

private static void testRateLimit() {//double permitsPerSecond = 20D;int taskCount = 100;println("========================================");// 1. SmoothBursty 模式的限頻器: 平滑分配token, 可以看代碼實現RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond);// 111111111111111111111111111-1---1---1--1---1---1---1// ---1---1---1----1--1---1---1----1--1---1---1---1// [passed=46, rejected=54][耗時=2346ms]String result = metrics(rateLimiter, taskCount);println("1. SmoothBursty 模式的限頻器.result:==========" + result);println("========================================");// 2. SmoothWarmingUp 模式的限頻器: 系統需要預熱的話,最初的時候,放行的請求會比較少;rateLimiter = RateLimiter.create(permitsPerSecond, 1, TimeUnit.SECONDS);// 1-----------1----------1---------1---------1--------1// -------1------1-----1-----1----1---1---1---1---// [passed=14, rejected=86][耗時=2251ms]result = metrics(rateLimiter, taskCount);println("2. SmoothWarmingUp 模式的限頻器.result:==========" + result);println("========================================");}

我將輸出的內容放在了雙斜線注釋里面, 1表示放行, -表示拒絕。
可以看到:

  • SmoothBursty 模式, 直接放過了前面的一定量的聚簇流量
  • SmoothWarmingUp 模式, 開始時在預熱, 放過的請求較少, 預熱完成后正常放行和拒絕。

3.5 測試緩沖時間與等待耗時

下面的方法, 測試 tryAcquire 方法指定緩沖時間時, 會消耗多少時間等待。

private static void testRateLimitTimeout() {int permitsPerSecond = 500;RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond);//int timeout = 50;int clusterCount = timeout * permitsPerSecond / 1000;AtomicInteger passedCount = new AtomicInteger(0);long startMillis = System.currentTimeMillis();long maxTimeoutMillis = 0;for (int i = 0; i < clusterCount; i++) {long beginMillis = System.currentTimeMillis();// 限頻時使用緩沖時間區間: 短暫放過聚集在一起的少量(并發)請求數: // 放過的數量為: timeout * permitsPerSecond/1000boolean passed = rateLimiter.tryAcquire(1, 50, TimeUnit.MILLISECONDS);if (passed) {passedCount.incrementAndGet();}long timeoutMillis = System.currentTimeMillis() - beginMillis;maxTimeoutMillis = Math.max(timeoutMillis, maxTimeoutMillis);}long costMillis = System.currentTimeMillis() - startMillis;// [2025-04-28 22:49:00]testRateLimitTimeout:// [clusterCount=25];[passedCount=25]println("testRateLimitTimeout:[clusterCount=" + clusterCount + "];[passedCount=" + passedCount.get() + "]");// [2025-04-28 22:49:00]testRateLimitTimeout:// 耗時:[costMillis=47][maxTimeoutMillis=3]println("testRateLimitTimeout:耗時:[costMillis=" +costMillis + "][maxTimeoutMillis=" + maxTimeoutMillis + "]");}

我們的測試條件為: timeout = 50; permitsPerSecond = 500.
放過的聚簇流量公式為: timeout * permitsPerSecond/1000
可以看到, 測試結果里面的日志為:

[clusterCount=25];[passedCount=25]

符合我們的預期和計算。

等待耗時時間最大為 maxTimeoutMillis=3, 這個等待時間還可以接受:

耗時:[costMillis=47][maxTimeoutMillis=3]

我們使用時根據需要配置相關參數即可。

4. 完整的測試代碼

完整的測試代碼如下所示:

import com.google.common.util.concurrent.RateLimiter;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;// 測試限頻:
public class RateLimiterTimeoutTest {private static ExecutorService executorService = Executors.newFixedThreadPool(8, new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setDaemon(true);t.setName("RateLimiter-1");return t;}});// 測試性能public static void main(String[] args) {testRateLimitTimeout();testRateLimit();}private static void testRateLimitTimeout() {int permitsPerSecond = 500;RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond);//int timeout = 50;int clusterCount = timeout * permitsPerSecond / 1000;AtomicInteger passedCount = new AtomicInteger(0);long startMillis = System.currentTimeMillis();long maxTimeoutMillis = 0;for (int i = 0; i < clusterCount; i++) {long beginMillis = System.currentTimeMillis();// 限頻時使用緩沖時間區間: 短暫放過聚集在一起的少量(并發)請求數: // 放過的數量為: timeout * permitsPerSecond/1000boolean passed = rateLimiter.tryAcquire(1, 50, TimeUnit.MILLISECONDS);if (passed) {passedCount.incrementAndGet();}long timeoutMillis = System.currentTimeMillis() - beginMillis;maxTimeoutMillis = Math.max(timeoutMillis, maxTimeoutMillis);}long costMillis = System.currentTimeMillis() - startMillis;// [2025-04-28 22:49:00]testRateLimitTimeout:// [clusterCount=25];[passedCount=25]println("testRateLimitTimeout:[clusterCount=" + clusterCount + "];[passedCount=" + passedCount.get() + "]");// [2025-04-28 22:49:00]testRateLimitTimeout:// 耗時:[costMillis=47][maxTimeoutMillis=3]println("testRateLimitTimeout:耗時:[costMillis=" +costMillis + "][maxTimeoutMillis=" + maxTimeoutMillis + "]");}private static void testRateLimit() {//double permitsPerSecond = 20D;int taskCount = 100;println("========================================");// 1. SmoothBursty模式的限頻器: 平滑分配token, 可以看代碼實現RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond);// 111111111111111111111111111-1---1---1--1---1---1---1// ---1---1---1----1--1---1---1----1--1---1---1---1// [passed=46, rejected=54][耗時=2346ms]String result = metrics(rateLimiter, taskCount);println("1. SmoothBursty 模式的限頻器.result:==========" + result);println("========================================");// 2. SmoothWarmingUp模式的限頻器: 系統需要預熱的話,最初的時候,放行的請求會比較少;rateLimiter = RateLimiter.create(permitsPerSecond, 1, TimeUnit.SECONDS);// 1-----------1----------1---------1---------1--------1// -------1------1-----1-----1----1---1---1---1---// [passed=14, rejected=86][耗時=2251ms]result = metrics(rateLimiter, taskCount);println("2. SmoothWarmingUp 模式的限頻器.result:==========" + result);println("========================================");}private static String metrics(RateLimiter rateLimiter, int taskCount) {long startMillis = System.currentTimeMillis();// 休息1SrateLimiter.tryAcquire();sleep(1_000);//RateLimiterJob job = new RateLimiterJob(taskCount, rateLimiter);for (int i = 0; i < taskCount; i++) {sleep(10);executorService.submit(job);}// 等待結果try {job.latch.await();} catch (InterruptedException e) {e.printStackTrace();}long costMillis = System.currentTimeMillis() - startMillis;//String result = job.resultBuilder.toString();result = result + "[passed=" + job.passedCounter.get() +", rejected=" + job.rejectedCounter.get() + "]"+ "[耗時=" + costMillis + "ms]";return result;}private static void sleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {e.printStackTrace();}}private static void println(String msg) {System.out.println("[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "]" + msg);}private static class RateLimiterJob implements Runnable {//CountDownLatch latch;RateLimiter rateLimiter;// 結果StringBuilder resultBuilder = new StringBuilder();AtomicInteger passedCounter = new AtomicInteger();AtomicInteger rejectedCounter = new AtomicInteger();public RateLimiterJob(int taskCount, RateLimiter rateLimiter) {this.latch = new CountDownLatch(taskCount);this.rateLimiter = rateLimiter;}@Overridepublic void run() {//boolean passed = rateLimiter.tryAcquire(1, 5, TimeUnit.MILLISECONDS);if (passed) {passedCounter.incrementAndGet();resultBuilder.append("1");} else {rejectedCounter.incrementAndGet();resultBuilder.append("-");}//latch.countDown();}}}

測試代碼總的只有100多行, 并不是很復雜。

5. 簡單小結

本文簡單介紹了Guava限頻器(RateLimiter)的用法。
使用要點是 tryAcquire 時需要給一定量的緩沖時間, 避免聚簇的少量請求被誤攔截。

我們的測試條件為: timeout = 50; permitsPerSecond = 500.
放過的聚簇流量公式為: timeout * permitsPerSecond/1000

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/82232.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/82232.shtml
英文地址,請注明出處:http://en.pswp.cn/web/82232.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(面試)獲取View寬高的幾種方式

Android 中獲取 View 寬高的幾種方式&#xff0c;以及它們的適用場景和注意事項&#xff1a; 1. View.getWidth() 和 View.getHeight() 原理: 直接從 View 對象中獲取已經計算好的寬度和高度。 優點: 簡單直接。 缺點: 在 onCreate()、onStart() 等生命周期方法中&#xff0…

PostgreSQL pgrowlocks 擴展

PostgreSQL pgrowlocks 擴展 pgrowlocks 是 PostgreSQL 的一個系統擴展&#xff0c;用于顯示表中行級鎖定信息。這個擴展特別適合診斷鎖爭用問題和性能調優。 一、擴展安裝與啟用 1. 安裝擴展 -- 使用超級用戶安裝 CREATE EXTENSION pgrowlocks;2. 驗證安裝 -- 查看擴展是…

JavaSE知識總結 ~個人筆記以及不斷思考~持續更新

目錄 字符串常量池 如果是創建對象還會嗎&#xff1f; Integer也是在字串常量池中復用&#xff1f; 字符串拼接 為什么String是不可變的&#xff1f; String的不可變性是怎么做的&#xff1f; 外部代碼不能創建對象&#xff1f; 構造方法不是私有的嗎&#xff1f; 怎么…

使用HTTPS進行傳輸加密

文章目錄 說明示例&#xff08;公網上的公開web&#xff09;安裝SSL證書Certbot 的 Webroot 模式 和 Standalone 模式的區別**Webroot 模式****Standalone 模式** 技術對比表Node.js 場景下的最佳實踐推薦方案&#xff1a;**Webroot 模式**Standalone 模式應急使用&#xff1a;…

驅動開發(2)|魯班貓rk3568簡單GPIO波形操控

上篇文章寫了如何下載內核源碼、編譯源碼的詳細步驟&#xff0c;以及一個簡單的官方demo編譯&#xff0c;今天分享一下如何根據板子的引腳寫自己控制GPIO進行高低電平反轉。 想要控制GPIO之前要學會看自己的引腳分布圖&#xff0c;我用的是魯班貓RK3568&#xff0c;引腳分布圖如…

ArcGIS Pro 3.4 二次開發 - 布局

環境:ArcGIS Pro SDK 3.4 + .NET 8 文章目錄 布局1 布局工程項1.1 引用布局工程項及其關聯的布局1.2 在新視圖中打開布局工程項1.3 激活已打開的布局視圖1.4 引用活動布局視圖1.5 將 pagx 導入工程1.6 移除布局工程項1.7 創建并打開一個新的基本布局1.8 使用修改后的CIM創建新…

OpenCV 圖像像素的算術操作

一、知識點 1、operator (1)、MatExpr operator (const Mat & a, const Mat & b); a、a和b的行數、列數、通道數得相同。 b、a和b的每個像素的每個通道值分別相加。 (2)、MatExpr operator (const Mat & a, const Scalar & s); a、若a…

音視頻中的復用器

&#x1f3ac; 什么是復用器&#xff08;Muxer&#xff09;&#xff1f; 復用器&#xff08;muxer&#xff09;是負責把音頻、視頻、字幕等多個媒體流打包&#xff08;封裝&#xff09;成一個單一的文件格式的組件。 &#x1f4a1; 舉個形象的例子&#xff1a; 假設你有兩樣東…

數據庫安全性

一、計算機安全性概論 &#xff08;一&#xff09;核心概念 數據庫安全性&#xff1a;保護數據庫免受非法使用導致的數據泄露、更改或破壞&#xff0c;是衡量數據庫系統的關鍵指標之一&#xff0c;與計算機系統安全性相互關聯。計算機系統安全性&#xff1a;通過各類安全保護…

【Linux網絡編程】網絡層IP協議

目錄 IP協議的協議頭格式 網段劃分 特殊的IP地址 IP地址的數量限制 私有IP地址和公網IP地址 路由 IP協議的協議頭格式 4位版本號 &#xff1a;指定IP協議的版本&#xff0c;對于IPv4&#xff0c;版本號就是4。 4位首部長度&#xff1a;表名IP協議報頭的長度&#xff0c;單…

“候選對話鏈”(Candidate Dialogue Chain)概念

目錄 一、定義與形式 二、生成過程詳解 1. 語言模型生成&#xff08;LLM-Based Generation&#xff09; 2. 知識圖譜支持&#xff08;KG-Augmented Generation&#xff09; 3. 策略調控&#xff08;Policy-Driven Planning&#xff09; 三、候選對話鏈的屬性 四、候選對…

Unity中的JsonManager

1.具體代碼 先貼代碼 using LitJson; using System.IO; using UnityEngine;/// <summary> /// 序列化和反序列化Json時 使用的是哪種方案 有兩種 JsonUtility 不能直接序列化字典 ligJson可以序列化字典 /// </summary> public enum JsonType {JsonUtilit…

50天50個小項目 (Vue3 + Tailwindcss V4) ? | Split Landing Page(拆分展示頁)

&#x1f4c5; 我們繼續 50 個小項目挑戰&#xff01;—— SplitLandingPage 組件 倉庫地址&#xff1a;https://github.com/SunACong/50-vue-projects 項目預覽地址&#xff1a;https://50-vue-projects.vercel.app/ 在這篇文章中&#xff0c;我們將實現一個交互式的左右面板…

機器學習-ROC曲線?? 和 ??AUC指標

1. 什么是ROC曲線&#xff1f;?? ROC&#xff08;Receiver Operating Characteristic&#xff0c;受試者工作特征曲線&#xff09;是用來評估??分類模型性能??的一種方法&#xff0c;特別是針對??二分類問題??&#xff08;比如“患病”或“健康”&#xff09;。 ?…

Docker容器創建Redis主從集群

利用虛擬機中的三個Docker容器創建主從集群&#xff0c;容器信息&#xff1a; 容器名角色IP映射端口r1master192.168.150.1017001r2slave192.168.150.1017002r3slave192.168.150.1017003 啟動多個redis實例 新建一個docker-compose文件來構建主從集群&#xff1a; 文件內容&…

手寫ArrayList和LinkedList

項目倉庫&#xff1a;https://gitee.com/bossDuy/hand-tear-collection-series 基于b站up生生大佬&#xff1a;https://www.bilibili.com/video/BV1Kp5tzGEc5/?spm_id_from333.788.videopod.sections&vd_source4cda4baec795c32b16ddd661bb9ce865 LinkedList package com…

每日c/c++題 備戰藍橋杯(Cantor 表)

Cantor 表的探究與實現 在數學中&#xff0c;有理數的可枚舉性是一個令人驚嘆的結論。今天&#xff0c;就讓我們一起深入探討這個經典問題&#xff0c;并分享一段精心編寫的代碼&#xff0c;揭開這一數學奧秘的神秘面紗。 問題背景 在 19 世紀末&#xff0c;偉大的數學家康托…

解決idea與springboot版本問題

遇到以下問題&#xff1a; 1、springboot3.2.0與jdk1.8 提示這個包org.springframework.web.bind.annotation不存在&#xff0c;但是pom已經引入了spring-boot-starter-web 2、Error:Cannot determine path to tools.jar library for 17 (D:/jdk17) 3、Error:(3, 28) java: …

Notepad++找回自動暫存的文件

場景&#xff1a; 當你沒有保存就退出Notepad&#xff0c;下次進來Notepad會自動把你上次編輯的內容顯示出來&#xff0c;以便你繼續編輯。除非你手動關掉當前頁面&#xff0c;這樣Notepad就會刪除掉自動保存的內容。 問題&#xff1a; Notepad會將自動保存的文件地址,打開Note…

yolov12畢設前置知識準備 1

1 什么是目標檢測呢&#xff1f; 目標檢測&#xff08;Object Detection&#xff09;主要用于識別圖像或視頻中特定類型物體的位置&#xff0c;并標注其類別。 簡單來說&#xff0c;就是讓計算機像人類一樣 “看懂” 圖像內容&#xff0c;不僅能識別出物體&#xff08;如人、…