1. 固定窗口計數器(Fixed Window Counter)
- 原理:在固定時間窗口(如1分鐘)內統計請求數,超過閾值則拒絕后續請求。
- 優點:實現簡單,內存占用低。
- 缺點:存在窗口切換時的流量突增問題(如相鄰窗口邊界處可能允許雙倍流量)。
/*** @description: 固定窗口限流* @Author: whopxx* @CreateTime: 2025-03-16*/
public class FixedWindowLimiter {private final int limit; // 窗口內最大請求數private final long windowMs; // 窗口時間(毫秒)private final AtomicInteger counter = new AtomicInteger(0);private volatile long windowStart = System.currentTimeMillis();public FixedWindowLimiter(int limit, long windowMs) {this.limit = limit;this.windowMs = windowMs;}public boolean tryAcquire() {long now = System.currentTimeMillis();if (now - windowStart > windowMs) {synchronized (this) {if (now - windowStart > windowMs) {windowStart = now;counter.set(0);}}}return counter.incrementAndGet() <= limit;}public static void main(String[] args) {FixedWindowLimiter fixedWindowLimiter = new FixedWindowLimiter(5, 1000);for (int i = 0; i < 100; i++) {boolean b = fixedWindowLimiter.tryAcquire();System.out.println(b);try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
2. 滑動窗口計數器(Sliding Window Counter)
- 原理:將時間分割為更細粒度的子窗口(如每分鐘分為60個1秒窗口),統計最近完整時間窗口內的請求總數。
- 優點:緩解固定窗口的臨界問題,限流更平滑。
- 缺點:實現較復雜,需存儲子窗口的請求記錄。
/*** @description: 滑動窗口限流* @Author: whopxx* @CreateTime: 2025-03-16*/
public class SlidingWindowLimiter {private final long windowMs; // 窗口總時間(毫秒)private final int subWindowCount; // 子窗口數量private final long subWindowMs; // 子窗口時間(毫秒)private final int limit; // 窗口內最大請求數private final AtomicInteger[] counters;private final long[] windowStartTimes; // 每個子窗口的起始時間private final ReentrantLock lock = new ReentrantLock();public SlidingWindowLimiter(int limit, long windowMs, int subWindowCount) {this.limit = limit;this.windowMs = windowMs;this.subWindowCount = subWindowCount;this.subWindowMs = windowMs / subWindowCount;this.counters = new AtomicInteger[subWindowCount];this.windowStartTimes = new long[subWindowCount];for (int i = 0; i < subWindowCount; i++) {counters[i] = new AtomicInteger(0);windowStartTimes[i] = System.currentTimeMillis() - i * subWindowMs;}}public boolean tryAcquire() {long now = System.currentTimeMillis();lock.lock();try {// 1. 清理所有過期的子窗口int expiredCount = 0;for (int i = 0; i < subWindowCount; i++) {if (now - windowStartTimes[i] > windowMs) {expiredCount += counters[i].getAndSet(0);windowStartTimes[i] = now - (now % subWindowMs); // 對齊時間窗口}}// 2. 計算當前子窗口索引int currentSubIdx = (int) ((now % windowMs) / subWindowMs);// 3. 統計當前窗口內總請求數int total = expiredCount;for (int i = 0; i < subWindowCount; i++) {total += counters[i].get();}if (total >= limit) {return false;}// 4. 寫入當前子窗口counters[currentSubIdx].incrementAndGet();return true;} finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {// 測試:限制1秒內最多2次請求,窗口分為5個子窗口(每個200ms)SlidingWindowLimiter limiter = new SlidingWindowLimiter(2, 1000, 5);for (int i = 0; i < 10; i++) {System.out.println("Request " + (i + 1) + ": " + (limiter.tryAcquire() ? "OK" : "Limited"));Thread.sleep(150); // 模擬請求間隔150ms}}
}
3. 漏桶算法(Leaky Bucket)
- 原理:請求像水一樣進入桶中,桶以固定速率“漏水”(處理請求),桶滿則拒絕新請求。
- 優點:強制恒定速率處理,適合平滑突發流量。
- 缺點:無法靈活應對突發流量(即使系統空閑時也無法瞬時處理大量請求)。
/*** @description: 漏桶限流器* @Author: whopxx* @CreateTime: 2025-03-16*/
public class LeakyBucketLimiter {private final long capacity; // 桶容量private final long rate; // 流出速率,每秒rate個private AtomicLong water = new AtomicLong(0); // 當前水量private long lastLeakTime = System.currentTimeMillis();public LeakyBucketLimiter(long capacity, long rateMsPerReq) {this.capacity = capacity;this.rate = rateMsPerReq;}public synchronized boolean tryAcquire() {if (water.get() == 0){lastLeakTime = System.currentTimeMillis();water.set(1);return true;}water.set(water.get() - (System.currentTimeMillis() - lastLeakTime) / 1000 * rate);water.set(water.get() < 0 ? 0 : water.get());lastLeakTime += (System.currentTimeMillis() - lastLeakTime) / 1000 * 1000;if (water.get() >= capacity) {return false;} else {water.set(water.get() + 1);return true;}}public static void main(String[] args) {LeakyBucketLimiter limiter = new LeakyBucketLimiter(5, 1); // 容量為5,流出速率為1個/秒for (int i = 0; i < 100; i++) {boolean b = limiter.tryAcquire();System.out.println(b);try {Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
4. 令牌桶算法(Token Bucket)
- 原理:系統以固定速率生成令牌存入桶,請求需獲取令牌才能處理,無令牌時觸發限流。
- 優點:允許突發流量(桶內積累的令牌可一次性使用),更靈活。
- 缺點:需維護令牌生成邏輯,實現較復雜。
- 典型應用:Guava的
RateLimiter
、Nginx限流模塊。
/*** @description: 令牌桶限流算法* @Author: whopxx* @CreateTime: 2025-03-16*/
public class TokenBucketLimiter {//桶的容量private final long capacity;//放入令牌的速率 每秒放入的個數private final long rate;//上次放置令牌的時間private static long lastTime = System.currentTimeMillis();//桶中令牌的余量private static AtomicLong tokenNum = new AtomicLong();public TokenBucketLimiter(int capacity, int permitsPerSecond) {this.capacity = capacity;this.rate = permitsPerSecond;tokenNum.set(capacity);}public synchronized boolean tryAcquire() {//更新桶中剩余令牌的數量long now = System.currentTimeMillis();tokenNum.addAndGet((now - lastTime) / 1000 * rate);tokenNum.set(Math.min(capacity, tokenNum.get()));//更新時間lastTime += (now - lastTime) / 1000 * 1000;//桶中還有令牌就放行if (tokenNum.get() > 0) {tokenNum.decrementAndGet();return true;} else {return false;}}//測試public static void main(String[] args) {TokenBucketLimiter limiter = new TokenBucketLimiter(3, 2); // 允許每秒放入2個令牌,桶容量為5for (int i = 0; i < 100; i++) {if (limiter.tryAcquire()) {System.out.println("成功請求");} else {System.out.println("請求失敗");}try {Thread.sleep(300); // 模擬請求間隔} catch (InterruptedException e) {e.printStackTrace();}}}
}
對比總結
方式 | 適用場景 |
固定窗口 | 簡單低頻場景 |
滑動窗口 | 需平滑限流的場景 |
漏桶算法 | 恒定速率處理(如流量整形) |
令牌桶算法 | 允許突發的場景(如秒殺) |