一、限流概述
隨著互聯網化程度的不斷提高,大量用戶同時使用某些功能,可能會導致某些功能被耗盡,從而導致系統癱瘓。為了應對這種情況,限流成為了解決問題的重要手段。限流是指根據給定的規則和策略來控制、限制應用程序流量,以達到保護後端應用的目的。Java作為一門流行的編程語言,提供了多種限流機制,可以解決大流量並發任務下伺服器資源被耗光的問題。
二、限流分類
限流根據限流目的和實現方式的不同,可以分為五種類型:
1. 固定窗口演算法
固定窗口演算法限制時間窗口內的請求數量,同一時間窗口內的請求量不能超過設定的閾值。
public class FixedWindowRateLimiter implements RateLimiter { private AtomicInteger counter = new AtomicInteger(0); private final int limit; private final int intervalMs; private final ThreadLocal last = new ThreadLocal() { @Override protected Long initialValue() { return System.currentTimeMillis(); } }; public FixedWindowRateLimiter(int limit, int intervalMs) { this.limit = limit; this.intervalMs = intervalMs; } @Override public boolean tryAcquire() { long now = System.currentTimeMillis(); long lastTime = last.get(); if (now - lastTime > intervalMs) { counter.set(0); last.set(now); } return counter.incrementAndGet() <= limit; } }
2. 滑動窗口演算法
滑動窗口演算法仍然限制時間窗口內的請求數量,不同的是時間窗口可以滑動,即滑動窗口演算法最近的一段時間窗口都被重複使用。
public class SlidingWindowRateLimiter implements RateLimiter { private AtomicInteger counter = new AtomicInteger(0); private final int limit; private final int intervalMs; private final int bucketSize; private final ConcurrentLinkedDeque windows = new ConcurrentLinkedDeque(); public SlidingWindowRateLimiter(int limit, int intervalMs, int bucketSize) { this.limit = limit; this.intervalMs = intervalMs; this.bucketSize = bucketSize; } @Override public boolean tryAcquire() { long now = System.currentTimeMillis(); if (!windows.isEmpty() && now - windows.peek() > intervalMs) { int size = windows.size(); for (int i = 0; i < size && i < bucketSize; i++) { windows.poll(); } counter.set(windows.size()); } if (counter.incrementAndGet() <= limit) { windows.offer(System.currentTimeMillis()); return true; } counter.decrementAndGet(); return false; } }
3. Leaky Bucket演算法
Leaky Bucket演算法是一種限制請求流量的演算法,它通過給定最大速率控制進入網路的數據數量,有效地防止了請求峰的出現,從而保護了系統。
public class LeakyBucketRateLimiter implements RateLimiter { private AtomicInteger counter = new AtomicInteger(0); private final int limit; private final int intervalMs; private final float leakRate; private AtomicLong leftWater = new AtomicLong(0); private AtomicLong lastUpdateTime = new AtomicLong(System.nanoTime()); public LeakyBucketRateLimiter(int limit, int intervalMs, float leakRate) { this.limit = limit; this.intervalMs = intervalMs; this.leakRate = leakRate; } @Override public boolean tryAcquire() { long now = System.nanoTime(); long timeDelta = now - lastUpdateTime.get(); long left = leftWater.addAndGet((long) (-timeDelta * leakRate)); if (left < 0) { left = 0; leftWater.set(left); } if (left < limit) { if (counter.addAndGet(1) <= limit) { return true; } else { counter.decrementAndGet(); } } lastUpdateTime.set(now); return false; } }
4. Token Bucket演算法
Token Bucket演算法是一種計量允許突發請求流量的演算法,它通過對請求進行統計並抑制突發請求流量,保護系統正常運行。
public class TokenBucketRateLimiter implements RateLimiter { private AtomicInteger tokens = new AtomicInteger(0); private final int limit; private final int intervalMs; private final int refillTimeMs; private final int refillCount; private final ThreadLocal last = new ThreadLocal() { @Override protected Long initialValue() { return System.currentTimeMillis(); } }; public TokenBucketRateLimiter(int limit, int intervalMs, int refillTimeSec, int refillCount) { this.limit = limit; this.intervalMs = intervalMs; this.refillTimeMs = refillTimeSec * 1000; this.refillCount = refillCount; } @Override public boolean tryAcquire() { long now = System.currentTimeMillis(); int currentCount = tokens.get(); if (now - last.get() > refillTimeMs) { int newTokens = (int) ((now - last.get()) / refillTimeMs * refillCount); if (newTokens > 0) { tokens.set(Math.min(currentCount + newTokens, limit)); last.set(now); } } return tokens.compareAndSet(currentCount, currentCount - 1); } }
5. 基於Google Guava的流量控制
Google Guava提供了一種基於令牌桶演算法的流控方式,只需定製化配置令牌生成速率和令牌桶大小,即可輕鬆實現流控。
private static final RateLimiter RATE_LIMITER = RateLimiter.create(1.0); public void rateLimiter() { if (RATE_LIMITER.tryAcquire()) { // Allow access } else { // Reject request } }
三、應用場景
限流機制可以用於很多場景,例如:高並發請求、海量爬蟲請求、大量資料庫查詢操作等。以下是限流機制的一些具體應用場景:
1. 秒殺系統搶購場景
秒殺場景下用戶同時發起大量請求,伺服器負擔過重,為此可以通過限流措施對在一定時間內訪問的請求進行限制,比如高並發時採用Leaky Bucket演算法進行限流,控制請求流量。
2. 海量爬蟲請求場景
爬蟲請求注意要保證請求合理、搶佔時間均勻,限流是抵抗爬蟲攻擊、保護伺服器資源的重要手段,可以採用固定窗口、滑動窗口等演算法進行限流。
3. 流媒體播放區分不同視頻質量場景
流媒體播放場景下用戶可以通過質量切換功能,切換不同的碼率、解析度、FPS等參數,可以通過Token Bucket演算法或基於Guava的令牌桶演算法實現限流。
四、總結
本文從限流基本概念入手,詳細介紹了Java下的五種常見限流演算法及其應用場景。針對不同的應用場景,可以選擇不同的限流演算法實現或者結合使用多個限流演算法進行流量控制,以達到保護系統的目的。
原創文章,作者:PDYJO,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/325064.html