一、计数器限流算法(固定窗口限流算法)
定义:计数器限流算法是一种简单且直观的限流方法,它通过维护一个计数器变量来限制在特定时间间隔内的请求数量。具体实现是在一段时间间隔内(如50S)对请求进行计数,并将计数结果与设置的最大请求数进行比较。如果请求数超过了最大值,则进行限流处理。时间间隔结束时,计数器会被清零,重新开始计数。
优点:实现简单,直观易懂,设置明确的阈值,易于理解和配置。
缺点:存在窗口切换时的突增问题,即在时间窗口的临界点附近,如果请求数突然增加,可能会导致短时间内大量请求通过限流检查,从而对系统造成压力。
存在窗口切换时的突增问题
例如在40S~60S期间来了200个请求,导致20S内大量请求通过限流检查,给一个系统突然的峰值,而这个时间范围越小,峰值越高,就会导致系统的崩溃
简单实现:
import java.util.concurrent.atomic.AtomicInteger;
public class CounterRateLimiter {
private final AtomicInteger counter = new AtomicInteger(0);
private final long maxCount;
private final long intervalMillis;
private long lastResetTime;
public CounterRateLimiter(long maxCount, long intervalMillis) {
this.maxCount = maxCount;
this.intervalMillis = intervalMillis;
this.lastResetTime = System.currentTimeMillis();
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
// 检查是否需要重置计数器
if (currentTime - lastResetTime >= intervalMillis) {
counter.set(0);
lastResetTime = currentTime;
}
// 检查计数器是否小于最大值
if (counter.get() < maxCount) {
counter.incrementAndGet();
return true;
}
return false;
}
public static void main(String[] args) throws InterruptedException {
CounterRateLimiter rateLimiter = new CounterRateLimiter(5, 1000); // 每秒最多5个请求
// 模拟请求
for (int i = 0; i < 20; i++) {
boolean allowed = rateLimiter.tryAcquire();
System.out.println("Request " + (i + 1) + ": " + (allowed ? "Allowed" : "Blocked"));
Thread.sleep(100); // 假设每个请求间隔200毫秒
}
}
}
二、滑动窗口限流算法
定义:滑动窗口限流算法本质上也是一种计数器,但它通过对时间窗口的滑动来管理请求的计数,从而实现对请求速率的限制。与固定窗口算法相比,滑动窗口算法将时间窗口分为多个小周期,每个小周期都有自己的计数器。随着时间的滑动,过期的小周期数据被删除,这样可以更精确地控制流量。
优点:能够更精确地控制流量,尤其是在处理短时间内突发的高请求量时,通过动态调整时间窗口的起始点和结束点,可以有效地平滑流量波动,避免系统因瞬间高负载而崩溃。
缺点:实现相对复杂,需要维护多个时间窗口的计数器,并且需要处理时间窗口的滑动逻辑。
在当前窗口结束的位置有100个请求,时间超过了50S后,又来100个请求,此时由于窗口向前滑动了一小格,所以此时窗口内的请求是100,如果此时有请求进来,则大于100,那么他会把请求都会拒绝,直到窗口滑出50S的时间节点
public class SlidingWindowRateLimiter {
private final int maxCount;
private final long windowMillis;
private final Queue<Long> timestamps = new LinkedList<>();
public SlidingWindowRateLimiter(int maxCount, long windowMillis) {
this.maxCount = maxCount;
this.windowMillis = windowMillis;
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
// 移除过期的时间戳
while (!timestamps.isEmpty() && (currentTime - timestamps.peek() > windowMillis)) {
timestamps.poll();
}
// 检查队列中的时间戳数量是否小于最大值
if (timestamps.size() < maxCount) {
timestamps.offer(currentTime);
return true;
}
return false;
}
public static void main(String[] args) throws InterruptedException {
SlidingWindowRateLimiter rateLimiter = new SlidingWindowRateLimiter(5, 1000); // 每秒最多5个请求,滑动窗口1秒
Thread.sleep(800); // 假设每个请求间隔200毫秒
for (int i = 0; i < 5; i++) {
boolean allowed = rateLimiter.tryAcquire();
System.out.println("Request " + (i + 1) + ": " + (allowed ? "Allowed" : "Blocked"));
}
// 模拟请求
for (int i = 0; i < 20; i++) {
boolean allowed = rateLimiter.tryAcquire();
System.out.println("Request " + (i + 1) + ": " + (allowed ? "Allowed" : "Blocked"));
Thread.sleep(200); // 假设每个请求间隔200毫秒
}
}
}
三、 漏桶限流算法
定义:漏桶限流算法通过一个带有恒定流出速度的漏桶来模拟数据流量的处理过程。无论数据流入的速度如何变化,漏桶的流出速度始终保持不变。当请求到达时,它们被放入漏桶中。如果漏桶未满,请求将被接受并排队等待处理;如果漏桶已满,则根据算法策略处理(如丢弃请求)。
优点:能够提供一个稳定的流量输出,有效避免突发流量对系统的冲击,保证所有请求都按照相同的速率被处理,从而保证公平性。
缺点:漏出速率是固定的,无法应对需要突发传输的场景。在网络未发生拥塞时,漏桶算法可能无法充分利用网络资源。
public class LeakyBucket {
private final long capacity; // 桶的容量
private final long flowRate; // 桶的漏水速率,单位时间流出的水量
private long currentWater = 0; // 当前桶中的水量
private long lastLeakTime = System.currentTimeMillis(); // 上次漏水时间
private final ReentrantLock lock = new ReentrantLock(); // 线程锁
public LeakyBucket(long capacity, long flowRate) {
this.capacity = capacity;
this.flowRate = flowRate;
}
// 尝试加水,返回是否成功
public boolean tryAcquire() {
lock.lock();
try {
leak(); // 先让桶里的水漏掉一些
if (currentWater < capacity) { // 判断加水后是否会溢出
currentWater++; // 加水
return true;
} else {
return false; // 水溢出,加水失败
}
} finally {
lock.unlock();
}
}
// 桶中的水按速率流出
private void leak() {
long currentTime = System.currentTimeMillis();
long delta = currentTime - lastLeakTime; // 计算上次漏水到现在的时间
long leakedAmount = delta * flowRate / 1000; // 计算这段时间应该流出的水量
if (leakedAmount > 0) {
currentWater -= leakedAmount;
currentWater = Math.max(0, currentWater); // 防止水量为负
lastLeakTime = currentTime; // 更新漏水时间
}
}
public static void main(String[] args) throws InterruptedException {
LeakyBucket bucket = new LeakyBucket(10, 2); // 容量为10,每秒漏水速率为2(500ms 一滴)
// 模拟请求
for (int i = 0; i < 30; i++) {
System.out.println("Request " + (i + 1) + ": " + (bucket.tryAcquire() ? "Allowed" : "Blocked"));
Thread.sleep(100); // 每100毫秒发起一次请求
}
}
}