SpringBoot實(shí)現(xiàn)接口限流的常用方案
漏桶和令牌桶說(shuō)明
漏桶和令牌桶是最常用的兩種限流算法,核心差異如下:
| ?特性? | ?漏桶限流(Leaky Bucket)?? | ?令牌桶限流(Token Bucket)?? |
|---|---|---|
| ?核心邏輯? | 固定速率處理請(qǐng)求(“漏水”),桶緩沖突發(fā)請(qǐng)求 | 固定速率生成令牌,請(qǐng)求需獲取令牌才能處理 |
| ?輸出速率? | 嚴(yán)格固定(等于漏水速率) | 允許突發(fā)(最大為桶容量 + 新生成的令牌數(shù)) |
| ?突發(fā)容忍度? | 不允許突發(fā)(超過(guò)桶容量的請(qǐng)求直接拒絕) | 允許突發(fā)(桶中令牌足夠時(shí),可一次性處理多個(gè)請(qǐng)求) |
| ?資源利用率? | 可能較低(即使系統(tǒng)空閑,也需按固定速率處理) | 較高(系統(tǒng)空閑時(shí)可積累令牌,突發(fā)時(shí)快速處理) |
| ?典型場(chǎng)景? | 嚴(yán)格要求速率穩(wěn)定(如網(wǎng)絡(luò)流量整形) | 允許一定突發(fā)(如 API 接口限流) |
項(xiàng)目基礎(chǔ)配置
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.48.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.8-jre</version>
</dependency>
</dependencies>
application.yml
spring:
data:
redis:
host: redis
port: 6379
啟動(dòng)類
@SpringBootApplication
public class RateLimitApplication {
public static void main(String[] args) {
SpringApplication.run(RateLimitApplication.class, args);
}
}
基于Redisson的令牌桶
在分布式系統(tǒng)中,使用 Redisson 實(shí)現(xiàn)并發(fā)限流是一種常見(jiàn)且高效的方式
RateType限流鍵(key)的設(shè)計(jì)
OVERALL模式?:限流鍵需全局唯一(如global_api_limit),確保所有請(qǐng)求共享同一個(gè)桶。PER_CLIENT模式?:限流鍵需能唯一標(biāo)識(shí)客戶端(如user:${userId}、ip:${clientIp}),確保每個(gè)客戶端獨(dú)立使用自己的桶。
@Slf4j
@RestController
@RequestMapping(value = "redisson/rate-limit")
public class RedissonRateLimitController {
@Autowired
private RedissonClient redissonClient;
@PostMapping("create-order")
public Object createOrder(String info){
RRateLimiter rateLimiter = getLimiter();
boolean b = rateLimiter.tryAcquire();
if(b) {
log.info("允許訪問(wèn)");
return "SUCCESS";
}
log.warn("限流了");
throw new RuntimeException("請(qǐng)求太火爆");
}
/**
* 獲取限流器
* @return
*/
private RRateLimiter getLimiter() {
RRateLimiter rateLimiter = redissonClient.getRateLimiter("create-order");
rateLimiter.trySetRate(RateType.PER_CLIENT, 20, Duration.ofSeconds(5L));
return rateLimiter;
}
}
測(cè)試結(jié)果
我單線程請(qǐng)求了8S,請(qǐng)求成功40個(gè)

Guava的實(shí)現(xiàn)
Guava 的 RateLimiter是單機(jī)環(huán)境下基于令牌桶算法的輕量級(jí)限流工具,適用于控制接口請(qǐng)求頻率、資源訪問(wèn)速率或任務(wù)執(zhí)行頻率等場(chǎng)景。以下是不同場(chǎng)景下的使用案例,包含基礎(chǔ)用法、高級(jí)特性及實(shí)際業(yè)務(wù)集成。
接口說(shuō)明
RateLimiter.create(10.0):創(chuàng)建一個(gè)每秒生成 10 個(gè)令牌的限流器(即允許每秒 10 個(gè)請(qǐng)求)。RateLimiter rateLimiter = RateLimiter.create(20.0, 5, TimeUnit.SECONDS): //創(chuàng)建預(yù)熱限流器:目標(biāo)速率 20 個(gè)/秒,預(yù)熱時(shí)間 5 秒(從 2 個(gè)/秒逐步提升到 20 個(gè)/秒)rateLimiter.acquire(1):阻塞等待獲取 1 個(gè)令牌(若當(dāng)前無(wú)令牌,會(huì)等待直到新令牌生成)。返回值為等待時(shí)間(秒)rateLimiter.tryAcquire(long permits, long timeout, TimeUnit unit):非阻塞獲取
具體代碼
@Slf4j
@RestController
@RequestMapping(value = "guava/rate-limit")
public class GuavaRateLimitController {
private static final RateLimiter rateLimiter = RateLimiter.create(5);
@PostMapping("create-order")
public Object createOrder(String info){
boolean b = rateLimiter.tryAcquire();
if(b) {
log.info("允許訪問(wèn)");
return "SUCCESS";
}
log.warn("限流了");
throw new RuntimeException("請(qǐng)求太火爆");
}
}
結(jié)果
8s內(nèi)成功訪問(wèn)45個(gè)請(qǐng)求

基于nginx的限流
Nginx 限流核心模塊:ngx_http_limit_req_module
Nginx 內(nèi)置的 ngx_http_limit_req_module基于漏桶算法實(shí)現(xiàn)限流,通過(guò)以下兩個(gè)核心指令控制:
limit_req_zone:定義限流的共享內(nèi)存區(qū)和基礎(chǔ)速率。limit_req:在具體location或server塊中應(yīng)用限流規(guī)則。
(1)定義限流共享內(nèi)存區(qū)(limit_req_zone)
在 Nginx 主配置文件(nginx.conf)的 http塊中,定義共享內(nèi)存區(qū)存儲(chǔ)限流狀態(tài):
http {
# 定義限流共享內(nèi)存區(qū)(名稱=one,大小=10MB,速率=10請(qǐng)求/秒)
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
# 其他全局配置...
include mime.types;
default_type application/octet-stream;
}
$binary_remote_addr:限流的鍵(基于客戶端 IP 地址)。zone=one:10m:共享內(nèi)存區(qū)名稱為one,大小為 10MB(根據(jù)客戶端數(shù)量調(diào)整,10MB 約支持 16 萬(wàn) IP)。rate=10r/s:基礎(chǔ)速率(每秒允許 10 個(gè)請(qǐng)求)。
(2)在 server或 location中應(yīng)用限流規(guī)則
在需要限流的站點(diǎn)配置(如 server塊)中,使用 limit_req指令應(yīng)用規(guī)則,并設(shè)置被限流時(shí)的響應(yīng):
server {
listen 80;
server_name example.com;
location /api/ {
# 應(yīng)用限流規(guī)則(使用上面定義的 zone=one)
limit_req zone=one burst=20 nodelay; # burst=突發(fā)容量,nodelay=不等待直接拒絕
# 被限流時(shí)返回 429 狀態(tài)碼,并跳轉(zhuǎn)到自定義錯(cuò)誤頁(yè)
limit_req_status_code 429;
error_page 429 /custom_limit_msg;
# 正常請(qǐng)求的反向代理(示例)
proxy_pass http://backend_server;
}
# 自定義限流消息體(返回 JSON)
location = /custom_limit_msg {
internal; # 僅內(nèi)部訪問(wèn),禁止外部直接請(qǐng)求
add_header Content-Type application/json;
return 429 '{"code": 429, "message": "請(qǐng)求過(guò)于頻繁,請(qǐng)稍后再試"}';
}
}
關(guān)鍵參數(shù)說(shuō)明
| 參數(shù)/指令 | 含義 |
|---|---|
| limit_req_zone | 定義限流的共享內(nèi)存區(qū)、鍵(如 IP)和基礎(chǔ)速率(rate=10r/s表示每秒 10 個(gè)請(qǐng)求)。 |
| burst | 突發(fā)容量(允許短時(shí)間內(nèi)超過(guò)基礎(chǔ)速率的請(qǐng)求數(shù)),例如 burst=20表示最多允許 20 個(gè)突發(fā)請(qǐng)求。 |
| nodelay | 若啟用,被限流的請(qǐng)求直接拒絕?(不等待);若禁用(默認(rèn)),請(qǐng)求會(huì)等待直到令牌可用。 |
| limit_req_status_code | 設(shè)置被限流時(shí)的 HTTP 狀態(tài)碼(如 429 Too Many Requests)。 |
| error_page | 自定義限流時(shí)的響應(yīng)內(nèi)容(如返回 JSON 消息體)。 |
高級(jí)限流場(chǎng)景
1. 按用戶 ID 限流
若需按用戶 ID 限流(如防止惡意用戶高頻請(qǐng)求),可將限流鍵改為用戶 ID(需前端傳遞 X-User-ID頭):
http {
# 限流鍵為 X-User-ID 頭的值(需確保頭存在)
limit_req_zone $http_x_user_id zone=user_limit:10m rate=5r/s;
server {
location /api/user/ {
# 校驗(yàn) X-User-ID 頭是否存在,不存在則拒絕
if ($http_x_user_id = "") {
return 400 "Missing X-User-ID header";
}
# 應(yīng)用限流(鍵為用戶 ID)
limit_req zone=user_limit burst=10 nodelay;
limit_req_status_code 429;
error_page 429 /custom_limit_msg;
proxy_pass http://backend_server;
}
}
}
以上就是SpringBoot實(shí)現(xiàn)接口限流的常用方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot接口限流的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
學(xué)會(huì)Java字節(jié)碼指令,成為技術(shù)大佬
Java 字節(jié)碼指令是 JVM 體系中非常難啃的一塊硬骨頭,我估計(jì)有些讀者會(huì)有這樣的疑惑,“Java 字節(jié)碼難學(xué)嗎?我能不能學(xué)會(huì)?。俊北疚膸ьI(lǐng)大家一探究竟,幫助大家搞懂java底層代碼如何執(zhí)行2021-08-08
詳解基于MybatisPlus兩步實(shí)現(xiàn)多租戶方案
這篇文章主要介紹了詳解基于MybatisPlus兩步實(shí)現(xiàn)多租戶方案,本文分兩步,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Spring Data JPA踩坑記錄(@id @GeneratedValue)
這篇文章主要介紹了Spring Data JPA踩坑記錄(@id @GeneratedValue),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07

