SpringBoot通過(guò)攔截器實(shí)現(xiàn)接口限流的兩種方案
在Spring Boot中,可以通過(guò)自定義攔截器(Interceptor)結(jié)合Redis或內(nèi)存計(jì)數(shù)器實(shí)現(xiàn)接口限流。以下是兩種典型實(shí)現(xiàn)方式及代碼示例:
?方案一:基于Redis + Lua腳本的分布式限流?
?核心邏輯?
- ?Redis配置?:使用Lua腳本保證原子性操作(計(jì)數(shù)+過(guò)期時(shí)間設(shè)置)。
- ?攔截器?:攔截請(qǐng)求,通過(guò)Redis統(tǒng)計(jì)IP或用戶維度的訪問(wèn)次數(shù)。
- ?注冊(cè)攔截器?:指定攔截路徑和排除路徑。
?代碼實(shí)現(xiàn)?
1. Redis配置類
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public DefaultRedisScript<Long> rateLimitScript() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local expire = tonumber(ARGV[2])\n" +
"local current = redis.call('INCR', key)\n" +
"if current == 1 then\n" +
" redis.call('EXPIRE', key, expire)\n" +
"end\n" +
"return current > limit and 1 or 0"
);
script.setResultType(Long.class);
return script;
}
}
2. 限流攔截器
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private DefaultRedisScript<Long> rateLimitScript;
private static final int DEFAULT_LIMIT = 60; // 每分鐘60次
private static final int DEFAULT_TIMEOUT = 60; // 60秒過(guò)期
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String ip = request.getRemoteAddr();
String uri = request.getRequestURI();
String key = "rate_limit:" + ip + ":" + uri.split("/")[1]; // 按接口前綴分組
Long result = redisTemplate.execute(
rateLimitScript,
Collections.singletonList(key),
DEFAULT_LIMIT, DEFAULT_TIMEOUT
);
if (result != null && result == 1) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests");
return false;
}
return true;
}
}
3. 注冊(cè)攔截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RateLimitInterceptor rateLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login");
}
}
?方案二:基于內(nèi)存計(jì)數(shù)器的單機(jī)限流?
?核心邏輯?
- ?攔截器?:使用
ConcurrentHashMap存儲(chǔ)IP和訪問(wèn)時(shí)間戳。 - ?滑動(dòng)窗口?:統(tǒng)計(jì)1分鐘內(nèi)的請(qǐng)求數(shù),超限則拒絕。
?代碼實(shí)現(xiàn)?
public class RateLimitingInterceptor implements HandlerInterceptor {
private final ConcurrentMap<String, Long> requestCounts = new ConcurrentHashMap<>();
private static final long ALLOWED_REQUESTS_PER_MINUTE = 60;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String clientIp = request.getRemoteAddr();
long currentTime = System.currentTimeMillis();
// 清理過(guò)期請(qǐng)求
requestCounts.entrySet().removeIf(entry ->
currentTime - entry.getValue() > TimeUnit.MINUTES.toMillis(1)
);
// 統(tǒng)計(jì)當(dāng)前窗口請(qǐng)求數(shù)
long count = requestCounts.values().stream()
.filter(timestamp -> currentTime - timestamp < TimeUnit.MINUTES.toMillis(1))
.count();
if (count >= ALLOWED_REQUESTS_PER_MINUTE) {
response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
response.getWriter().write("請(qǐng)求過(guò)于頻繁");
return false;
}
requestCounts.put(clientIp, currentTime);
return true;
}
}
?關(guān)鍵對(duì)比與選擇建議?
| ?方案? | ?適用場(chǎng)景? | ?優(yōu)點(diǎn)? | ?缺點(diǎn)? |
|---|---|---|---|
| Redis + Lua | 分布式環(huán)境,高精度限流 | 原子性操作,支持分布式,可動(dòng)態(tài)調(diào)整參數(shù) | 依賴Redis,網(wǎng)絡(luò)開(kāi)銷較大 |
| 內(nèi)存計(jì)數(shù)器 | 單機(jī)環(huán)境,簡(jiǎn)單場(chǎng)景 | 無(wú)外部依賴,實(shí)現(xiàn)簡(jiǎn)單 | 不支持分布式,重啟后數(shù)據(jù)丟失 |
?擴(kuò)展建議?:
- ?動(dòng)態(tài)配置?:將限流參數(shù)(如
DEFAULT_LIMIT)改為從配置中心讀取。 - ?注解化?:結(jié)合自定義注解(如
@RateLimit)實(shí)現(xiàn)更靈活的限流規(guī)則。
兩種方案均能有效實(shí)現(xiàn)接口限流,根據(jù)項(xiàng)目需求選擇即可。
到此這篇關(guān)于SpringBoot通過(guò)攔截器實(shí)現(xiàn)接口限流的兩種方案的文章就介紹到這了,更多相關(guān)SpringBoot攔截器接口限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springcloud中Feign傳遞參數(shù)的過(guò)程解析
這篇文章主要介紹了Springcloud中Feign傳遞參數(shù)的過(guò)程,單個(gè)參數(shù)的傳值有兩種方式,第一種使用@RequestParam/@PathVariable進(jìn)行傳值,傳遞多個(gè)參數(shù):多個(gè)參數(shù)的傳值可以使用多個(gè)@RequestParam來(lái)進(jìn)行傳參,需要的朋友可以參考下2023-09-09
SpringSecurity實(shí)現(xiàn)前后端分離登錄token認(rèn)證詳解
目前市面上比較流行的權(quán)限框架主要實(shí)Shiro和Spring Security,這兩個(gè)框架各自側(cè)重點(diǎn)不同,各有各的優(yōu)劣,本文將給大家詳細(xì)介紹SpringSecurity如何實(shí)現(xiàn)前后端分離登錄token認(rèn)證2023-06-06
struts中動(dòng)態(tài)方法調(diào)用使用通配符
這篇文章主要介紹了struts中動(dòng)態(tài)方法調(diào)用使用通配符的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-09-09
解決SpringBoot多模塊發(fā)布時(shí)99%的問(wèn)題
本文歸納了以下 8 個(gè)原則和發(fā)布時(shí)經(jīng)常出現(xiàn)的 4 個(gè)問(wèn)題的解決方案,掌握了這些原則和解決方案,幾乎可以解決絕大數(shù)SpringBoot發(fā)布問(wèn)題2019-07-07
Java利用Spire.Doc實(shí)現(xiàn)XML轉(zhuǎn)PDF的實(shí)戰(zhàn)指南
本文介紹了使用Spire.DocforJava將XML轉(zhuǎn)換為PDF的過(guò)程,首先說(shuō)明了XML和PDF的應(yīng)用場(chǎng)景,然后通過(guò)Maven引入依賴,核心代碼展示了加載XML并轉(zhuǎn)換為PDF的方法,最后討論了進(jìn)階設(shè)置、注意事項(xiàng)及常見(jiàn)問(wèn)題,幫助開(kāi)發(fā)者更好地完成轉(zhuǎn)換任務(wù),需要的朋友可以參考下2026-04-04
SpringBoot+隨機(jī)鹽值+雙重MD5實(shí)現(xiàn)加密登錄
數(shù)據(jù)加密在很多項(xiàng)目上都可以用到,大部分都會(huì)采用MD5進(jìn)行加密,本文主要介紹了SpringBoot+隨機(jī)鹽值+雙重MD5實(shí)現(xiàn)加密登錄,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02
SpringBoot-JPA刪除不成功,只執(zhí)行了查詢語(yǔ)句問(wèn)題
這篇文章主要介紹了SpringBoot-JPA刪除不成功,只執(zhí)行了查詢語(yǔ)句問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
java監(jiān)聽(tīng)器實(shí)現(xiàn)在線人數(shù)統(tǒng)計(jì)
這篇文章主要為大家詳細(xì)介紹了java監(jiān)聽(tīng)器實(shí)現(xiàn)在線人數(shù)統(tǒng)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
Java實(shí)現(xiàn)HTML轉(zhuǎn)Word的完整指南
在Java應(yīng)用程序中處理文檔轉(zhuǎn)換時(shí),經(jīng)常需要將HTML內(nèi)容精準(zhǔn)導(dǎo)出為格式規(guī)范的Word文檔,通過(guò)Spire.Doc for Java庫(kù),開(kāi)發(fā)者可以輕松實(shí)現(xiàn)HTML到Word的高保真轉(zhuǎn)換,所以本文給大家介紹了Java實(shí)現(xiàn)HTML轉(zhuǎn)Word的完整指南,需要的朋友可以參考下2025-09-09

