redisson信号量(RSemaphore)与限流器实现

394 阅读4分钟

Redisson 的分布式信号量(RSemaphore)和限流器(RRateLimiter)是两种不同的限流工具,前者控制并发数量,后者控制请求速率。以下从原理、实现到应用场景详细说明:

一、分布式信号量(RSemaphore)

1. 核心原理

信号量维护一个许可(Permit)计数器,线程通过获取和释放许可来控制并发访问:

  • 获取许可:计数器减 1,若计数器为 0 则阻塞或失败;
  • 释放许可:计数器加 1,唤醒等待的线程。

Redisson 的 RSemaphore 将许可计数存储在 Redis 中,通过 Lua 脚本保证操作的原子性,支持跨节点的并发控制。

2. 基本用法

// 获取信号量实例,初始许可数为5
RSemaphore semaphore = redisson.getSemaphore("resource:semaphore");
semaphore.trySetPermits(5); // 设置初始许可数

// 方式1:阻塞获取许可
semaphore.acquire(); // 阻塞直到获取许可
try {
    // 执行受限操作
} finally {
    semaphore.release(); // 释放许可
}

// 方式2:非阻塞获取许可
boolean acquired = semaphore.tryAcquire();
if (acquired) {
    try {
        // 执行受限操作
    } finally {
        semaphore.release();
    }
} else {
    // 未获取到许可,处理失败逻辑
}

// 方式3:带超时的获取
boolean acquired = semaphore.tryAcquire(10, TimeUnit.SECONDS);

3. 高级特性

  • 动态调整许可数
    semaphore.addPermits(3); // 增加3个许可
    semaphore.reducePermits(2); // 减少2个许可
    
  • 公平性:支持公平信号量(先请求的线程优先获取许可):
    RSemaphore fairSemaphore = redisson.getFairSemaphore("fair:semaphore");
    

二、分布式限流器(RRateLimiter)

1. 核心原理

限流器基于令牌桶算法实现,系统以固定速率向桶中添加令牌,请求需获取令牌才能被处理:

  • 令牌生成:按预设速率(如 1000 个/秒)向桶中添加令牌;
  • 令牌获取:请求处理前需从桶中获取令牌,无令牌则拒绝或等待;
  • 突发流量处理:桶可存储一定数量的令牌(如 2000 个),允许短时间内的流量突发。

Redisson 的 RRateLimiter 将令牌桶状态存储在 Redis 中,确保多节点间的限流一致性。

2. 基本用法

// 获取限流器,设置每秒生成100个令牌
RRateLimiter rateLimiter = redisson.getRateLimiter("api:limiter");
rateLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS);

// 方式1:非阻塞获取令牌
boolean acquired = rateLimiter.tryAcquire();
if (acquired) {
    // 处理请求
} else {
    // 拒绝请求(限流)
}

// 方式2:阻塞获取令牌(带超时)
boolean acquired = rateLimiter.tryAcquire(1, 5, TimeUnit.SECONDS);
if (acquired) {
    // 在5秒内获取到了令牌
}

// 方式3:阻塞直到获取令牌
rateLimiter.acquire(); // 可能长时间阻塞

3. 限流模式

  • OVERALL:全局限流,所有节点共享同一个令牌桶(如全局限流 1000 QPS):
    rateLimiter.trySetRate(RateType.OVERALL, 1000, 1, RateIntervalUnit.SECONDS);
    
  • PER_CLIENT:客户端级限流,每个节点独立计数(如单节点限流 200 QPS):
    rateLimiter.trySetRate(RateType.PER_CLIENT, 200, 1, RateIntervalUnit.SECONDS);
    

三、信号量 vs 限流器

特性信号量(RSemaphore)限流器(RRateLimiter)
控制维度并发数量(同一时间允许的操作数)请求速率(单位时间内的请求数)
算法模型计数器令牌桶
典型场景资源池(如连接池、线程池)API 限流、流量整形
等待策略阻塞直到许可释放等待令牌生成或拒绝
突发处理不支持支持(通过令牌桶容量)
示例配置semaphore.trySetPermits(10)rateLimiter.trySetRate(100, 1, SECONDS)

四、应用场景

1. 信号量的典型场景

  • 数据库连接池限制
    RSemaphore dbConnectionSemaphore = redisson.getSemaphore("db:connections");
    dbConnectionSemaphore.trySetPermits(50); // 最多50个连接
    
    // 获取连接前
    dbConnectionSemaphore.acquire();
    try {
        // 获取数据库连接并执行操作
    } finally {
        dbConnectionSemaphore.release();
    }
    
  • 分布式任务并发控制
    // 限制同时运行的任务数为3
    RSemaphore taskSemaphore = redisson.getSemaphore("task:semaphore");
    taskSemaphore.trySetPermits(3);
    
    // 任务执行前
    taskSemaphore.acquire();
    try {
        executeTask();
    } finally {
        taskSemaphore.release();
    }
    

2. 限流器的典型场景

  • API 接口限流
    // 限制API每秒最多1000次请求
    RRateLimiter apiLimiter = redisson.getRateLimiter("api:/users");
    apiLimiter.trySetRate(RateType.OVERALL, 1000, 1, RateIntervalUnit.SECONDS);
    
    // 处理请求前
    if (apiLimiter.tryAcquire()) {
        // 处理请求
    } else {
        return "Too Many Requests";
    }
    
  • 防止缓存击穿
    // 对热点Key查询限流(每秒最多500次)
    RRateLimiter cacheLimiter = redisson.getRateLimiter("cache:key:hot");
    cacheLimiter.trySetRate(RateType.OVERALL, 500, 1, RateIntervalUnit.SECONDS);
    
    String getFromCache(String key) {
        String value = redis.get(key);
        if (value == null && key.equals("hot")) {
            // 热点Key,需限流保护
            if (!cacheLimiter.tryAcquire()) {
                return "Try Later"; // 拒绝部分请求
            }
            // 仅允许少量请求访问数据库
            value = db.query(key);
            redis.set(key, value);
        }
        return value;
    }
    

五、配置优化建议

1. 信号量配置

  • 初始许可数:根据资源总量设置(如数据库最大连接数、服务器CPU核心数)。
  • 动态调整:可根据系统负载动态调整许可数(如高峰期增加,低峰期减少)。
  • 公平性选择:若需避免线程饥饿,使用公平信号量(getFairSemaphore())。

2. 限流器配置

  • 令牌生成速率:根据系统处理能力设置(如 API 最大 QPS)。
  • 桶容量:设置突发流量缓冲区(如允许 3 倍的瞬时峰值):
    // 每秒生成100个令牌,桶容量为300(允许3倍突发)
    rateLimiter.trySetRate(RateType.OVERALL, 100, 300, RateIntervalUnit.SECONDS);
    
  • 时间单位选择
    • 精确限流(如 1000 次/秒):使用 RateIntervalUnit.SECONDS
    • 平滑限流(如 10 次/100毫秒):使用 RateIntervalUnit.MILLISECONDS

六、总结

  • 信号量(RSemaphore) 适合控制有限资源的并发访问(如连接池、线程池),保证同一时间内操作数不超过上限。
  • 限流器(RRateLimiter) 适合控制请求速率(如 API 限流),通过令牌桶算法平滑流量,支持突发处理。
  • 两者结合可实现更复杂的流量控制策略(如先限流,超出后限制并发)。