Java限流方法常見實(shí)現(xiàn)方案(單機(jī)限流和分布式限流)
在 Java 項(xiàng)目中限制短時(shí)間內(nèi)的頻繁訪問(即接口限流),是保護(hù)系統(tǒng)資源、防止惡意攻擊或高頻請(qǐng)求導(dǎo)致過載的重要手段。常見實(shí)現(xiàn)方案可分為單機(jī)限流和分布式限流,以下是具體實(shí)現(xiàn)方式:
一、核心限流算法
無論哪種方案,底層通常基于以下算法:
- 固定窗口計(jì)數(shù)器:將時(shí)間劃分為固定窗口(如 1 秒),統(tǒng)計(jì)窗口內(nèi)請(qǐng)求數(shù),超過閾值則拒絕。
- 優(yōu)點(diǎn):簡單易實(shí)現(xiàn);缺點(diǎn):窗口交界處可能出現(xiàn) “突增流量”(如窗口邊緣允許雙倍閾值請(qǐng)求)。
- 滑動(dòng)窗口:將固定窗口拆分為多個(gè)小窗口,實(shí)時(shí)滑動(dòng)計(jì)算請(qǐng)求數(shù),解決臨界問題。
- 令牌桶:勻速生成令牌放入桶中,請(qǐng)求需獲取令牌才能處理,支持突發(fā)流量(桶內(nèi)令牌可累積)。
- 漏桶:請(qǐng)求先進(jìn)入桶中,系統(tǒng)以固定速率處理,平滑流量波動(dòng)(不支持突發(fā)流量)。
二、單機(jī)限流實(shí)現(xiàn)(適用于單實(shí)例服務(wù))
1. 基于 Guava 的 RateLimiter(令牌桶算法)
Guava 提供了現(xiàn)成的RateLimiter工具類,適合快速實(shí)現(xiàn)單機(jī)限流。
依賴引入:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>代碼示例(結(jié)合 Spring 攔截器):
// 1. 定義限流攔截器
public class RateLimitInterceptor implements HandlerInterceptor {
// 每秒允許10個(gè)請(qǐng)求(令牌桶算法)
private final RateLimiter rateLimiter = RateLimiter.create(10.0);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 嘗試獲取令牌,無令牌則拒絕
if (!rateLimiter.tryAcquire()) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("請(qǐng)求過于頻繁,請(qǐng)稍后再試");
return false;
}
return true;
}
}
// 2. 注冊(cè)攔截器(Spring配置)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 對(duì)指定路徑生效(如所有接口)
registry.addInterceptor(new RateLimitInterceptor())
.addPathPatterns("/**");
}
}2. 基于滑動(dòng)窗口的自定義實(shí)現(xiàn)
適合需要更精細(xì)控制的場景(如按 IP 限流):
public class SlidingWindowLimiter {
// 窗口大?。ê撩耄?
private final long windowSize;
// 窗口內(nèi)最大請(qǐng)求數(shù)
private final int maxRequests;
// 記錄每個(gè)時(shí)間片的請(qǐng)求數(shù)(key:時(shí)間片起始時(shí)間,value:請(qǐng)求數(shù))
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();
// 計(jì)算當(dāng)前窗口的起始時(shí)間
long windowStart = now - windowSize;
// 移除過期的時(shí)間片
timeSliceCounts.keySet().removeIf(timestamp -> timestamp < windowStart);
// 統(tǒng)計(jì)當(dāng)前窗口總請(qǐng)求數(shù)
int totalRequests = timeSliceCounts.values().stream().mapToInt(Integer::intValue).sum();
if (totalRequests < maxRequests) {
// 記錄當(dāng)前時(shí)間片的請(qǐng)求(精確到100ms,可調(diào)整精度)
long currentSlice = now - (now % 100);
timeSliceCounts.put(currentSlice, timeSliceCounts.getOrDefault(currentSlice, 0) + 1);
return true;
}
return false;
}
}
// 使用示例(在Controller中)
@RestController
public class TestController {
// 10秒內(nèi)最多允許5次請(qǐng)求(按IP限流)
private final Map<String, SlidingWindowLimiter> ipLimiters = new ConcurrentHashMap<>();
@GetMapping("/test")
public String test(HttpServletRequest request) {
String ip = request.getRemoteAddr();
// 為每個(gè)IP創(chuàng)建獨(dú)立的限流器
SlidingWindowLimiter limiter = ipLimiters.computeIfAbsent(ip,
k -> new SlidingWindowLimiter(10000, 5));
if (!limiter.tryAcquire()) {
return "IP:" + ip + " 請(qǐng)求過于頻繁";
}
return "請(qǐng)求成功";
}
}三、分布式限流(適用于多實(shí)例集群)
單機(jī)限流無法跨服務(wù)實(shí)例共享狀態(tài),分布式場景需借助中間件(如 Redis)實(shí)現(xiàn)全局計(jì)數(shù)。
基于 Redis + Lua 腳本(滑動(dòng)窗口算法)
利用 Redis 的原子性和 Lua 腳本保證限流邏輯的一致性:
Lua 腳本(限流邏輯):
-- 限流key(如:接口名:IP)
local key = KEYS[1]
-- 窗口大?。ê撩耄?
local windowSize = tonumber(ARGV[1])
-- 最大請(qǐng)求數(shù)
local maxRequests = tonumber(ARGV[2])
-- 當(dāng)前時(shí)間
local now = tonumber(ARGV[3])
-- 窗口起始時(shí)間
local windowStart = now - windowSize
-- 移除窗口外的請(qǐng)求記錄
redis.call('ZREMRANGEBYSCORE', key, 0, windowStart)
-- 統(tǒng)計(jì)當(dāng)前窗口內(nèi)的請(qǐng)求數(shù)
local currentCount = redis.call('ZCARD', key)
if currentCount < maxRequests then
-- 記錄當(dāng)前請(qǐng)求時(shí)間戳
redis.call('ZADD', key, now, now .. ':' .. math.random())
-- 設(shè)置key過期時(shí)間(避免內(nèi)存泄漏)
redis.call('EXPIRE', key, windowSize / 1000 + 1)
return 1 -- 允許請(qǐng)求
end
return 0 -- 拒絕請(qǐng)求Java 代碼調(diào)用:
@Component
public class RedisRateLimiter {
@Autowired
private 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);
}
/**
* 嘗試獲取請(qǐng)求權(quán)限
* @param key 限流標(biāo)識(shí)(如:"api:test:192.168.1.1")
* @param windowSize 窗口大小(毫秒)
* @param maxRequests 最大請(qǐng)求數(shù)
* @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 {
@Autowired
private RedisRateLimiter redisRateLimiter;
@GetMapping("/test")
public String test(HttpServletRequest request) {
String ip = request.getRemoteAddr();
String key = "api:test:" + ip;
// 10秒內(nèi)最多5次請(qǐng)求
boolean allowed = redisRateLimiter.tryAcquire(key, 10000, 5);
if (!allowed) {
return "請(qǐng)求過于頻繁,請(qǐng)稍后再試";
}
return "請(qǐng)求成功";
}
}四、成熟框架推薦
生產(chǎn)環(huán)境中,推薦使用現(xiàn)成的限流框架簡化開發(fā):
- Sentinel:阿里開源的流量控制框架,支持限流、熔斷、降級(jí),可通過注解或配置中心動(dòng)態(tài)調(diào)整規(guī)則。
- Resilience4j:輕量級(jí)熔斷限流框架,支持令牌桶、滑動(dòng)窗口等多種算法,適合 Spring Boot 項(xiàng)目。
- Spring Cloud Gateway:網(wǎng)關(guān)層限流(如基于 Redis 的
RequestRateLimiter過濾器),適合在入口層統(tǒng)一限流。
總結(jié)
- 單機(jī)服務(wù):優(yōu)先使用 Guava 的
RateLimiter或自定義滑動(dòng)窗口(簡單場景)。 - 分布式服務(wù):必須基于 Redis 等中間件實(shí)現(xiàn)全局限流,配合 Lua 腳本保證原子性。
- 復(fù)雜場景:直接集成 Sentinel 等成熟框架,減少重復(fù)開發(fā)并支持動(dòng)態(tài)配置。
根據(jù)業(yè)務(wù)需求(如限流粒度:IP / 用戶 / 接口、是否允許突發(fā)流量)選擇合適的方案即可。
到此這篇關(guān)于JAVA限流方法的文章就介紹到這了,更多相關(guān)java限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis攔截器實(shí)現(xiàn)通用權(quán)限字段添加的方法
這篇文章主要給大家介紹了關(guān)于mybatis攔截器實(shí)現(xiàn)通用權(quán)限字段添加的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用mybatis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
SpringBoot2整合JTA組件實(shí)現(xiàn)多數(shù)據(jù)源事務(wù)管理
這篇文章主要介紹了SpringBoot2整合JTA組件實(shí)現(xiàn)多數(shù)據(jù)源事務(wù)管理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
LogBack+MybatisPlus實(shí)現(xiàn)打印sql日志
本文主要介紹了LogBack+MybatisPlus實(shí)現(xiàn)打印sql日志,設(shè)置異步appender實(shí)現(xiàn)按天滾動(dòng)切割日志,保留3天歷史并自動(dòng)清理,替代P6Spy避免日志過大,同時(shí)需配置MybatisPlus日志輸出路徑,并區(qū)分MyBatis與MyBatisPlus日志輸出2025-09-09

