一、固定窗口算法 (Fixed Window)
实现原理: 固定窗口算法将时间划分为固定大小的窗口,并在每个窗口内计算请求的数量。每当接收到请求时,只需要检查当前时间所在的窗口内已处理的请求量是否超过了设定的阈值。窗口之间的请求不进行累计,即窗口切换时请求计数重置。
优点:
- 实现简单。
- 对突发流量反应迅速,一旦窗口刷新,即可重新接受请求。
缺点:
- 窗口切换时可能存在“突刺”现象,即上个窗口末尾的大量请求可能会导致下一个窗口一开始就达到限流阈值。
- 对流量的平滑控制能力较弱,容易造成误判。
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class FixedWindowRateLimiter {
// 存储每个窗口的请求计数
private ConcurrentHashMap<Long, AtomicInteger> windowCounts = new ConcurrentHashMap<>();
// 窗口大小
private Duration windowSize;
// 单窗口最大请求次数
private int maxRequestsPerWindow;
public FixedWindowRateLimiter(Duration windowSize, int maxRequestsPerWindow) {
this.windowSize = windowSize;
this.maxRequestsPerWindow = maxRequestsPerWindow;
}
public boolean allowRequest() {
Instant now = Instant.now();
// 计算当前窗口的起始时间点
long windowStart = now.minusMillis(windowSize.toMillis()).truncatedTo(windowSize.getUnit()).toEpochMilli();
// 获取或创建窗口计数器
AtomicInteger countInWindow = windowCounts.computeIfAbsent(windowStart, k -> new AtomicInteger());
// 增加请求计数
int currentCount = countInWindow.incrementAndGet();
// 检查是否超过阈值
if (currentCount > maxRequestsPerWindow) {
// 如果超过阈值,可以选择回滚计数器,这里为了简单直接返回false
countInWindow.decrementAndGet();
return false;
}
// 清理早于当前窗口的其他窗口计数
windowCounts.entrySet().removeIf(e -> e.getKey() < windowStart);
return true;
}
}
二、滑动窗口算法 (Sliding Window)
实现原理: 滑动窗口算法同样将时间划分为窗口,但窗口不是互斥的,而是连续的。新窗口的开始总是紧接着上一个窗口的结束,因此请求会在连续的窗口内累积,从而提供更加平滑的限流效果。
优点:
- 对流量控制更准确,避免了固定窗口带来的突刺问题。
- 更好地反映了近期的请求频率。
缺点:
- 实现相对复杂,需要维护窗口内的所有请求记录。
- 可能需要更多的内存来存储窗口内的数据。
import java.time.Instant;
import java.util.LinkedList;
import java.util.Queue;
public class SlidingWindowRateLimiter {
// 存储每个请求的时间戳
private Queue<Instant> requestsQueue = new LinkedList<>();
// 窗口大小(单位:毫秒)
private long windowSizeInMillis;
// 最大请求次数
private int maxRequestsPerWindow;
public SlidingWindowRateLimiter(long windowSize, int maxRequestsPerWindow) {
this.windowSizeInMillis = windowSize;
this.maxRequestsPerWindow = maxRequestsPerWindow;
}
public synchronized boolean allowRequest() {
Instant now = Instant.now();
// 移除窗口之外的旧请求
while (!requestsQueue.isEmpty() && now.minusMillis(windowSizeInMillis).isAfter(requestsQueue.peek())) {
requestsQueue.poll();
}
// 添加新请求到队列
requestsQueue.add(now);
// 检查窗口内的请求数量是否超过阈值
return requestsQueue.size() <= maxRequestsPerWindow;
}
}
三、漏桶算法 (Leaky Bucket)
实现原理: 漏桶算法通过一个队列(桶)接收流入的请求,并以恒定速率处理这些请求。无论请求何时到达,都会进入桶中排队,桶会以固定的速率释放请求进行处理。如果桶满了,后续的新请求将会被直接丢弃或等待桶中有空位。
优点:
- 无论请求何时到达,其处理速率都保持一致,有助于平滑流量,防止服务被瞬时高峰冲垮。
- 控制的是请求流出速率,而非总量。
缺点:
- 对于突发流量没有弹性,不能应对短时间内大量合法请求的情况。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class LeakyBucketRateLimiter {
private final BlockingQueue<Request> queue;
private final int bucketCapacity;
private final int processRate; // 请求处理速率,例如每秒处理5个请求
public LeakyBucketRateLimiter(int bucketCapacity, int processRate) {
this.queue = new LinkedBlockingQueue<>(bucketCapacity);
this.bucketCapacity = bucketCapacity;
this.processRate = processRate;
}
public boolean allowRequest(Request request) {
// 尝试将请求放入桶中
if (!queue.offer(request)) {
// 桶已满,拒绝请求
return false;
}
// 在单独的线程或定时任务中模拟处理请求的过程
simulateProcessing();
return true;
}
private void simulateProcessing() {
// 这里只是一个模拟处理过程的例子,实际情况可能更复杂
for (int i = 0; i < processRate && !queue.isEmpty(); i++) {
try {
Request request = queue.poll(1, TimeUnit.SECONDS);
if (request != null) {
// 处理请求
handleRequest(request);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
private void handleRequest(Request request) {
// 实际处理请求的逻辑
System.out.println("Handling request: " + request);
}
}
四、令牌桶算法 (Token Bucket)
实现原理: 令牌桶算法有一个令牌生成器,按照一定速率持续向桶中添加令牌。每次请求到来时,需要从桶中获取一个令牌才能被执行,如果桶中没有令牌,则请求被拒绝。令牌桶允许令牌积累,所以它可以应对短期的流量高峰。
优点:
- 可以应对突发流量,因为令牌可以提前积累。
- 控制的是请求流入速率,同时也可设置总的请求量上限。
缺点:
- 实现相对复杂,尤其是令牌的生成与消耗逻辑。
借助Google Guava库的RateLimiter实现令牌桶算法
import com.google.common.util.concurrent.RateLimiter;
public class TokenBucketRateLimiter {
private final RateLimiter rateLimiter;
public TokenBucketRateLimiter(double permitsPerSecond, double burstyBuckets) {
// 创建令牌桶,参数含义:每秒生成令牌数,最大令牌容量(burstiness)
rateLimiter = RateLimiter.create(permitsPerSecond, burstyBuckets);
}
public boolean allowRequest() {
// 尝试获取令牌,若成功则返回true,否则返回false
return rateLimiter.tryAcquire();
}
}