SpringBoot 3.3 中實(shí)現(xiàn) API 接口限流就是這么簡單
在互聯(lián)網(wǎng)飛速發(fā)展的今天,隨著系統(tǒng)用戶規(guī)模的不斷擴(kuò)大和分布式架構(gòu)的廣泛應(yīng)用,API 接口的穩(wěn)定性和性能成為系統(tǒng)設(shè)計(jì)中至關(guān)重要的因素。無論是應(yīng)對突發(fā)的流量高峰,還是防止惡意爬蟲的惡意請求,限流策略都已成為現(xiàn)代系統(tǒng)不可或缺的一部分。
為什么需要接口限流?
- 防止系統(tǒng)過載: 在短時(shí)間內(nèi)大量的請求可能導(dǎo)致系統(tǒng)資源耗盡,進(jìn)而導(dǎo)致服務(wù)降級甚至宕機(jī)。通過限流,我們可以有效控制流量的上限,確保系統(tǒng)在高負(fù)載下仍然能夠提供穩(wěn)定的服務(wù)。
- 保護(hù)關(guān)鍵資源: 一些關(guān)鍵的 API 接口可能涉及到數(shù)據(jù)庫、緩存等有限資源的操作,如果不加限制,可能會導(dǎo)致資源耗盡,影響系統(tǒng)整體性能。限流可以確保這些關(guān)鍵資源的訪問量在可控范圍內(nèi)。
- 應(yīng)對惡意攻擊: 分布式拒絕服務(wù)攻擊(DDoS)是常見的網(wǎng)絡(luò)攻擊手段,攻擊者通過發(fā)送大量請求癱瘓系統(tǒng)。限流策略可以作為第一道防線,快速識別并過濾掉異常流量,減少攻擊對系統(tǒng)的影響。
- 提升用戶體驗(yàn): 在用戶訪問量大的情況下,如果不加以控制,可能會出現(xiàn)系統(tǒng)響應(yīng)速度下降的情況,影響用戶體驗(yàn)。合理的限流策略能夠?yàn)橛脩籼峁└臃€(wěn)定和一致的服務(wù)質(zhì)量。
- 公平資源分配: 在多用戶、多租戶的場景下,限流能夠確保系統(tǒng)資源的公平分配,避免某個(gè)用戶或租戶獨(dú)占資源,影響其他用戶的正常使用。
為了解決上述問題,我們可以在 API 接口上實(shí)施限流策略,使得系統(tǒng)能夠在高并發(fā)環(huán)境下保持穩(wěn)定,并且能夠合理應(yīng)對各類突發(fā)情況。在本文中,我們將探討如何在 SpringBoot 3.3 中,通過簡單的配置和代碼實(shí)現(xiàn) API 接口的限流。
運(yùn)行效果:
圖片
圖片
若想獲取項(xiàng)目完整代碼以及其他文章的項(xiàng)目源碼,且在代碼編寫時(shí)遇到問題需要咨詢交流,歡迎加入下方的知識星球。
項(xiàng)目結(jié)構(gòu)
我們將從項(xiàng)目的結(jié)構(gòu)開始,先了解一下本示例項(xiàng)目的文件布局。
rate-limiter/
├── src/
│ ├── main/
│ │ ├── java/com/icoderoad/ratelimiter/
│ │ │ ├── controller/
│ │ │ │ └── RateLimiterController.java
│ │ │ ├── config/
│ │ │ │ └── RateLimiterConfig.java
│ │ │ ├── properties/
│ │ │ │ └── RateLimiterProperties.java
│ │ │ └── application/
│ │ │ └── SpringBootRateLimiterApplication.java
│ │ ├── resources/
│ │ │ ├── templates/
│ │ │ │ └── index.html
│ │ │ └── application.yml
└── pom.xml
接下來,我們將逐步搭建項(xiàng)目,實(shí)現(xiàn) API 接口限流功能。
引入依賴
在 pom.xml 中引入以下依賴:
<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>ratelimiter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ratelimiter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<guava.version>31.1-jre</guava.version>
</properties>
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Guava 用于限流 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- Thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置限流參數(shù)
在 src/main/resources/application.yml 中配置限流參數(shù):
server:
port: 8080
rate-limiter:
permits-per-second: 5 # 每秒許可數(shù)
warmup-period: 0 # 預(yù)熱時(shí)間(秒)
timeout: 0 # 獲取許可的超時(shí)時(shí)間(秒)
參數(shù)說明:
- permits-per-second: 每秒允許的請求數(shù)量。
- warmup-period: 限流器預(yù)熱時(shí)間,用于平滑地增加到最大速率。
- timeout: 獲取許可的超時(shí)時(shí)間,0 表示立即返回獲取結(jié)果。
創(chuàng)建限流配置屬性類
在 src/main/java/com/icoderoad/ratelimiter/properties/RateLimiterProperties.java 中創(chuàng)建配置屬性類,用于映射 application.yml 中的配置:
package com.icoderoad.ratelimiter.propertie;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
@Data
@Component
@ConfigurationProperties(prefix = "rate-limiter")
public class RateLimiterProperties {
/**
* 每秒許可數(shù)
*/
private double permitsPerSecond;
/**
* 預(yù)熱時(shí)間(秒)
*/
private long warmupPeriod;
/**
* 獲取許可的超時(shí)時(shí)間(秒)
*/
private long timeout;
}
說明:
- 使用 @ConfigurationProperties 注解將配置屬性映射到類中,便于在代碼中使用。
- 提供對應(yīng)的 Getter 和 Setter 方法,方便 Spring Boot 自動(dòng)注入配置。
配置 RateLimiter
在 src/main/java/com/icoderoad/ratelimiter/config/RateLimiterConfig.java 中創(chuàng)建限流器配置:
package com.icoderoad.ratelimiter.config;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.util.concurrent.RateLimiter;
import com.icoderoad.ratelimiter.propertie.RateLimiterProperties;
@Configuration
public class RateLimiterConfig {
/**
* 配置 RateLimiter Bean
*
* @param properties 注入的限流配置屬性
* @return RateLimiter 實(shí)例
*/
@Bean
public RateLimiter rateLimiter(RateLimiterProperties properties) {
if (properties.getWarmupPeriod() > 0) {
// 創(chuàng)建帶有預(yù)熱期的 RateLimiter
return RateLimiter.create(
properties.getPermitsPerSecond(),
properties.getWarmupPeriod(),
TimeUnit.SECONDS
);
} else {
// 創(chuàng)建標(biāo)準(zhǔn)的 RateLimiter
return RateLimiter.create(properties.getPermitsPerSecond());
}
}
}
說明:
- 根據(jù)配置文件中的參數(shù)動(dòng)態(tài)創(chuàng)建 RateLimiter 實(shí)例。
- 支持帶有預(yù)熱期的限流器配置,滿足不同場景下的需求。
創(chuàng)建控制器
在 src/main/java/com/icoderoad/ratelimiter/controller/RateLimiterController.java 中創(chuàng)建控制器,處理 API 請求:
package com.icoderoad.ratelimiter.controller;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.google.common.util.concurrent.RateLimiter;
import com.icoderoad.ratelimiter.propertie.RateLimiterProperties;
@Controller
public class RateLimiterController {
@Autowired
private RateLimiter rateLimiter;
@Autowired
private RateLimiterProperties properties;
/**
* 測試限流接口
*
* @return 請求結(jié)果
*/
@GetMapping("/api/test")
@ResponseBody
public ResponseEntity<String> testApi() {
boolean acquired = rateLimiter.tryAcquire(properties.getTimeout(), TimeUnit.SECONDS);
if (acquired) {
// 允許請求,返回成功響應(yīng)
return ResponseEntity.ok("請求成功!");
} else {
// 拒絕請求,返回限流響應(yīng)
return ResponseEntity.status(429).body("請求過多,請稍后再試!");
}
}
}
說明:
- 使用 rateLimiter.tryAcquire(timeout, TimeUnit.SECONDS) 方法嘗試獲取許可,支持超時(shí)等待。
- 根據(jù)獲取許可的結(jié)果返回對應(yīng)的響應(yīng):
成功獲取:返回 200 狀態(tài)碼和成功消息。
獲取失敗:返回 429 狀態(tài)碼和錯(cuò)誤提示。
創(chuàng)建前端頁面
在 src/main/resources/templates/index.html 中創(chuàng)建前端頁面,使用 Thymeleaf、Bootstrap 和 jQuery 實(shí)現(xiàn):
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>API 限流測試</title>
<!-- 引入 Bootstrap CSS -->
<link rel="stylesheet" >
<!-- 自定義樣式 -->
<style>
body {
padding-top: 50px;
}
</style>
</head>
<body>
<div class="container">
<h1 class="mb-4">API 限流測試</h1>
<button id="testButton" class="btn btn-primary">發(fā)送請求</button>
<div id="alertPlaceholder" class="mt-3"></div>
</div>
<!-- 引入 jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- 引入 Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function () {
$('#testButton').click(function () {
$.ajax({
url: '/api/test',
method: 'GET',
success: function (response) {
showAlert(response, 'success');
},
error: function (xhr) {
if (xhr.status === 429) {
showAlert(xhr.responseText, 'danger');
} else {
showAlert('發(fā)生未知錯(cuò)誤,請稍后重試。', 'warning');
}
}
});
});
/**
* 顯示提示信息
* @param message 消息內(nèi)容
* @param type 提示類型('success', 'danger', 'warning' 等)
*/
function showAlert(message, type) {
const alertHtml = `
<div class="alert alert-${type} alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
`;
$('#alertPlaceholder').html(alertHtml);
}
});
</script>
</body>
</html>
說明:
- 引入資源:
使用 CDN 加載 Bootstrap 和 jQuery,確保資源的快速和穩(wěn)定加載。
- 頁面結(jié)構(gòu):
一個(gè)按鈕用于觸發(fā) API 請求。
一個(gè)占位符 div 用于顯示提示信息。
JavaScript 邏輯:
使用 jQuery 監(jiān)聽按鈕點(diǎn)擊事件,發(fā)送 AJAX 請求到 /api/test 接口。
根據(jù)響應(yīng)結(jié)果,調(diào)用 showAlert 函數(shù),在頁面上顯示不同類型的提示信息。
showAlert 函數(shù)使用 Bootstrap 的 Alert 組件,提供友好的用戶提示。
效果展示:
- 請求成功: 顯示綠色的成功提示。
- 請求被限流: 顯示紅色的錯(cuò)誤提示,提示用戶請求過多。
- 未知錯(cuò)誤: 顯示黃色的警告提示,提示發(fā)生未知錯(cuò)誤。
啟動(dòng)應(yīng)用
在 src/main/java/com/icoderoad/ratelimiter/application/SpringBootRateLimiterApplication.java 中啟動(dòng) Spring Boot 應(yīng)用:
package com.icoderoad.ratelimiter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.icoderoad.ratelimiter")
public class RatelimiterApplication {
public static void main(String[] args) {
SpringApplication.run(RatelimiterApplication.class, args);
}
}
說明:
- 使用 @SpringBootApplication 注解標(biāo)注主啟動(dòng)類,并指定掃描的基礎(chǔ)包路徑。
- 運(yùn)行 main 方法即可啟動(dòng)應(yīng)用。
8. 測試與驗(yàn)證
步驟:
- 啟動(dòng)應(yīng)用: 運(yùn)行主啟動(dòng)類,啟動(dòng) Spring Boot 應(yīng)用。
- 訪問頁面: 在瀏覽器中訪問 http://localhost:8080/,看到 API 限流測試頁面。
- 發(fā)送請求:點(diǎn)擊“發(fā)送請求”按鈕,觀察頁面提示信息。
- 正常情況: 如果請求未超過限流閾值,顯示綠色的“請求成功!”提示。
- 限流情況: 如果在短時(shí)間內(nèi)連續(xù)多次點(diǎn)擊按鈕,超過配置的每秒許可數(shù),將顯示紅色的“請求過多,請稍后再試!”提示。
- 調(diào)整配置: 可以修改 application.yml 中的限流參數(shù),重新啟動(dòng)應(yīng)用,測試不同的限流策略效果。
示例演示:
- 設(shè)置 permits-per-second 為 2,表示每秒允許 2 個(gè)請求。
- 連續(xù)快速點(diǎn)擊按鈕,多數(shù)請求將被限流,提示用戶稍后重試。
9. 總結(jié)
通過本文的示例,我們成功地在 Spring Boot 3.3 中實(shí)現(xiàn)了簡單而有效的 API 接口限流功能。我們利用了 Guava 提供的 RateLimiter 工具,結(jié)合 Spring Boot 的配置屬性管理和依賴注入機(jī)制,實(shí)現(xiàn)了靈活可配的限流策略。同時(shí),通過前端頁面的簡單設(shè)計(jì)和友好提示,使得用戶能夠清晰地感知到限流機(jī)制的存在和作用。