分布式限流:Redis集群与双层限流
分布式限流:Redis集群与双层限流
限流算法概述
限流是保护系统稳定性的关键手段。常见算法包括固定窗口计数器、滑动窗口、漏桶和令牌桶。每种算法有不同的适用场景和特性。
// 限流器接口
public interface RateLimiter {
boolean tryAcquire(String key, int permits);
boolean tryAcquire(String key, int permits, long timeoutMs);
void release(String key, int permits);
}
滑动窗口限流
滑动窗口比固定窗口更平滑,避免了窗口边界处的突发流量问题。
import time
from collections import defaultdict
class SlidingWindowRateLimiter:
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.requests = defaultdict(list)
def try_acquire(self, key: str) -> bool:
now = time.time()
window_start = now - self.window_seconds
# 清理过期记录
self.requests[key] = [
ts for ts in self.requests[key] if ts > window_start
]
# 检查是否超过限制
if len(self.requests[key]) >= self.max_requests:
return False
# 记录请求
self.requests[key].append(now)
return True
# Redis滑动窗口实现
def sliding_window_acquire(redis_client, key: str, limit: int, window: int) -> bool:
"""使用Redis有序集合实现滑动窗口"""
now = time.time()
pipe = redis_client.pipeline()
# 移除窗口外的记录
pipe.zremrangebyscore(key, 0, now - window)
# 获取当前窗口内的请求数
pipe.zcard(key)
# 添加当前请求
pipe.zadd(key, {str(now): now})
# 设置过期时间
pipe.expire(key, window)
results = pipe.execute()
current_count = results[1]
return current_count < limit
令牌桶算法
令牌桶算法以固定速率生成令牌,请求需要获取令牌才能通过。支持突发流量但限制平均速率。
type TokenBucket struct {
capacity int64 // 桶容量
tokens int64 // 当前令牌数
refillRate int64 // 令牌生成速率(每秒)
lastRefill time.Time // 上次填充时间
mu sync.Mutex
}
func NewTokenBucket(capacity, refillRate int64) *TokenBucket {
return &TokenBucket{
capacity: capacity,
tokens: capacity,
refillRate: refillRate,
lastRefill: time.Now(),
}
}
func (tb *TokenBucket) TryAcquire(tokens int64) bool {
tb.mu.Lock()
defer tb.mu.Unlock()
tb.refill()
if tb.tokens >= tokens {
tb.tokens -= tokens
return true
}
return false
}
func (tb *TokenBucket) refill() {
now := time.Now()
elapsed := now.Sub(tb.lastRefill).Seconds()
newTokens := int64(elapsed * float64(tb.refillRate))
if newTokens > 0 {
tb.tokens = min(tb.capacity, tb.tokens+newTokens)
tb.lastRefill = now
}
}
Redis集群双层限流
生产环境使用本地限流+Redis分布式限流的双层架构,减少Redis压力同时保证全局一致性。
// 双层限流器
public class TwoLayerRateLimiter {
private final LocalRateLimiter localLimiter;
private final RedisRateLimiter distributedLimiter;
private final int localThreshold;
public TwoLayerRateLimiter(int maxPerSecond, int localThreshold) {
this.localLimiter = new LocalRateLimiter(maxPerSecond);
this.distributedLimiter = new RedisRateLimiter(maxPerSecond);
this.localThreshold = localThreshold;
}
public boolean tryAcquire(String key) {
// 第一层:本地限流
if (!localLimiter.tryAcquire(key)) {
return false;
}
// 第二层:分布式限流(本地通过后才访问Redis)
return distributedLimiter.tryAcquire(key);
}
}
// Lua脚本保证Redis操作原子性
String rateLimitScript = """
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- 移除窗口外的请求
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- 获取当前窗口内的请求数
local current = redis.call('ZCARD', key)
if current < limit then
-- 允许请求
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return 1
else
-- 拒绝请求
return 0
end
""";
public boolean tryAcquire(String key, int limit, int windowSeconds) {
Long result = redisTemplate.execute(
new DefaultRedisScript<>(rateLimitScript, Long.class),
Collections.singletonList(key),
String.valueOf(limit),
String.valueOf(windowSeconds),
String.valueOf(System.currentTimeMillis())
);
return result != null && result == 1L;
}
限流策略选择
根据场景选择合适的限流算法:简单计数器适合固定窗口场景,滑动窗口适合平滑限流,令牌桶适合突发流量控制,漏桶适合严格匀速处理。双层限流是生产环境的最佳实践。