在 Java 項目中限制短時間內的頻繁訪問(即接口限流),是保護系統資源、防止惡意攻擊或高頻請求導致過載的重要手段。常見實現方案可分為單機限流和分布式限流,以下是具體實現方式:
一、核心限流算法
無論哪種方案,底層通常基于以下算法:
- 固定窗口計數器:將時間劃分為固定窗口(如 1 秒),統計窗口內請求數,超過閾值則拒絕。
- 優點:簡單易實現;缺點:窗口交界處可能出現 “突增流量”(如窗口邊緣允許雙倍閾值請求)。
- 滑動窗口:將固定窗口拆分為多個小窗口,實時滑動計算請求數,解決臨界問題。
- 令牌桶:勻速生成令牌放入桶中,請求需獲取令牌才能處理,支持突發流量(桶內令牌可累積)。
- 漏桶:請求先進入桶中,系統以固定速率處理,平滑流量波動(不支持突發流量)。
二、單機限流實現(適用于單實例服務)
1. 基于 Guava 的 RateLimiter(令牌桶算法)
Guava 提供了現成的RateLimiter
工具類,適合快速實現單機限流。
依賴引入:
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version>
</dependency>
代碼示例(結合 Spring 攔截器):
// 1. 定義限流攔截器
public class RateLimitInterceptor implements HandlerInterceptor {// 每秒允許10個請求(令牌桶算法)private final RateLimiter rateLimiter = RateLimiter.create(10.0);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 嘗試獲取令牌,無令牌則拒絕if (!rateLimiter.tryAcquire()) {response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());response.getWriter().write("請求過于頻繁,請稍后再試");return false;}return true;}
}// 2. 注冊攔截器(Spring配置)
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 對指定路徑生效(如所有接口)registry.addInterceptor(new RateLimitInterceptor()).addPathPatterns("/**");}
}
2. 基于滑動窗口的自定義實現
適合需要更精細控制的場景(如按 IP 限流):
public class SlidingWindowLimiter {// 窗口大小(毫秒)private final long windowSize;// 窗口內最大請求數private final int maxRequests;// 記錄每個時間片的請求數(key:時間片起始時間,value:請求數)private final ConcurrentHashMap<Long, Integer> timeSliceCounts = new ConcurrentHashMap<>();public SlidingWindowLimiter(long windowSize, int maxRequests) {this.windowSize = windowSize;this.maxRequests = maxRequests;}public synchronized boolean tryAcquire() {long now = System.currentTimeMillis();// 計算當前窗口的起始時間long windowStart = now - windowSize;// 移除過期的時間片timeSliceCounts.keySet().removeIf(timestamp -> timestamp < windowStart);// 統計當前窗口總請求數int totalRequests = timeSliceCounts.values().stream().mapToInt(Integer::intValue).sum();if (totalRequests < maxRequests) {// 記錄當前時間片的請求(精確到100ms,可調整精度)long currentSlice = now - (now % 100);timeSliceCounts.put(currentSlice, timeSliceCounts.getOrDefault(currentSlice, 0) + 1);return true;}return false;}
}// 使用示例(在Controller中)
@RestController
public class TestController {// 10秒內最多允許5次請求(按IP限流)private final Map<String, SlidingWindowLimiter> ipLimiters = new ConcurrentHashMap<>();@GetMapping("/test")public String test(HttpServletRequest request) {String ip = request.getRemoteAddr();// 為每個IP創建獨立的限流器SlidingWindowLimiter limiter = ipLimiters.computeIfAbsent(ip, k -> new SlidingWindowLimiter(10000, 5));if (!limiter.tryAcquire()) {return "IP:" + ip + " 請求過于頻繁";}return "請求成功";}
}
三、分布式限流(適用于多實例集群)
單機限流無法跨服務實例共享狀態,分布式場景需借助中間件(如 Redis)實現全局計數。
基于 Redis + Lua 腳本(滑動窗口算法)
利用 Redis 的原子性和 Lua 腳本保證限流邏輯的一致性:
Lua 腳本(限流邏輯):
-- 限流key(如:接口名:IP)
local key = KEYS[1]
-- 窗口大小(毫秒)
local windowSize = tonumber(ARGV[1])
-- 最大請求數
local maxRequests = tonumber(ARGV[2])
-- 當前時間
local now = tonumber(ARGV[3])-- 窗口起始時間
local windowStart = now - windowSize-- 移除窗口外的請求記錄
redis.call('ZREMRANGEBYSCORE', key, 0, windowStart)
-- 統計當前窗口內的請求數
local currentCount = redis.call('ZCARD', key)if currentCount < maxRequests then-- 記錄當前請求時間戳redis.call('ZADD', key, now, now .. ':' .. math.random())-- 設置key過期時間(避免內存泄漏)redis.call('EXPIRE', key, windowSize / 1000 + 1)return 1 -- 允許請求
endreturn 0 -- 拒絕請求
Java 代碼調用:
@Component
public class RedisRateLimiter {@Autowiredprivate StringRedisTemplate redisTemplate;// 加載Lua腳本private final DefaultRedisScript<Long> limitScript;public RedisRateLimiter() {limitScript = new DefaultRedisScript<>();limitScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limit.lua")));limitScript.setResultType(Long.class);}/*** 嘗試獲取請求權限* @param key 限流標識(如:"api:test:192.168.1.1")* @param windowSize 窗口大小(毫秒)* @param maxRequests 最大請求數* @return 是否允許*/public boolean tryAcquire(String key, long windowSize, int maxRequests) {Long result = redisTemplate.execute(limitScript,Collections.singletonList(key),String.valueOf(windowSize),String.valueOf(maxRequests),String.valueOf(System.currentTimeMillis()));return result != null && result == 1;}
}// 在Controller中使用
@RestController
public class TestController {@Autowiredprivate RedisRateLimiter redisRateLimiter;@GetMapping("/test")public String test(HttpServletRequest request) {String ip = request.getRemoteAddr();String key = "api:test:" + ip;// 10秒內最多5次請求boolean allowed = redisRateLimiter.tryAcquire(key, 10000, 5);if (!allowed) {return "請求過于頻繁,請稍后再試";}return "請求成功";}
}
四、成熟框架推薦
生產環境中,推薦使用現成的限流框架簡化開發:
- Sentinel:阿里開源的流量控制框架,支持限流、熔斷、降級,可通過注解或配置中心動態調整規則。
- Resilience4j:輕量級熔斷限流框架,支持令牌桶、滑動窗口等多種算法,適合 Spring Boot 項目。
- Spring Cloud Gateway:網關層限流(如基于 Redis 的
RequestRateLimiter
過濾器),適合在入口層統一限流。
總結
- 單機服務:優先使用 Guava 的
RateLimiter
或自定義滑動窗口(簡單場景)。 - 分布式服務:必須基于 Redis 等中間件實現全局限流,配合 Lua 腳本保證原子性。
- 復雜場景:直接集成 Sentinel 等成熟框架,減少重復開發并支持動態配置。
根據業務需求(如限流粒度:IP / 用戶 / 接口、是否允許突發流量)選擇合適的方案即可。