令牌桶限流算法

你提供的 Java 代碼實現的是令牌桶限流算法(Token Bucket Algorithm),這是目前最常用、最靈活的限流算法之一。它允許一定程度的“突發流量”,同時又能控制平均速率。

下面我將:

  1. 逐行詳細解釋?TokenBucketLimiter?類的每一行代碼邏輯和作用
  2. 分析?test4()?方法的運行流程和輸出預期
  3. 指出當前實現的優點與潛在問題
  4. 提供優化建議和改進版本

? 一、TokenBucketLimiter?類逐行解析(帶注釋優化建議)

/*** 令牌桶算法限流實現*/
public class TokenBucketLimiter {// 上一次補充令牌的時間(毫秒)private long lastTime = System.currentTimeMillis();/*** 桶的容量* 作用:表示桶最多能存放多少個令牌* 當桶滿時,新生成的令牌會被丟棄*/private long capacity;/*** 當前令牌數量* 作用:記錄當前桶中可用的令牌數* 每次請求成功時,需從中減去 1 個令牌*/private long tokens;/*** 令牌放入速度,單位:個/秒* 作用:每秒向桶中添加多少個令牌* 例如 rate = 2 表示每秒生成 2 個令牌*/private long rate;/*** 構造函數:初始化令牌桶* @param capacity 桶的最大容量* @param rate 令牌生成速率(每秒生成多少個)*/public TokenBucketLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.tokens = capacity; // 初始狀態:桶是滿的(常見設計)// 注:原代碼未初始化 tokens,會導致初始為 0,這是個 Bug!}/*** 嘗試獲取一個令牌* @return true: 獲取成功(放行),false: 獲取失敗(限流)* 注意:方法加了 synchronized,保證線程安全*/public synchronized boolean tryAcquire() {// 獲取當前時間戳(毫秒)long now = System.currentTimeMillis();// 計算自上次請求以來,應該新增的令牌數量// (now - lastTime) 是毫秒差,除以 1000.0 得到秒數,乘以 rate 得到應生成的令牌數// 使用 Math.round 是為了四舍五入,避免浮點誤差long increasedTokens = Math.round((now - lastTime) / 1000.0 * rate);System.out.println("now=" + now + ", lastTime=" + lastTime + ", increasedTokens=" + increasedTokens);// 更新當前令牌數:新增令牌,但不能超過桶的容量tokens = Math.min(capacity, tokens + increasedTokens);// 更新“上次補充令牌時間”為當前時間lastTime = now;// 判斷是否有足夠令牌(至少 1 個)if (tokens < 1) {// 沒有可用令牌,拒絕請求return false;} else {// 有令牌,領取一個(消耗一個)tokens--;return true; // 放行請求}}
}

? 二、test4()?方法運行流程分析

@Test
public void test4() throws Exception {// 創建令牌桶:容量 10,每秒生成 2 個令牌TokenBucketLimiter limiter = new TokenBucketLimiter(10, 2);for (int i = 0; i < 36; i++) {if (i != 0 && i % 10 == 0) {Thread.sleep(500); // 每處理 10 個請求后,休眠 500ms}boolean tryAcquire = limiter.tryAcquire();if (tryAcquire) {System.out.println("請求" + i + "正常執行");} else {System.out.println("請求" + i + "被限流");}}
}

🧪 運行邏輯模擬(假設啟動時間為 T)

  • 桶容量:10
  • 生成速率:2 個/秒
  • 每 10 次請求后休眠 500ms
問題:tokens?未初始化!

原代碼中:

private long tokens; // 默認值為 0

→ 構造函數沒有初始化 tokens → 初始 tokens = 0

這意味著:第一個請求來時,桶是空的!

這不符合令牌桶“允許突發”的設計初衷。通常應初始化為滿桶


? 修正后邏輯(假設?tokens = capacity = 10?初始化)

前 10 個請求(i=0~9):
  • 請求密集到來,間隔極短
  • now ≈ lastTime?→?increasedTokens ≈ 0
  • tokens?從 10 遞減到 1 → 全部放行
i=10:
  • 執行?Thread.sleep(500),休眠 500ms
  • 下一次請求(i=11)時,距離上次已過去 ~500ms
  • increasedTokens = round(0.5 * 2) = 1
  • tokens = min(10, 0 + 1) = 1(假設之前已用完)
  • tokens >= 1?→ 放行,tokens = 0
后續請求:
  • 若請求間隔短,increasedTokens ≈ 0tokens?仍為 0 → 被限流
  • 每過 500ms 可生成 1 個令牌,可放行 1 次

? 預期輸出(修正后):

請求0正常執行
請求1正常執行
...
請求9正常執行
請求10被限流
請求11正常執行
請求12被限流
請求13被限流
...
請求35被限流

實際上,由于休眠 500ms,每 500ms 可生成 1 個令牌,所以大約每 500ms 能處理 1 個請求。


?? 三、當前實現的問題與優化建議

? 問題1:tokens?未初始化(嚴重 Bug)

private long tokens; // 默認為 0

→ 第一個請求來時,桶是空的!

? 修復:在構造函數中初始化為滿桶:

this.tokens = capacity;

? 問題2:使用?Math.round?可能導致“多補令牌”

long increasedTokens = Math.round((now - lastTime) / 1000.0 * rate);
  • 四舍五入可能導致:本應補 0.6 個,結果補了 1 個 →?變相提高速率
  • 尤其在高并發、高頻調用下,可能累積誤差

? 建議:使用 Math.floor 或直接用 double 累積


? 優化建議1:使用?double?提高精度(推薦)

private double tokens = 0.0; // 支持小數令牌public synchronized boolean tryAcquire() {long now = System.currentTimeMillis();double seconds = (now - lastTime) / 1000.0;double newTokens = seconds * rate;tokens = Math.min(capacity, tokens + newTokens);lastTime = now;if (tokens >= 1.0) {tokens -= 1.0;return true;} else {return false;}
}

更精確,避免浮點舍入誤差,適合高并發場景。


? 問題3:synchronized?可能成為性能瓶頸

  • 所有請求串行化判斷,影響吞吐量
  • 在高并發下,建議使用?AtomicLong?+ CAS 實現無鎖化

? 優化建議2:無鎖版本(使用?AtomicLong

import java.util.concurrent.atomic.AtomicLong;public class TokenBucketLimiter {private final long capacity;private final double rate;private final AtomicLong tokens = new AtomicLong();private final AtomicLong lastTime = new AtomicLong(System.currentTimeMillis());public TokenBucketLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.tokens.set(capacity); // 初始滿桶this.lastTime.set(System.currentTimeMillis());}public boolean tryAcquire() {while (true) {long now = System.currentTimeMillis();long prevTime = lastTime.get();double seconds = (now - prevTime) / 1000.0;double newTokens = seconds * rate;long prevTokens = tokens.get();long updatedTokens = (long) Math.min(capacity, prevTokens + newTokens);if (updatedTokens >= 1) {if (tokens.compareAndSet(prevTokens, updatedTokens - 1) &&lastTime.compareAndSet(prevTime, now)) {return true;}} else {if (lastTime.compareAndSet(prevTime, now)) {return false;}}// CAS 失敗,重試}}
}

更高性能,適合高并發場景。


? 四、總結

項目說明
算法類型令牌桶算法(Token Bucket)
核心思想系統以固定速率生成令牌,請求需“領取令牌”才能執行
優點支持突發流量(桶滿時可瞬間處理多個請求),輸出速率靈活
缺點實現稍復雜,需注意浮點精度和線程安全
適用場景API 限流、網關、高并發服務(如秒殺)
對比漏桶令牌桶允許突發,漏桶輸出更平滑

🔁 令牌桶 vs 漏桶 對比

特性令牌桶漏桶
是否允許突發? 允許(桶滿時可突發)? 不允許(恒定輸出)
輸出速率可變(突發時快)恒定
實現難度中等中等
適用場景高并發、允許突發平滑限流、防止沖擊

? 結論

你的代碼基本正確,但存在一個關鍵 Bug:tokens 未初始化,導致第一個請求可能被錯誤限流。

? 推薦修復后的版本:

public class TokenBucketLimiter {private long lastTime = System.currentTimeMillis();private final long capacity;private long tokens;private final long rate;public TokenBucketLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.tokens = capacity; // 修復:初始化為滿桶}public synchronized boolean tryAcquire() {long now = System.currentTimeMillis();long increasedTokens = (long) Math.floor((now - lastTime) / 1000.0 * rate); // 改用 floortokens = Math.min(capacity, tokens + increasedTokens);lastTime = now;if (tokens >= 1) {tokens--;return true;} else {return false;}}
}

如果你需要,我也可以提供基于 Redis 的分布式令牌桶實現,適用于集群環境。

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

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

相關文章

基于springboot的寵物商城設計與實現

管理員&#xff1a;登錄&#xff0c;個人中心&#xff0c;用戶管埋&#xff0c;寵物分類管理&#xff0c;寵物信息管理&#xff0c;留言反饋&#xff0c;寵物論壇&#xff0c;系統管理&#xff0c;訂單管理用戶&#xff1a;寵物信息&#xff0c;寵物論壇&#xff0c;公告信息&a…

Python day36

浙大疏錦行 Python day36. 復習日 本周內容&#xff1a; 如何導入模塊以及庫項目的規范拆分和寫法官方文檔的閱讀MLP神經網絡的訓練在GPU上訓練模型可視化以及推理

【gaussian-splatting】用自己的數據復現高斯潑濺(一)

1.環境準備1.1.下載diff-gaussian-rasterization這里本來沒啥說的&#xff0c;直接從github上下載就行了&#xff0c;但是我踩坑了&#xff0c;下的版本不對&#xff0c;后續運行報錯參數個數對不上&#xff0c;特在此給大家避坑&#xff0c;注意一定要下帶3dgs版本的diff-gaus…

中國移動h10g-01_S905L處理器安卓7.1當貝純凈版線刷機包帶root權限_融合終端網關

下載固件之前請先將主板上的屏蔽罩取下&#xff0c;查看處理器型號 是否為S905L型號&#xff0c;然后再下載固件進行刷機&#xff1b; 本頁面的固件是采用雙公頭數據線進行刷機的哈&#xff1b; 安卓4.4.2版本固件下載地址&#xff1a;點此進行下載 安卓7.1版本固件下載地址…

夜天之書 #110 涓滴開源:Cronexpr 的故事

在年初的一篇關于商業開源的博文當中&#xff0c;我介紹了在開發商業軟件的過程中&#xff0c;衍生出開源公共軟件庫的模式。在那篇博文里面&#xff0c;我只是簡單羅列了相關開源庫的名字及一句話總結。近期&#xff0c;我會結合商業開源實踐的最新進展&#xff0c;對其中一些…

完整的登陸學生管理系統(配置數據庫)

目錄 要求 思路 1. 登錄模塊&#xff08;LoginFrame.java&#xff09; 2. 學生信息管理模塊&#xff08;StudentFrame.java&#xff09; 3. 數據層&#xff08;StudentDAO.java&#xff09; 4. 業務層&#xff08;StudentService.java / UserService.java&#xff09; 5…

譯 | 在 Python 中從頭開始構建 Qwen-3 MoE

文章出自&#xff1a;基于 2個Expert 的 MoE 架構分步指南 本篇適合 MoE 架構初學者。文章亮點在于詳細拆解 Qwen 3 MoE 架構&#xff0c;并用簡單代碼從零實現 MoE 路由器、RMSNorm 等核心組件&#xff0c;便于理解內部原理。 該方法適用于需部署高性能、高效率大模型&#x…

Spring Boot + ShardingSphere 分庫分表實戰

&#x1f680;Spring Boot ShardingSphere 實戰&#xff1a;分庫分表&#xff0c;性能暴增的終極指南&#xff01; ? 適用場景&#xff1a;千萬級大表、高并發、讀寫分離場景 ? 核心框架&#xff1a;Spring Boot 3.x ShardingSphere-JDBC 5.4.1 ? 數據庫&#xff1a;MySQL…

MaxKB 使用 MCP 連接 Oracle (免安裝 cx_Oracle 和 Oracle Instant Client)

一、背景 安裝cx_Oracle包和Oracle Instant Client來操作數據庫&#xff0c;比較繁瑣同時容易沖突&#xff0c;不同的 Oracle 版本都需要安裝不同的插件。這篇文章將介紹使用 MCP 協議的連接方法。 二、操作步驟 1、使用 1Panel 安裝 DBhub a) 數據庫類型選擇 Oracle 類型。…

基于Python的超聲波OFDM數字通信鏈路設計與實現

基于Python的超聲波OFDM數字通信鏈路設計與實現 摘要 本文詳細介紹了使用Python實現的超聲波OFDM(正交頻分復用)數字通信鏈路系統。該系統能夠在標準音響設備上運行&#xff0c;利用高于15kHz的超聲波頻段進行數據傳輸&#xff0c;采用48kHz采樣率。文章涵蓋了從OFDM基本原理、…

滑動窗口相關題目

近些年來&#xff0c;我國防沙治沙取得顯著成果。某沙漠新種植N棵胡楊&#xff08;編號1-N&#xff09;&#xff0c;排成一排。一個月后&#xff0c;有M棵胡楊未能成活。現可補種胡楊K棵&#xff0c;請問如何補種&#xff08;只能補種&#xff0c;不能新種&#xff09;&#xf…

Java 工具類的“活化石”:Apache Commons 核心用法、性能陷阱與現代替代方案

在上一篇文章中&#xff0c;我們回顧了 Apache Commons 的經典組件。但作為 Java 世界中資歷最老、影響最深遠的工具庫&#xff0c;它的價值遠不止于此。許多開發者可能只使用了它 10% 的功能&#xff0c;卻忽略了另外 80% 能極大提升代碼質量的“隱藏寶石”。本文將提供一個更…

數據結構——圖及其C++實現 多源最短路徑 FloydWarshall算法

目錄 一、前言 二、算法思想 三、代碼實現 四、測試 五、源碼 一、前言 前兩篇學習的Dijkstra算法和Bellman-Ford算法都是用來求解圖的單源最短路徑&#xff0c;即從圖中指定的一個源點出發到圖中其他任意頂點的最短路徑。Dijkstra算法不能求解帶有負權重的圖的最短路徑&…

解決微軟應用商店 (Microsoft store) 打不開,無網絡連接的問題!

很多小伙伴都會遇見微軟應用商店 (Microsoft store)打開后出現無網絡的問題&#xff0c;一般出現這種問題基本都是因為你的電腦安裝了某些銀行的網銀工具&#xff0c;因為網銀工具為了安全會關閉Internet 選項中的最新版本的TLS協議&#xff0c;而微軟商店又需要最新的TLS協議才…

Android—服務+通知=>前臺服務

文章目錄1、Android服務1.1、定義1.2、基本用法1.2.1、定義一個服務1.2.2、服務注冊1.2.3、啟動和停止服務1.2.4、活動和服務進行通信1.3、帶綁定的服務示例1.3.1、定義服務類1.3.2、客戶端&#xff08;Activity&#xff09;綁定與交互?1.3.3、AndroidManifest.xml 注冊?1.3.…

從基礎功能到自主決策, Agent 開發進階路怎么走

Agent 開發進階路線大綱基礎功能實現核心模塊構建環境感知&#xff1a;傳感器數據處理&#xff08;視覺、語音、文本等輸入&#xff09;基礎動作控制&#xff1a;API調用、硬件驅動、簡單反饋機制狀態管理&#xff1a;有限狀態機&#xff08;FSM&#xff09;或行為樹&#xff0…

《動手學深度學習》讀書筆記—9.6編碼器-解碼器架構

本文記錄了自己在閱讀《動手學深度學習》時的一些思考&#xff0c;僅用來作為作者本人的學習筆記&#xff0c;不存在商業用途。 正如我們在9.5機器翻譯中所討論的&#xff0c;機器翻譯是序列轉換模型的一個核心問題&#xff0c;其輸入和輸出都是長度可變的序列。為了處理這種類…

DocBench:面向大模型文檔閱讀系統的評估基準與數據集分析

本文由「大千AI助手」原創發布&#xff0c;專注用真話講AI&#xff0c;回歸技術本質。拒絕神話或妖魔化。搜索「大千AI助手」關注我&#xff0c;一起撕掉過度包裝&#xff0c;學習真實的AI技術&#xff01; 一、數據集概述與核心目標 DocBench 是由研究團隊于2024年提出的首個…

Python高級排序技術:非原生可比對象的自定義排序策略詳解

引言&#xff1a;超越原生比較操作的排序挑戰在Python數據處理中&#xff0c;我們經常需要處理不原生支持比較操作的對象。根據2024年《Python開發者生態系統報告》&#xff0c;在大型項目中&#xff0c;開發者平均需處理28%的自定義對象排序需求&#xff0c;這些對象包括&…

低代碼系統的技術深度:超越“可視化操作”的架構與實現挑戰

在很多非開發者眼中&#xff0c;低代碼平臺似乎只是簡化流程、快速搭建頁面的工具。然而&#xff0c;在真實的企業級應用中&#xff0c;低代碼系統必須面對高并發請求、復雜業務規則、多角色權限、跨系統集成與持續演進等一系列工程挑戰。高效交付&#xff08;Rapid Delivery&a…