java單機滑動窗口限流器的實現(xiàn)
下面使用 ConcurrentHashMap + ConcurrentLinkedDeque 實現(xiàn)一個滑動窗口限流器,限制在 10 秒內(nèi)最多 N 次請求(例如 5 次)。這種方案只依賴本地內(nèi)存,適合單機限流場景。
實現(xiàn)原理
為每個用戶(或 IP)維護一個 雙端隊列,存儲每次請求的時間戳(毫秒)。
每次請求時,先清理隊列中 超過 10 秒 的舊時間戳,再判斷隊列大小是否小于閾值:
- 若小于,則添加當前時間戳,返回 允許;
- 否則,返回 限流。
使用
ConcurrentHashMap保證對用戶 Key 的線程安全,使用synchronized對每個隊列做同步,避免并發(fā)修改。
代碼實現(xiàn)
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
/**
* 基于 Map 的滑動窗口限流器(10秒限頻)
*/
public class LocalSlidingWindowRateLimiter {
// 存儲每個用戶的請求時間戳隊列
private final Map<String, ConcurrentLinkedDeque<Long>> userQueueMap = new ConcurrentHashMap<>();
private final int windowSeconds; // 窗口大?。耄?
private final int maxRequests; // 窗口內(nèi)最大請求數(shù)
public LocalSlidingWindowRateLimiter(int windowSeconds, int maxRequests) {
this.windowSeconds = windowSeconds;
this.maxRequests = maxRequests;
}
/**
* 檢查是否允許請求
* @param key 限流標識(如 userId、ip)
* @return true 允許,false 被限流
*/
public boolean allowRequest(String key) {
long now = System.currentTimeMillis();
long windowStart = now - TimeUnit.SECONDS.toMillis(windowSeconds);
// 獲取或創(chuàng)建該用戶的隊列
ConcurrentLinkedDeque<Long> deque = userQueueMap.computeIfAbsent(key,
k -> new ConcurrentLinkedDeque<>());
// 對隊列加鎖,保證清理和添加的原子性
synchronized (deque) {
// 1. 清理過期記錄(窗口外的舊時間戳)
while (!deque.isEmpty() && deque.peekFirst() < windowStart) {
deque.pollFirst();
}
// 2. 判斷是否超過閾值
if (deque.size() < maxRequests) {
// 允許請求,記錄當前時間戳
deque.addLast(now);
return true;
} else {
return false;
}
}
}
// 可選:定期清理無數(shù)據(jù)的用戶 Key,避免內(nèi)存泄漏(可以用 ScheduledExecutorService 定時清理)
public void cleanEmptyKeys() {
userQueueMap.entrySet().removeIf(entry -> entry.getValue().isEmpty());
}
}使用示例
public class Main {
public static void main(String[] args) throws InterruptedException {
// 限流規(guī)則:10秒內(nèi)最多5次請求
LocalSlidingWindowRateLimiter limiter = new LocalSlidingWindowRateLimiter(10, 5);
String userId = "user_123";
for (int i = 0; i < 10; i++) {
boolean allowed = limiter.allowRequest(userId);
System.out.println("請求 " + (i + 1) + ":" + (allowed ? "通過" : "限流"));
Thread.sleep(1000); // 模擬間隔 1 秒
}
}
}輸出示例(第 6 次請求被限流,因為 10 秒內(nèi)已有 5 次):
請求 1:通過
請求 2:通過
請求 3:通過
請求 4:通過
請求 5:通過
請求 6:限流
請求 7:限流
...
注意事項
- 內(nèi)存清理:長期不活動的用戶 Key 會殘留隊列對象,建議定時調(diào)用
cleanEmptyKeys()清理。 - 并發(fā)安全:每個隊列使用
synchronized鎖保證原子操作,鎖粒度小,性能較高。 - 時間精度:使用毫秒級時間戳,滑動窗口邊界判斷精確。
- 適用場景:單機應用、對分布式一致性要求不高的限流場景。
到此這篇關(guān)于java單機滑動窗口限流器的實現(xiàn)的文章就介紹到這了,更多相關(guān)java 滑動窗口限流器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Spring的@Autowired依賴注入常見錯誤的總結(jié)
有時我們會使用@Autowired自動注入,同時也存在注入到集合、數(shù)組等復雜類型的場景。這都是方便寫 bug 的場景,本篇文章帶你了解Spring @Autowired依賴注入的坑2021-09-09
Java Swing CardLayout卡片布局的實現(xiàn)示例
這篇文章主要介紹了Java Swing CardLayout卡片布局的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-12-12
spring boot+redis 監(jiān)聽過期Key的操作方法
這篇文章主要介紹了spring boot+redis 監(jiān)聽過期Key,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
Java數(shù)據(jù)結(jié)構(gòu)之數(shù)組(動力節(jié)點之Java學院整理)
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之數(shù)組(動力節(jié)點之Java學院整理)的相關(guān)資料,包括創(chuàng)建和內(nèi)存分配,數(shù)組封裝后的使用等,需要的朋友參考下吧2017-04-04
mybatis-plus使用@Delete注解批量刪除實現(xiàn)方式
文章介紹了如何使用@Delete注解進行批量刪除操作,包括控制器調(diào)用、接口實現(xiàn)和效果圖展示,作者分享了個人經(jīng)驗,并鼓勵大家參考和點贊支持2026-02-02

