后端下載限速(redis記錄實時并發,bucket4j動態限速)

  • ? 使用 Redis 記錄 所有用戶的實時并發下載數
  • ? 使用 Bucket4j 實現 全局下載速率限制(動態)
  • ? 支持 動態調整限速策略
  • ? 下載接口安全、穩定、可監控

🧩 整體架構概覽

模塊功能
Redis存儲全局并發數和帶寬令牌桶狀態
Bucket4j + Redis分布式限速器(基于令牌桶算法)
Spring Boot Web提供文件下載接口
AOP / Interceptor(可選)用于統一處理限流邏輯

📦 1. Maven 依賴(pom.xml

<dependencies><!-- Spring Boot --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Redis 連接池 --><dependency><groupId>io.lettuce.core</groupId><artifactId>lettuce-core</artifactId></dependency><!-- Bucket4j 核心與 Redis 集成 --><dependency><groupId>com.github.vladimir-bukhtoyarov</groupId><artifactId>bucket4j-core</artifactId><version>5.3.0</version></dependency><dependency><groupId>com.github.vladimir-bukhtoyarov</groupId><artifactId>bucket4j-redis</artifactId><version>5.3.0</version></dependency></dependencies>

🛠? 2. Redis 工具類:記錄全局并發數

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;import java.util.Collections;
import java.util.concurrent.TimeUnit;@Component
public class GlobalDownloadCounter {private final StringRedisTemplate redisTemplate;private final DefaultRedisScript<Long> incrScript;private final DefaultRedisScript<Long> decrScript;public static final String KEY_CONCURRENT = "global:download:concurrent";private static final long TTL_SECONDS = 60; // 自動清理僵尸計數public GlobalDownloadCounter(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;// Lua 腳本:原子增加并發數并設置過期時間String scriptIncr = """local key = KEYS[1]local ttl = tonumber(ARGV[1])local count = redis.call('GET', key)if not count thenredis.call('SET', key, 1)redis.call('EXPIRE', key, ttl)return 1elsecount = tonumber(count) + 1redis.call('SET', key, count)redis.call('EXPIRE', key, ttl)return countend""";incrScript = new DefaultRedisScript<>(scriptIncr, Long.class);// Lua 腳本:原子減少并發數String scriptDecr = """local key = KEYS[1]local count = redis.call('GET', key)if not count or tonumber(count) <= 0 thenreturn 0elsecount = tonumber(count) - 1redis.call('SET', key, count)return countend""";decrScript = new DefaultRedisScript<>(scriptDecr, Long.class);}public long increment() {return redisTemplate.execute(incrScript, Collections.singletonList(KEY_CONCURRENT), TTL_SECONDS).longValue();}public long decrement() {return redisTemplate.execute(decrScript, Collections.singletonList(KEY_CONCURRENT)).longValue();}public long getCurrentCount() {String value = redisTemplate.opsForValue().get(KEY_CONCURRENT);return value == null ? 0 : Long.parseLong(value);}
}

?? 3. Bucket4j 配置:分布式限速器(帶 Redis)

import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill;
import io.github.bucket4j.distributed.proxy.ProxyManager;
import io.github.bucket4j.distributed.proxy.RedisProxyManager;
import io.github.bucket4j.redis.lettuce.cas.LettuceReactiveProxyManager;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.time.Duration;@Configuration
public class BandwidthLimiterConfig {@Beanpublic RedisClient redisClient() {return RedisClient.create("redis://localhost:6379");}@Beanpublic StatefulRedisConnection<String, String> redisConnection(RedisClient redisClient) {return redisClient.connect();}@Beanpublic ProxyManager<String> proxyManager(StatefulRedisConnection<String, String> connection) {return LettuceReactiveProxyManager.builder().build(connection.reactive());}@Beanpublic Bandwidth globalBandwidthLimit() {// 默認 10MB/sreturn Bandwidth.classic(10 * 1024 * 1024, Refill.greedy(10 * 1024 * 1024, Duration.ofSeconds(1)));}@Beanpublic Bucket globalBandwidthLimiter(ProxyManager<String> proxyManager, Bandwidth bandwidthLimit) {return proxyManager.builder().build("global:bandwidth:limiter", bandwidthLimit);}
}

📡 4. 下載接口實現

import io.github.bucket4j.Bucket;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;@RestController
@RequestMapping("/api/download")
public class DownloadController {private static final int MAX_CONCURRENT_DOWNLOADS = 100;private final GlobalDownloadCounter downloadCounter;private final Bucket bandwidthLimiter;public DownloadController(GlobalDownloadCounter downloadCounter, Bucket bandwidthLimiter) {this.downloadCounter = downloadCounter;this.bandwidthLimiter = bandwidthLimiter;}@GetMapping("/{fileId}")public void downloadFile(@PathVariable String fileId, HttpServletResponse response) throws IOException {long currentCount = downloadCounter.getCurrentCount();if (currentCount >= MAX_CONCURRENT_DOWNLOADS) {response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);response.getWriter().write("Too many downloads. Please try again later.");return;}downloadCounter.increment();try {// 設置響應頭response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment; filename=" + fileId + ".bin");ServletOutputStream out = response.getOutputStream();// 文件路徑(示例)String filePath = "/path/to/files/" + fileId + ".bin";if (!Files.exists(Paths.get(filePath))) {response.setStatus(HttpServletResponse.SC_NOT_FOUND);response.getWriter().write("File not found.");return;}byte[] buffer = new byte[8192]; // 每次讀取 8KBRandomAccessFile file = new RandomAccessFile(filePath, "r");int bytesRead;while ((bytesRead = file.read(buffer)) != -1) {if (bytesRead > 0) {boolean consumed = bandwidthLimiter.tryConsume(bytesRead);if (!consumed) {Thread.sleep(100); // 等待令牌生成continue;}out.write(buffer, 0, bytesRead);out.flush();}}file.close();out.close();} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 恢復中斷狀態} finally {downloadCounter.decrement();}}
}

🔁 5. 動態調整下載速率接口(可選)

@RestController
@RequestMapping("/api/limit")
public class RateLimitController {private final Bucket bandwidthLimiter;private final Bandwidth globalBandwidthLimit;public RateLimitController(Bucket bandwidthLimiter, Bandwidth globalBandwidthLimit) {this.bandwidthLimiter = bandwidthLimiter;this.globalBandwidthLimit = globalBandwidthLimit;}@PostMapping("/set-bandwidth")public String setBandwidth(@RequestParam int mbPerSecond) {Bandwidth newLimit = Bandwidth.classic(mbPerSecond * 1024 * 1024,Refill.greedy(mbPerSecond * 1024 * 1024, Duration.ofSeconds(1)));bandwidthLimiter.replaceConfiguration(newLimit);return "Global bandwidth limit updated to " + mbPerSecond + " MB/s";}
}

📊 6. 監控接口(可選)

@GetMapping("/monitor/concurrent")
public ResponseEntity<Long> getConcurrentDownloads() {return ResponseEntity.ok(downloadCounter.getCurrentCount());
}

🧪 7. 測試建議

你可以使用 curl 或 Postman 發起多并發請求測試:

for i in {1..200}; docurl -X GET "http://localhost:8080/api/download/file1" --output "file$i.bin" &
done

觀察是否觸發限流、并發控制是否生效。


? 總結

組件作用
Redis分布式存儲并發數和限流令牌桶
Lua 腳本原子操作并發計數器
Bucket4j + Redis全局下載速率限制
Spring Boot Controller處理下載邏輯
try-finally保證資源釋放
動態接口 /set-bandwidth支持運行時修改限速

📌 擴展建議(可選)

  • 將限流邏輯封裝到 AOP 切面
  • 添加 Prometheus 指標暴露并發數、限流次數等
  • 使用 Nginx 或 Gateway 做額外的限流保護
  • 加入用戶身份識別,支持 用戶級限速
  • 使用 Kafka 異步記錄日志或審計下載行為

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

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

相關文章

android app 一個 crash的解決過程!

一、日志&#xff1a; crash 2024-10-25 12:15:33.020 2113-2113 AndroidRuntime pid-2113 E FATAL EXCEPTION: main Process: com..workhome, PID: 2113 java.lang.RuntimeException: Unable to start activity ComponentInfo{com..w…

[Java 基礎]Object 類

java.lang.Object 是 Java 所有類的直接或間接父類&#xff0c;Java 中每個類都默認繼承 Object 類&#xff08;即使你沒寫 extends Object&#xff09;。 Object 中的常用方法&#xff1a; 方法名功能簡介toString()返回對象的字符串表示equals(Object)判斷兩個對象是否“邏…

大數據學習(135)-Linux系統性指令

&#x1f34b;&#x1f34b;大數據學習&#x1f34b;&#x1f34b; &#x1f525;系列專欄&#xff1a; &#x1f451;哲學語錄: 用力所能及&#xff0c;改變世界。 &#x1f496;如果覺得博主的文章還不錯的話&#xff0c;請點贊&#x1f44d;收藏??留言&#x1f4dd;支持一…

【Fifty Project - D35】

今日完成記錄 TimePlan完成情況7&#xff1a;00 - 7&#xff1a;40爬坡√8&#xff1a;30 - 11&#xff1a;30Rabbit MQ√17&#xff1a;30 - 18&#xff1a;30羽毛球√ RabbitMQ 消費者端如何保證可靠性&#xff1f; 消息投遞過程出現網絡故障消費者接收到消息但是突然宕機…

P3 QT項目----記事本(3.4)

3.4 文件選擇對話框 QFileDialog 3.4.1 QFileDialog 開發流程 使用 QFileDialog 的基本步驟通常如下&#xff1a; 實例化 &#xff1a;首先&#xff0c;創建一個 QFileDialog 對象的實例。 QFileDialog qFileDialog;設置模式 &#xff1a;根據需要設置對話框的模式&…

學習筆記(26):線性代數-張量的降維求和,簡單示例

學習筆記(26)&#xff1a;線性代數-張量的降維求和&#xff0c;簡單示例 1.先理解 “軸&#xff08;Axis&#xff09;” 的含義 張量的 “軸” 可以理解為 維度的方向索引 。對于形狀為 (2, 3, 4) 的張量&#xff0c;3 個軸的含義是&#xff1a; 軸 0&#xff08;axis0&…

健康檔案實訓室:構建全周期健康管理的數據基石

一、健康檔案實訓室建設背景 隨著“健康中國2030”戰略深入推進&#xff0c;健康檔案作為居民健康數據的核心載體&#xff0c;在疾病預防、慢性病管理、醫療決策等領域的價值日益凸顯。在此背景下&#xff0c;健康檔案實訓室建設成為職業院校對接政策要求、培養專業健康管理…

【MATLAB第119期】基于MATLAB的KRR多輸入多輸出全局敏感性分析模型運用(無目標函數,考慮代理模型)

【MATLAB第119期】基于MATLAB的KRR多輸入多輸出全局敏感性分析模型運用&#xff08;無目標函數&#xff0c;考慮代理模型&#xff09; 下一期研究SHAP的多輸入多輸出敏感性分析方法 一、SOBOL&#xff08;無目標函數&#xff09; &#xff08;1&#xff09;針對簡單線性數據…

Linux常用文件目錄命令

瀏覽目錄命令&#xff1a; ls 、pwd目錄操作命令&#xff1a;cd、mkdir、rmdir瀏覽文件命令&#xff1a;cat、more、less、head、tail文件操作命令&#xff1a;cp、rm、mv、find、grep、tar 瀏覽目錄命令 ls ? 命令名稱&#xff1a;ls ? 命令英文原意&#xff1a;list ? …

PIN碼vs密碼,電腦登錄的快捷鍵你用對了嗎?

你是否也遇到過這樣的窘境&#xff1a;信心滿滿地輸入電腦開機密碼&#xff0c;屏幕卻無情地提示“密碼錯誤”。仔細一看&#xff0c;才發現登錄界面悄悄地變成了要求輸入“PIN碼”。這種因為混淆了PIN碼和賬戶密碼而導致的開機失敗&#xff0c;相信不少朋友都碰到過。 PIN碼作…

【大模型科普】AIGC技術發展與應用實踐(一文讀懂AIGC)

【作者主頁】Francek Chen 【專欄介紹】 ? ? ?人工智能與大模型應用 ? ? ? 人工智能&#xff08;AI&#xff09;通過算法模擬人類智能&#xff0c;利用機器學習、深度學習等技術驅動醫療、金融等領域的智能化。大模型是千億參數的深度神經網絡&#xff08;如ChatGPT&…

Spring是如何解決Bean的循環依賴:三級緩存機制

1、什么是 Bean 的循環依賴 在 Spring框架中,Bean 的循環依賴是指多個 Bean 之間?互相持有對方引用?,形成閉環依賴關系的現象。 多個 Bean 的依賴關系構成環形鏈路,例如: 雙向依賴:Bean A 依賴 Bean B,同時 Bean B 也依賴 Bean A(A?B)。鏈條循環: Bean A → Bean…

XXE漏洞知識

目錄 1.XXE簡介與危害 XML概念 XML與HTML的區別 1.pom.xml 主要作用 2.web.xml 3.mybatis 2.XXE概念與危害 案例&#xff1a;文件讀取&#xff08;需要Apache >5.4版本&#xff09; 案例&#xff1a;內網探測&#xff08;雞肋&#xff09; 案例&#xff1a;執行命…

02-性能方案設計

需求分析與測試設計 根據具體的性能測試需求&#xff0c;確定測試類型&#xff0c;以及壓測的模塊(web/mysql/redis/系統整體)前期要與相關人員充分溝通&#xff0c;初步確定壓測方案及具體的性能指標QA完成性能測試設計后&#xff0c;需產出測試方案文檔發送郵件到項目組&…

STL優先級隊列的比較函數與大堆小堆的關系

STL中的priority_queue&#xff08;優先級隊列&#xff09;通過比較函數來確定元素的優先級順序&#xff0c;從而決定其內部是形成大堆還是小堆。以下是關鍵點總結&#xff1a; 默認行為與大堆&#xff1a; 默認情況下&#xff0c;priority_queue使用std::less<T>作為比較…

React---day11

14.4 react-redux第三方庫 提供connect、thunk之類的函數 以獲取一個banner數據為例子 store&#xff1a; 我們在使用異步的時候理應是要使用中間件的&#xff0c;但是configureStore 已經自動集成了 redux-thunk&#xff0c;注意action里面要返回函數 import { configureS…

OD 算法題 B卷【反轉每對括號間的子串】

文章目錄 反轉每對括號間的子串 反轉每對括號間的子串 給出一個字符串s&#xff0c; 僅含有小寫英文字母和英文括號’(’ ‘)’&#xff1b;按照從括號內到外的順序&#xff0c;逐層反轉每對括號中的字符串&#xff0c;并返回最終的結果&#xff1b;結果中不能包含任何括號&am…

如何做好一份技術文檔?從規劃到實踐的完整指南

如何做好一份技術文檔&#xff1f;從規劃到實踐的完整指南 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 總有一行代碼&#xff0c;能點亮萬千星辰。 &#x1f50d; 在技術的宇宙中&#xff0c;我愿做永不停歇的探索者。 ? 用代碼丈量世界&…

css的定位(position)詳解:相對定位 絕對定位 固定定位

在 CSS 中&#xff0c;元素的定位通過 position 屬性控制&#xff0c;共有 5 種定位模式&#xff1a;static&#xff08;靜態定位&#xff09;、relative&#xff08;相對定位&#xff09;、absolute&#xff08;絕對定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…

詳細講解Flutter GetX的使用

Flutter GetX 框架詳解&#xff1a;狀態管理、路由與依賴注入 GetX 是 Flutter 生態中一款強大且輕量級的全功能框架&#xff0c;集成了狀態管理、路由管理和依賴注入三大核心功能。其設計理念是簡潔高效&#xff0c;通過最小的代碼實現最大的功能&#xff0c;特別適合快速開發…