← 返回首页
🌐

分布式限流:Redis集群与双层限流

📂 architecture ⏱ 2 min 371 words

分布式限流: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;
}

限流策略选择

根据场景选择合适的限流算法:简单计数器适合固定窗口场景,滑动窗口适合平滑限流,令牌桶适合突发流量控制,漏桶适合严格匀速处理。双层限流是生产环境的最佳实践。