限流算法用于控制系统的访问速率,以保护系统免受过载或恶意攻击的影响。以下是一些常见的限流算法:
-
固定窗口计数器(Fixed Window Counter):将请求按固定时间窗口进行计数,当请求超过阈值时进行限流。但存在突发性请求时容易发生流量突刺的问题。
-
滑动窗口计数器(Sliding Window Counter):与固定窗口计数器类似,但是滑动窗口计数器允许时间窗口滑动,更加平滑地处理突发性请求。
-
令牌桶算法(Token Bucket):在固定时间间隔内产生令牌,每个请求需要消耗一个令牌才能被处理,当令牌桶中的令牌不足时,拒绝请求或延迟处理。适用于平滑限制请求速率。
-
漏桶算法(Leaky Bucket):请求以固定速率进入漏桶,当漏桶溢出时,拒绝请求或延迟处理。适用于平滑突发请求。
-
计数器窗口算法(Counting Window):将请求按时间窗口进行计数,并且将计数结果存储在时间窗口中,通过控制时间窗口的大小和数量来限制请求速率。
-
令牌桶 + 漏桶组合算法:综合利用令牌桶和漏桶算法的优点,适用于灵活控制请求速率和处理延迟。
以上是一些常见的限流算法,每种算法都有其优缺点和适用场景,选择合适的限流算法取决于系统的需求和性能要求。
以下是用 Java 实现的几种限流算法的简单示例:
- 固定窗口计数器(Fixed Window Counter):
import java.util.concurrent.atomic.AtomicInteger;
public class FixedWindowCounter {
private final int limit;
private AtomicInteger counter;
public FixedWindowCounter(int limit) {
this.limit = limit;
this.counter = new AtomicInteger(0);
}
public synchronized boolean allowRequest() {
int count = counter.incrementAndGet();
if (count > limit) {
counter.decrementAndGet();
return false;
}
return true;
}
public static void main(String[] args) {
FixedWindowCounter counter = new FixedWindowCounter(10);
for (int i = 0; i < 15; i++) {
if (counter.allowRequest()) {
System.out.println("Request " + (i + 1) + ": Allowed");
} else {
System.out.println("Request " + (i + 1) + ": Rejected");
}
}
}
}
- 滑动窗口计数器(Sliding Window Counter):
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.atomic.AtomicInteger;
public class SlidingWindowCounter {
private final int windowSize;
private final int limit;
private final long windowDuration;
private Deque<Long> window;
private AtomicInteger counter;
public SlidingWindowCounter(int windowSize, int limit, long windowDuration) {
this.windowSize = windowSize;
this.limit = limit;
this.windowDuration = windowDuration;
this.window = new ArrayDeque<>();
this.counter = new AtomicInteger(0);
}
public synchronized boolean allowRequest() {
long currentTime = System.currentTimeMillis();
cleanExpiredWindows(currentTime);
if (counter.get() < limit) {
counter.incrementAndGet();
window.addLast(currentTime);
return true;
}
return false;
}
private void cleanExpiredWindows(long currentTime) {
long windowStart = currentTime - windowDuration;
while (!window.isEmpty() && window.peekFirst() < windowStart) {
window.pollFirst();
counter.decrementAndGet();
}
while (window.size() >= windowSize) {
window.pollFirst();
counter.decrementAndGet();
}
}
public static void main(String[] args) {
SlidingWindowCounter slidingWindowCounter = new SlidingWindowCounter(5, 3, 1000);
for (int i = 0; i < 10; i++) {
if (slidingWindowCounter.allowRequest()) {
System.out.println("Request " + (i + 1) + ": Allowed");
} else {
System.out.println("Request " + (i + 1) + ": Rejected");
}
try {
Thread.sleep(200); // 模拟请求间隔
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
滑动窗口计时器算法通过维护一个固定大小的窗口,窗口中记录了过去一段时间内的请求情况。当新的请求到来时,先清理过期的窗口,然后判断当前窗口中的请求数是否超过限制。
- 令牌桶算法(Token Bucket):
import java.util.concurrent.atomic.AtomicLong;
public class TokenBucket {
private final long capacity;
private final long rate;
private AtomicLong tokens;
private long lastRefillTime;
public TokenBucket(long capacity, long rate) {
this.capacity = capacity;
this.rate = rate;
this.tokens = new AtomicLong(capacity);
this.lastRefillTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest(int tokensRequested) {
refill();
long currentTokens = tokens.get();
if (currentTokens >= tokensRequested) {
tokens.addAndGet(-tokensRequested);
return true;
}
return false;
}
private void refill() {
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastRefillTime;
long newTokens = elapsedTime * rate / 1000;
if (newTokens > 0) {
tokens.set(Math.min(capacity, tokens.get() + newTokens));
lastRefillTime = currentTime;
}
}
public static void main(String[] args) {
TokenBucket tokenBucket = new TokenBucket(100, 10);
for (int i = 0; i < 15; i++) {
int tokensRequested = (int) (Math.random() * 20 + 1);
if (tokenBucket.allowRequest(tokensRequested)) {
System.out.println("Request " + (i + 1) + ": Allowed (" + tokensRequested + " tokens)");
} else {
System.out.println("Request " + (i + 1) + ": Rejected (" + tokensRequested + " tokens)");
}
}
}
}
- 漏桶算法(Leaky Bucket):
public class LeakyBucket {
private final long capacity;
private final long rate;
private long content;
private long lastLeakTime;
public LeakyBucket(long capacity, long rate) {
this.capacity = capacity;
this.rate = rate;
this.content = 0;
this.lastLeakTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
leak();
if (content < capacity) {
content++;
return true;
}
return false;
}
private void leak() {
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastLeakTime;
long leaked = elapsedTime * rate / 1000;
if (leaked > 0) {
content = Math.max(0, content - leaked);
lastLeakTime = currentTime;
}
}
public static void main(String[] args) {
LeakyBucket leakyBucket = new LeakyBucket(10, 2);
for (int i = 0; i < 15; i++) {
if (leakyBucket.allowRequest()) {
System.out.println("Request " + (i + 1) + ": Allowed");
} else {
System.out.println("Request " + (i + 1) + ": Rejected");
}
}
}
}
- 计数器窗口算法(Counting Window):
import java.util.concurrent.ConcurrentHashMap;
public class CountingWindow {
private final long windowSize;
private final int limit;
private ConcurrentHashMap<Long, Integer> window;
public CountingWindow(long windowSize, int limit) {
this.windowSize = windowSize;
this.limit = limit;
this.window = new ConcurrentHashMap<>();
}
public synchronized boolean allowRequest() {
long currentTime = System.currentTimeMillis();
long windowStart = currentTime - windowSize;
// 清理过期的窗口
for (long timestamp : window.keySet()) {
if (timestamp < windowStart) {
window.remove(timestamp);
}
}
// 判断请求是否超过限制
if (window.size() < limit) {
window.put(currentTime, window.size() + 1);
return true;
}
return false;
}
public static void main(String[] args) {
CountingWindow countingWindow = new CountingWindow(1000, 5);
for (int i = 0; i < 10; i++) {
if (countingWindow.allowRequest()) {
System.out.println("Request " + (i + 1) + ": Allowed");
} else {
System.out.println("Request " + (i + 1) + ": Rejected");
}
try {
Thread.sleep(200); // 模拟请求间隔
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5.令牌桶 + 漏桶组合算法
import java.util.concurrent.atomic.AtomicLong;
public class TokenBucketLeakyBucket {
private final long capacity;
private final long rate;
private AtomicLong tokens;
private long lastRefillTime;
private final long leakRate;
private long content;
private long lastLeakTime;
public TokenBucketLeakyBucket(long capacity, long rate, long leakRate) {
this.capacity = capacity;
this.rate = rate;
this.tokens = new AtomicLong(capacity);
this.lastRefillTime = System.currentTimeMillis();
this.leakRate = leakRate;
this.content = 0;
this.lastLeakTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
refill();
leak();
if (content < capacity && tokens.get() > 0) {
tokens.decrementAndGet();
content++;
return true;
}
return false;
}
private void refill() {
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastRefillTime;
long newTokens = elapsedTime * rate / 1000;
if (newTokens > 0) {
tokens.set(Math.min(capacity, tokens.get() + newTokens));
lastRefillTime = currentTime;
}
}
private void leak() {
long currentTime = System.currentTimeMillis();
long elapsedTime = currentTime - lastLeakTime;
long leaked = elapsedTime * leakRate / 1000;
if (leaked > 0) {
content = Math.max(0, content - leaked);
lastLeakTime = currentTime;
}
}
public static void main(String[] args) {
TokenBucketLeakyBucket tokenBucketLeakyBucket = new TokenBucketLeakyBucket(10, 5, 2);
for (int i = 0; i < 15; i++) {
if (tokenBucketLeakyBucket.allowRequest()) {
System.out.println("Request " + (i + 1) + ": Allowed");
} else {
System.out.println("Request " + (i + 1) + ": Rejected");
}
}
}
}
这个算法同时使用了令牌桶和漏桶的思想。令牌桶部分控制了请求的速率,漏桶部分控制了请求的突发量,两者结合起来可以更灵活地控制流量。