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。
- 精确限流(如 1000 次/秒):使用
六、总结
- 信号量(RSemaphore) 适合控制有限资源的并发访问(如连接池、线程池),保证同一时间内操作数不超过上限。
- 限流器(RRateLimiter) 适合控制请求速率(如 API 限流),通过令牌桶算法平滑流量,支持突发处理。
- 两者结合可实现更复杂的流量控制策略(如先限流,超出后限制并发)。