在Java项目中,如果不使用任何框架来实现限流,可以通过手动编写代码来控制请求的速率。以下是一个简单的示例,展示了如何使用令牌桶算法实现限流:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class RateLimiter {
private final long maxTokens; // 最大令牌数
private final long refillTokens; // 每次补充的令牌数
private final long refillPeriod; // 补充令牌的时间间隔(毫秒)
private long lastRefillTimestamp; // 上次补充令牌的时间戳
private long currentTokens; // 当前令牌数
private final LinkedBlockingQueue<Long> tokenBucket; // 存放令牌的队列
public RateLimiter(long maxTokens, long refillTokens, long refillPeriod) {
this.maxTokens = maxTokens;
this.refillTokens = refillTokens;
this.refillPeriod = refillPeriod;
this.lastRefillTimestamp = System.currentTimeMillis();
this.currentTokens = maxTokens;
this.tokenBucket = new LinkedBlockingQueue<>(maxTokens);
// 初始化令牌桶
for (int i = 0; i < maxTokens; i++) {
tokenBucket.offer(System.currentTimeMillis());
}
// 启动一个定时任务,定期补充令牌
new Thread(() -> {
while (!Thread.interrupted()) {
try {
TimeUnit.MILLISECONDS.sleep(refillPeriod);
refill();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
}
public boolean tryAcquire() {
long now = System.currentTimeMillis();
if (now - lastRefillTimestamp >= refillPeriod) {
refill();
}
if (tokenBucket.isEmpty()) {
return false; // 没有可用的令牌
} else {
Long token = tokenBucket.poll();
if (token != null && token <= now) {
return true; // 令牌有效
} else {
tokenBucket.offer(token); // 放回无效的令牌
return false; // 没有可用的令牌
}
}
}
private void refill() {
long now = System.currentTimeMillis();
long tokensToAdd = (now - lastRefillTimestamp + refillPeriod - 1) / refillPeriod * refillTokens;
lastRefillTimestamp = now;
currentTokens = Math.min(maxTokens, currentTokens + tokensToAdd);
for (int i = 0; i < tokensToAdd; i++) {
tokenBucket.offer(now);
}
}
}
使用这个RateLimiter
类,可以在需要限流的地方调用tryAcquire()
方法,如果返回true
,则表示有可用的令牌,可以继续处理请求;如果返回false
,则表示没有可用的令牌,可以选择拒绝请求或者排队等待。
Java限流算法主要包括以下几种:
固定窗口算法:这种算法通过一个支持原子操作的计数器来累计一定时间窗口内的请求次数。当请求次数达到设定的阈值时,触发拒绝策略。每过一个时间窗口,计数器重置为0重新开始计数[^2^]。
滑动窗口算法:这是对固定窗口算法的改进。在滑动窗口算法中,窗口的大小是固定的,但起止时间是动态的。它将一个大的时间窗口分割成多个小的子窗口,每个子窗口单独计数,所有子窗口的计数之和不超过整体阈值。当新请求的时间点超过时间窗口的右边界时,窗口向右移动一个小窗口的距离,最左边的小窗口的计数值被舍弃[^3^][^4^]。
漏桶算法:漏桶算法将请求看作是流入的水,水桶容量有限,水以恒定的速率流出。如果水桶满了,新的请求(水)就会被丢弃。这种方法可以平滑突发流量,保证流量以相对稳定的速率处理[^5^]。
令牌桶算法:令牌桶算法通过一个令牌桶来控制请求的速率。系统以一定的速率向桶中添加令牌,当有请求到达时,需要从桶中取出一个令牌才能继续处理。如果桶中没有令牌,请求要么排队等待,要么被直接拒绝。这种方法既可以控制请求的突发流量,又可以允许一定程度的突发请求处理[^5^]。
总的来说,这些算法各有优缺点,适用于不同的场景。选择合适的限流算法取决于具体的应用需求、系统性能要求以及预期的流量模式。