SpringBoot實(shí)現(xiàn)網(wǎng)絡(luò)限速的詳細(xì)步驟
概述
本文介紹在 Spring Boot 3 中實(shí)現(xiàn)多維度網(wǎng)絡(luò)帶寬限速的完整方案?;?strong>令牌桶算法手動實(shí)現(xiàn)核心邏輯,通過自定義 HandlerInterceptor 攔截請求、HttpServletResponseWrapper 包裝響應(yīng)流、RateLimitedOutputStream 控制輸出速率,實(shí)現(xiàn)對文件下載、視頻流等場景的精確速度控制。

為什么需要帶寬限速
帶寬限速與常見的 API 限流不同:限流控制的是請求次數(shù)(如每分鐘100次),而限速控制的是網(wǎng)絡(luò)帶寬(如每秒200KB)。在實(shí)際應(yīng)用中,帶寬限速有著重要的業(yè)務(wù)價(jià)值:
場景一:文件下載服務(wù)對于網(wǎng)盤或資源分發(fā)平臺,免費(fèi)用戶限制在 200KB/s,VIP 用戶提升到 2MB/s,既能保障基礎(chǔ)體驗(yàn),又能激勵付費(fèi)轉(zhuǎn)化。
場景二:視頻流媒體不同清晰度對應(yīng)不同帶寬限制(480P 用 500KB/s,1080P 用 3MB/s),避免高碼率視頻占用過多服務(wù)器帶寬。
場景三:API 接口保護(hù)大數(shù)據(jù)量接口(如導(dǎo)出報(bào)表)如果沒有帶寬控制,單個(gè)請求可能占滿整個(gè)出口帶寬,影響其他用戶訪問。
核心原理:令牌桶算法
令牌桶算法是流量控制的經(jīng)典方案,其思想非常直觀:想象一個(gè)桶,系統(tǒng)以固定速率向桶中放入令牌,請求數(shù)據(jù)時(shí)必須從桶中取走對應(yīng)數(shù)量的令牌。
核心參數(shù)解析:
1. 桶容量(Capacity):決定能承受多大突發(fā)流量。容量為 200KB 時(shí),即使桶已滿,最多也只能連續(xù)發(fā)送 200KB 數(shù)據(jù),之后必須等待令牌補(bǔ)充。
2. 填充速率(Refill Rate):決定長期平均傳輸速度。每秒補(bǔ)充 200KB 令牌,意味著平均速度就是 200KB/s。
3. 分塊大?。–hunk Size):影響流量平滑度。將 8KB 數(shù)據(jù)拆分成 2KB×4 次寫入,每次寫入之間進(jìn)行令牌檢查,比一次性寫入 8KB 更加平滑。
算法流程:
發(fā)送數(shù)據(jù)前: 1. 計(jì)算距離上次補(bǔ)充的時(shí)間差 2. 根據(jù) 時(shí)間差 × 填充速率 計(jì)算新增令牌數(shù) 3. 更新桶中令牌數(shù)(不超過容量上限) 發(fā)送數(shù)據(jù)時(shí): 1. 檢查令牌是否足夠 2. 足夠:直接扣除令牌,發(fā)送數(shù)據(jù) 3. 不足:計(jì)算 (缺少令牌數(shù) / 填充速率) 得到等待時(shí)間,精確等待后發(fā)送
技術(shù)設(shè)計(jì)
整體流程
本方案采用攔截器模式,在請求處理的早期階段完成限速組件的初始化,通過請求屬性傳遞包裝后的響應(yīng)對象。
請求流程:
┌─────────────────────────────────────────────────────────────────────┐
│ 1. DispatcherServlet 分發(fā)請求 │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 2. BandwidthLimitInterceptor.preHandle() │
│ - 解析 @BandwidthLimit 注解 │
│ - 從 BandwidthLimitManager 獲取共享 TokenBucket │
│ - 創(chuàng)建 BandwidthLimitResponseWrapper 并存入 request attribute │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 3. Controller 處理請求 │
│ - 通過 BandwidthLimitHelper.getLimitedResponse() 獲取包裝后的響應(yīng) │
│ - 向響應(yīng)流寫入數(shù)據(jù)(自動觸發(fā)限速) │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 4. BandwidthLimitInterceptor.afterCompletion() │
│ - 清理資源,關(guān)閉流 │
└─────────────────────────────────────────────────────────────────────┘為什么選擇 HandlerInterceptor
在 Spring Boot 中實(shí)現(xiàn)請求處理,有兩種常見方式:Filter 和 HandlerInterceptor。本方案選擇 HandlerInterceptor 的關(guān)鍵原因是:注解解析需要 HandlerMethod 對象。
Filter 在 DispatcherServlet 之前執(zhí)行,此時(shí)還沒有確定具體的處理方法,無法獲取方法上的 @BandwidthLimit 注解。而 HandlerInterceptor 在處理器確定后執(zhí)行,可以通過 HandlerMethod 精確獲取方法級別和類級別的注解信息。
核心組件職責(zé)
組件 | 職責(zé) |
|---|---|
@BandwidthLimit | 聲明式注解,配置限速參數(shù) |
BandwidthLimitInterceptor | 攔截請求,解析注解,創(chuàng)建響應(yīng)包裝器 |
BandwidthLimitManager | 管理多維度限速桶(全局/API/用戶/IP) |
BandwidthLimitResponseWrapper | 包裝 HttpServletResponse,替換 OutputStream |
RateLimitedOutputStream | 實(shí)現(xiàn)限速邏輯,包裝 TokenBucket |
TokenBucket | 令牌桶算法實(shí)現(xiàn) |
BandwidthLimitHelper | 從請求屬性中獲取包裝后的響應(yīng)對象 |
多維度限速實(shí)現(xiàn)
本方案支持四種限速維度,滿足不同業(yè)務(wù)場景需求:
全局限速(GLOBAL)
所有請求共享同一個(gè)限速桶,適合保護(hù)服務(wù)器整體出口帶寬。例如設(shè)置 10MB/s 全局限制,即使有100個(gè)并發(fā)下載,總帶寬也不會超過 10MB/s。
@BandwidthLimit(value = 200, unit = BandwidthUnit.KB, type = LimitType.GLOBAL)
@GetMapping("/download/global")
public void downloadGlobal(HttpServletResponse response) throws IOException {
HttpServletResponse limitedResponse = BandwidthLimitHelper.getLimitedResponse(request, response);
// 寫入數(shù)據(jù)...
}API 維度限速(API)
每個(gè)接口路徑獨(dú)立限速,不同接口的流量互不影響。/api/file/download 限制 500KB/s,/api/video/stream 限制 2MB/s,兩個(gè)接口可以同時(shí)達(dá)到各自的速度上限。
@BandwidthLimit(value = 500, unit = BandwidthUnit.KB, type = LimitType.API)
@GetMapping("/download/file")
public void downloadFile(HttpServletResponse response) throws IOException {
// 文件下載邏輯
}
@BandwidthLimit(value = 2048, unit = BandwidthUnit.KB, type = LimitType.API)
@GetMapping("/stream/video")
public void streamVideo(HttpServletResponse response) throws IOException {
// 視頻流邏輯
}用戶維度限速(USER)
根據(jù)用戶標(biāo)識(如請求頭 X-User-Id)進(jìn)行限速,每個(gè)用戶獨(dú)立計(jì)算帶寬。配合 free 和 vip 參數(shù),可實(shí)現(xiàn)差異化服務(wù):
@BandwidthLimit(value = 200, unit = BandwidthUnit.KB, type = LimitType.USER,
free = 200, vip = 2048)
@GetMapping("/download/user")
public void downloadByUser(@RequestHeader("X-User-Type") String userType,
HttpServletResponse response) throws IOException {
// 根據(jù)請求頭 X-User-Type 自動應(yīng)用 200KB/s 或 2MB/s 限速
}IP 維度限速(IP)
根據(jù)客戶端 IP 地址限速,防止單個(gè) IP 占用過多帶寬。支持代理環(huán)境下的 IP 獲取(X-Forwarded-For、X-Real-IP)。
@BandwidthLimit(value = 300, unit = BandwidthUnit.KB, type = LimitType.IP)
@GetMapping("/download/ip")
public void downloadByIp(HttpServletResponse response) throws IOException {
// 每個(gè)獨(dú)立 IP 限制 300KB/s
}關(guān)鍵代碼實(shí)現(xiàn)
1. 令牌桶核心算法
TokenBucket 的核心在于精確的時(shí)間計(jì)算和令牌補(bǔ)充。使用 System.nanoTime() 獲取納秒級時(shí)間戳,確保高精度速率控制。
public synchronized void acquire(long permits) {
// 1. 補(bǔ)充令牌
refill();
// 2. 計(jì)算等待時(shí)間
if (tokens >= permits) {
tokens -= permits;
return;
}
long deficit = permits - tokens;
long waitNanos = (deficit * 1_000_000_000L) / refillRate;
// 3. 精確等待
sleepNanos(waitNanos);
// 4. 等待后消費(fèi)
tokens = 0;
}
private void refill() {
long now = System.nanoTime();
long elapsedNanos = now - lastRefillTime;
long newTokens = (elapsedNanos * refillRate) / 1_000_000_000L;
tokens = Math.min(capacity, tokens + newTokens);
lastRefillTime = now;
}2. 響應(yīng)包裝器
HttpServletResponseWrapper 是 Servlet 規(guī)范提供的響應(yīng)包裝基類,通過覆蓋 getOutputStream() 方法返回自定義的限速輸出流。
public class BandwidthLimitResponseWrapper extends HttpServletResponseWrapper {
private final TokenBucket sharedTokenBucket; // 共享的令牌桶
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (limitedOutputStream == null && sharedTokenBucket != null) {
// 使用共享 TokenBucket,確保多維度統(tǒng)計(jì)正確
limitedOutputStream = new RateLimitedOutputStream(
super.getOutputStream(),
sharedTokenBucket,
bandwidthBytesPerSecond
);
}
return limitedOutputStream;
}
}3. 攔截器獲取包裝響應(yīng)
攔截器在 preHandle 中創(chuàng)建響應(yīng)包裝器,存儲到 request attribute,Controller 通過 BandwidthLimitHelper 獲取。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
BandwidthLimit annotation = findAnnotation(handler);
if (annotation != null) {
// 從 Manager 獲取共享 TokenBucket
TokenBucket bucket = limitManager.getBucket(type, key, capacity, rate);
// 創(chuàng)建包裝器并存儲
BandwidthLimitResponseWrapper wrappedResponse =
new BandwidthLimitResponseWrapper(response, bucket, bandwidthBytesPerSecond, chunkSize);
request.setAttribute("BandwidthLimitWrappedResponse", wrappedResponse);
}
return true;
}4. Controller 獲取限速響應(yīng)
Controller 通過 BandwidthLimitHelper.getLimitedResponse() 獲取包裝后的響應(yīng),所有寫入操作都會自動限速。
@GetMapping("/download/global")
public void downloadGlobal(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpServletResponse limitedResponse = BandwidthLimitHelper.getLimitedResponse(request, response);
limitedResponse.setContentType("application/octet-stream");
limitedResponse.setHeader("Content-Disposition", "attachment; filename=test.bin");
// 寫入數(shù)據(jù)時(shí)自動限速
limitedResponse.getOutputStream().write(data);
}參數(shù)調(diào)優(yōu)指南
桶容量選擇
容量決定突發(fā)流量承受能力:
容量設(shè)置 | 突發(fā)能力 | 適用場景 |
|---|---|---|
速率 × 0.5 | 平滑,無突發(fā) | 流量控制嚴(yán)格的場景 |
速率 × 1.0 | 允許 1 秒突發(fā) | 默認(rèn)推薦值 |
速率 × 2.0 | 允許 2 秒突發(fā) | 需要良好首屏加載 |
// 注解配置 @BandwidthLimit(value = 200, unit = BandwidthUnit.KB, capacityMultiplier = 1.0)
分塊大小選擇
分塊大小影響流量平滑度,經(jīng)驗(yàn)公式:chunkSize = bandwidth / 50
帶寬 | 推薦分塊 | 理由 |
|---|---|---|
200 KB/s | 1-4 KB | 小分塊保證平滑 |
1 MB/s | 4-8 KB | 平衡平滑與性能 |
5 MB/s+ | 8-16 KB | 減少系統(tǒng)調(diào)用開銷 |
// 自動計(jì)算(推薦) @BandwidthLimit(value = 200, unit = BandwidthUnit.KB, chunkSize = -1) // 手動指定 @BandwidthLimit(value = 200, unit = BandwidthUnit.KB, chunkSize = 4096)
總結(jié)
本文基于令牌桶算法,通過 HandlerInterceptor + HttpServletResponseWrapper,在 Spring Boot 中實(shí)現(xiàn)了多維度帶寬限速。
支持全局/API/用戶/IP 四種限速維度,提供實(shí)時(shí)統(tǒng)計(jì)監(jiān)控,適用于API接口保護(hù)、文件下載、視頻流等場景。
以上就是SpringBoot實(shí)現(xiàn)網(wǎng)絡(luò)限速的詳細(xì)步驟的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot網(wǎng)絡(luò)限速的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis使用collection映射一對多查詢分頁問題詳解
本文通過修改SQL查詢語句,解決了在分頁條件下關(guān)聯(lián)查詢多表時(shí),多的一端數(shù)據(jù)不正確的問題,從而實(shí)現(xiàn)了正常的展示2025-12-12
Java按照List內(nèi)存儲的對象的某個(gè)字段進(jìn)行排序的實(shí)例
下面小編就為大家?guī)硪黄狫ava按照List內(nèi)存儲的對象的某個(gè)字段進(jìn)行排序的實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12
Spring實(shí)戰(zhàn)之Bean銷毀之前的行為操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之Bean銷毀之前的行為操作,結(jié)合實(shí)例形式分析了spring在bean銷毀之前的行為相關(guān)設(shè)置與使用技巧,需要的朋友可以參考下2019-11-11
rabbitmq使用springboot實(shí)現(xiàn)direct模式(最新推薦)
這篇文章主要介紹了rabbitmq使用springboot實(shí)現(xiàn)direct模式,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
SpringBoot 快速實(shí)現(xiàn) api 加密的方法
在項(xiàng)目中,為了保證數(shù)據(jù)的安全,我們常常會對傳遞的數(shù)據(jù)進(jìn)行加密,常用的加密算法包括對稱加密(AES)和非對稱加密(RSA),本文給大家介紹SpringBoot 快速實(shí)現(xiàn) api 加密,感興趣的朋友一起看看吧2023-10-10

