Spring Boot 接口限流全攻略:注解 + 令牌桶 + 用戶/IP/設(shè)備維度限流一網(wǎng)打盡!
限流,從“救火”到“預(yù)防性策略”
在高并發(fā)環(huán)境下,服務(wù)不怕請求多,怕的是毫無防線地“裸奔”。限流機(jī)制正是你系統(tǒng)的第一道防火墻,它不是阻礙增長的絆腳石,而是保障可持續(xù)運(yùn)行的關(guān)鍵策略。
本文將從注解 + AOP 切入,擴(kuò)展出支持用戶、IP、設(shè)備 ID 維度的客體級(jí)限流系統(tǒng),核心基于 Guava 提供的 RateLimiter
實(shí)現(xiàn) 令牌桶算法,以一種高性能、非侵入、易配置的方式守護(hù)你的接口安全。
路徑結(jié)構(gòu)與包名規(guī)劃
/src/main
└── /java
└── /com
└── /icoderoad
├── /annotation // 限流注解定義
├── /aspect // AOP 限流切面
├── /controller // 控制器示例
└── /utils // 工具類(獲取用戶/IP/設(shè)備信息)
限流場景概覽與算法回顧
限流的目的是防止系統(tǒng)資源被過度消耗,從而保障整體服務(wù)穩(wěn)定。
常見限流維度
維度類型 | 說明 |
接口級(jí)別限流 | 限制某接口總訪問頻率 |
用戶級(jí)別限流 | 同一用戶請求頻率控制 |
IP 限流 | 同一 IP 單位時(shí)間內(nèi)請求數(shù)限制 |
設(shè)備 ID 限流 | 依據(jù)設(shè)備號(hào)或終端號(hào)限流,常用于移動(dòng)端 |
支持的限流算法
- 固定窗口算法
- 滑動(dòng)窗口算法
- 漏桶算法
- 令牌桶算法(本實(shí)現(xiàn)采用)
限流實(shí)現(xiàn)方案詳解(支持用戶/IP/設(shè)備)
注解定義
路徑:
/*/src/main/java/com/icoderoad/annotation/RateLimit.java*/
package com.icoderoad.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int count() default 100; // 每 time 秒允許請求次數(shù)
int time() default 1; // 時(shí)間窗口長度(秒)
boolean perUser() default false; // 是否按用戶限流
boolean perIp() default false; // 是否按IP限流
boolean perDevice() default false; // 是否按設(shè)備ID限流
}
工具類:統(tǒng)一抽取用戶/IP/設(shè)備信息
路徑:
/*/src/main/java/com/icoderoad/utils/RequestUtils.java*/
package com.icoderoad.utils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.*;
public class RequestUtils {
public static HttpServletRequest getRequest() {
RequestAttributes attr = RequestContextHolder.getRequestAttributes();
return attr == null ? null : ((ServletRequestAttributes) attr).getRequest();
}
public static String getUserId() {
// 模擬獲取,實(shí)際項(xiàng)目應(yīng)取登錄上下文、JWT 等
return "user-123";
}
public static String getIpAddr() {
HttpServletRequest request = getRequest();
if (request == null) return "unknown";
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
public static String getDeviceId() {
HttpServletRequest request = getRequest();
if (request == null) return "device-unknown";
// 假設(shè)設(shè)備ID在請求頭中,如 `Device-Id`
String deviceId = request.getHeader("Device-Id");
return (deviceId == null || deviceId.isEmpty()) ? "device-unknown" : deviceId;
}
}
AOP 切面實(shí)現(xiàn)(動(dòng)態(tài)生成維度Key)
路徑:
/*/src/main/java/com/icoderoad/aspect/RateLimitAspect.java*/
package com.icoderoad.aspect;
import com.google.common.util.concurrent.RateLimiter;
import com.icoderoad.annotation.RateLimit;
import com.icoderoad.utils.RequestUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Aspect
@Component
public class RateLimitAspect {
private final Map<String, RateLimiter> limiterPool = new ConcurrentHashMap<>();
@Before("@annotation(rateLimit)")
public void applyRateLimit(JoinPoint joinPoint, RateLimit rateLimit) {
String baseKey = joinPoint.getSignature().toLongString();
StringBuilder keyBuilder = new StringBuilder(baseKey);
if (rateLimit.perUser()) {
keyBuilder.append(":USER:").append(RequestUtils.getUserId());
}
if (rateLimit.perIp()) {
keyBuilder.append(":IP:").append(RequestUtils.getIpAddr());
}
if (rateLimit.perDevice()) {
keyBuilder.append(":DEVICE:").append(RequestUtils.getDeviceId());
}
String key = keyBuilder.toString();
RateLimiter rateLimiter = limiterPool.computeIfAbsent(
key,
k -> RateLimiter.create(rateLimit.count() / (double) rateLimit.time())
);
if (!rateLimiter.tryAcquire()) {
throw new RuntimeException("訪問過于頻繁,請稍后再試");
}
}
}
控制器示例
路徑:
/*/src/main/java/com/icoderoad/controller/TestController.java*/
package com.icoderoad.controller;
import com.icoderoad.annotation.RateLimit;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/test")
public class TestController {
// 接口全局限流,每秒最多10次
@RateLimit(count = 10, time = 1)
@GetMapping("/global")
public String globalLimit() {
return "接口級(jí)別限流成功";
}
// 用戶維度限流,每用戶每秒最多5次
@RateLimit(count = 5, time = 1, perUser = true)
@GetMapping("/user")
public String userLimit() {
return "用戶維度限流成功";
}
// IP 限流:每個(gè)IP每秒最多3次
@RateLimit(count = 3, time = 1, perIp = true)
@GetMapping("/ip")
public String ipLimit() {
return "IP維度限流成功";
}
// 設(shè)備ID限流:每設(shè)備每秒最多2次
@RateLimit(count = 2, time = 1, perDevice = true)
@GetMapping("/device")
public String deviceLimit() {
return "設(shè)備維度限流成功";
}
// 綜合限流:用戶 + IP + 設(shè)備 多維組合限流
@RateLimit(count = 1, time = 1, perUser = true, perIp = true, perDevice = true)
@GetMapping("/all")
public String combinedLimit() {
return "多維度限流成功";
}
}
技術(shù)方案總結(jié)
特性 | 描述 |
非侵入式設(shè)計(jì) | 通過注解和 AOP 實(shí)現(xiàn),與業(yè)務(wù)代碼解耦 |
多維度支持 | 支持接口級(jí)、用戶級(jí)、IP、設(shè)備ID限流 |
動(dòng)態(tài)速率配置 | 注解級(jí)別自由配置 count/time 參數(shù) |
高性能內(nèi)存實(shí)現(xiàn) | 基于 Guava 的 RateLimiter,納秒級(jí)響應(yīng) |
容易擴(kuò)展 | 可擴(kuò)展 Redis 分布式限流、API分組限流、黑名單機(jī)制等 |
結(jié)語:限流機(jī)制的本質(zhì)是“可控增長”
在系統(tǒng)架構(gòu)中,限流從來都不是“封鎖通道”,而是提供有序釋放系統(tǒng)能力的護(hù)欄。
當(dāng)你掌握了如本文所述的用戶/IP/設(shè)備維度限流機(jī)制之后,你將具備:
- 對(duì)接口訪問的精細(xì)化調(diào)控能力
- 面對(duì)突發(fā)高峰的穩(wěn)定服務(wù)能力
- 對(duì)系統(tǒng)濫用行為的有效應(yīng)對(duì)手段