限流
限流
限流策略概览
限流通过控制请求速率保护系统,防止突发流量导致服务崩溃,常见算法包括令牌桶、漏桶和滑动窗口。
┌─────────────────────────────────────────┐
│ 限流策略 │
├─────────┬─────────┬─────────┬───────────┤
│ 令牌桶 │ 漏桶 │ 滑动窗口 │ 固定窗口 │
├─────────┼─────────┼─────────┼───────────┤
│ 允许突发│ 匀速输出│ 高精度 │ 实现简单 │
└─────────┴─────────┴─────────┴───────────┘
令牌桶算法
令牌桶以固定速率生成令牌,请求需获取令牌才能处理,允许一定突发流量。
public class TokenBucket {
private final int capacity;
private final double refillRate;
private double tokens;
private long lastRefillTime;
public TokenBucket(int capacity, double refillRate) {
this.capacity = capacity;
this.refillRate = refillRate;
this.tokens = capacity;
this.lastRefillTime = System.nanoTime();
}
public synchronized boolean tryAcquire() {
refill();
if (tokens >= 1) {
tokens -= 1;
return true;
}
return false;
}
private void refill() {
long now = System.nanoTime();
double elapsed = (now - lastRefillTime) / 1_000_000_000.0;
tokens = Math.min(capacity, tokens + elapsed * refillRate);
lastRefillTime = now;
}
}
漏桶算法
漏桶以恒定速率处理请求,平滑流量峰值,适合对输出速率有严格要求的场景。
public class LeakyBucket {
private final int capacity;
private final long leakInterval;
private int water;
private long lastLeakTime;
public LeakyBucket(int capacity, long leakInterval) {
this.capacity = capacity;
this.leakInterval = leakInterval;
this.lastLeakTime = System.currentTimeMillis();
}
public synchronized boolean tryAcquire() {
leak();
if (water < capacity) {
water++;
return true;
}
return false;
}
private void leak() {
long now = System.currentTimeMillis();
long elapsed = now - lastLeakTime;
int leaked = (int) (elapsed / leakInterval);
if (leaked > 0) {
water = Math.max(0, water - leaked);
lastLeakTime = now;
}
}
}
滑动窗口限流
滑动窗口统计时间窗口内的请求数,精度高于固定窗口,避免边界突发问题。
public class SlidingWindowRateLimiter {
private final int maxRequests;
private final long windowMs;
private final LinkedList<Long> timestamps = new LinkedList<>();
public SlidingWindowRateLimiter(int maxRequests, long windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
while (!timestamps.isEmpty() && timestamps.peekFirst() <= now - windowMs) {
timestamps.pollFirst();
}
if (timestamps.size() < maxRequests) {
timestamps.addLast(now);
return true;
}
return false;
}
}
分布式限流
// Redis Lua脚本实现分布式令牌桶
String script = """
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local tokens = tonumber(redis.call('GET', key) or capacity)
local lastTime = tonumber(redis.call('HGET', key, 'time') or now)
local elapsed = now - lastTime
tokens = math.min(capacity, tokens + elapsed * rate)
if tokens >= 1 then
tokens = tokens - 1
redis.call('SET', key, tokens)
redis.call('HSET', key, 'time', now)
return 1
end
return 0
""";