Redisson完全指南:分布式系统的瑞士军刀
一、为什么需要 Redisson
1.1 一个让人头疼的场景
想象一下:你的电商系统部署了 3 个实例,用户下单扣库存的请求被负载均衡分发到不同实例上:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 实例 A │ │ 实例 B │ │ 实例 C │
│ 读库存=1 │ │ 读库存=1 │ │ │
│ 扣减→0 │ │ 扣减→0 │ │ │
└─────┬────┘ └─────┬────┘ └──────────┘
│ │
└────────┬───────┘
↓
┌──────────────┐
│ MySQL 数据库 │
│ 库存被扣成了 -1 │ ← 超卖!
└──────────────┘
两个实例同时读到库存为 1,各自扣减,最终库存变成 -1。这就是经典的分布式并发问题。
本地锁(synchronized、ReentrantLock)只能保护单个 JVM 内的互斥,跨进程就失效了。你需要的是一把「全局锁」——分布式锁。
1.2 自己用 Redis 实现分布式锁有多难?
很多人觉得用 Redis 的 SET key value NX EX 30 就够了。但生产环境下,你需要处理的问题远不止这些:
| 问题 | 说明 |
|---|---|
| 死锁 | 客户端获取锁后崩溃,锁永远不会释放 |
| 误删 | 客户端 A 的锁过期了,客户端 B 获取锁后,A 又把 B 的锁删了 |
| 续期 | 业务执行时间不确定,锁的过期时间设长了浪费、短了不安全 |
| 重入 | 同一线程嵌套调用,自己把自己锁住了 |
| 重试 | 获取锁失败要不要重试?怎么重试?等多久? |
| 网络异常 | Redis 连接断了怎么办?命令执行超时怎么办? |
// ❌ 一个"能用"但有隐患的手写实现
String nodeId = UUID.randomUUID().toString();
jedis.set("lock:key", nodeId, "NX", "EX", 30);
// ... 业务执行 ...
// 释放锁时还要用 Lua 脚本保证原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList("lock:key"), Collections.singletonList(nodeId));
这还只是最基础的场景,公平锁、读写锁、联锁的实现复杂度更是指数级增长。
1.3 Redisson:开箱即用的分布式工具箱
Redisson 是一个基于 Redis 的 Java 驻内存数据网格(In-Memory Data Grid)客户端。简单说,它帮你把上面那些头疼的问题全部封装好了:
// ✅ 同样的场景,用 Redisson 只需 3 行
RLock lock = redissonClient.getLock("lock:key");
lock.lock(); // 自动重试 + 自动续期 + 防死锁
try {
deductStock(); // 专注业务逻辑
} finally {
lock.unlock(); // 只删自己的锁
}
核心价值:
- 简化开发:分布式锁、信号量、集合等复杂逻辑一行代码搞定
- 生产级可靠:自动处理续期、重试、网络异常等边界情况
- 功能丰富:提供锁、限流器、集合、队列等 20+ 分布式对象
- 集群友好:无缝支持 Redis 单节点、哨兵、集群模式
1.4 Redisson vs 其他方案
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis SET NX | 简单、无依赖 | 需要自己处理续期、重入、误删等问题 | 非常简单的临时场景 |
| Redisson | 功能全面、生产级可靠 | 依赖 Redis、学习成本略高 | 大多数分布式场景 |
| ZooKeeper/Curator | 强一致性、支持临时节点 | 性能较低、运维复杂 | 强一致性要求高的场景 |
| etcd | 强一致性、Go 生态友好 | Java 客户端不如 Redisson 成熟 | 云原生/K8s 生态 |
| 数据库乐观锁 | 无额外依赖 | 性能差、不适合高并发 | 低并发简单场景 |
选型建议:如果你的系统已经使用了 Redis(大多数 Java 项目都是),Redisson 是分布式协调的最优选。只有在对一致性有极端要求(如金融核心交易)时才考虑 ZooKeeper。
二、快速上手
2.1 添加依赖
Maven:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.45.0</version>
</dependency>
Gradle:
implementation 'org.redisson:redisson-spring-boot-starter:3.45.0'
版本选择:建议使用 3.x 最新稳定版。3.45+ 基于 Netty 4.1.x,支持 Java 8+,与 Spring Boot 2.x/3.x 均兼容。如果使用 Spring Boot 3.x,需确认 Redisson 版本 ≥ 3.25.0。
2.2 配置方式
方式一:YAML 配置文件(推荐)
创建 src/main/resources/redisson.yml:
# 单节点模式
singleServerConfig:
address: "redis://localhost:6379"
password: null
database: 0
connectionPoolSize: 24
connectionMinimumIdleSize: 8
idleConnectionTimeout: 10000
connectTimeout: 5000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
# 线程配置
threads: 16
nettyThreads: 32
# 传输模式(默认 NIO,Linux 下可改为 EPOLL 获得更佳性能)
# transportMode: "EPOLL"
在 application.yml 中引用:
spring:
redis:
redisson:
file: classpath:redisson.yml
方式二:Spring Boot 配置(最简洁)
直接在 application.yml 中配置:
spring:
data:
redis:
host: localhost
port: 6379
password:
database: 0
# Redisson 自动复用上面的 Redis 配置
方式三:编程式配置(最灵活)
@Configuration
public class RedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setPassword(null)
.setDatabase(0)
.setConnectionPoolSize(24)
.setConnectionMinimumIdleSize(8)
.setConnectTimeout(5000)
.setTimeout(3000)
.setRetryAttempts(3)
.setRetryInterval(1500);
config.setThreads(16)
.setNettyThreads(32);
return Redisson.create(config);
}
}
2.3 哨兵模式配置
# redisson.yml
sentinelServersConfig:
masterName: "mymaster"
sentinelAddresses:
- "redis://sentinel1:26379"
- "redis://sentinel2:26379"
- "redis://sentinel3:26379"
password: "your-password"
database: 0
2.4 集群模式配置
# redisson.yml
clusterServersConfig:
nodeAddresses:
- "redis://node1:6379"
- "redis://node2:6379"
- "redis://node3:6379"
- "redis://node4:6379"
- "redis://node5:6379"
- "redis://node6:6379"
password: "your-password"
scanInterval: 2000 # 集群状态扫描间隔(毫秒)
2.5 Hello World
@Autowired
private RedissonClient redissonClient;
public void hello() {
// 分布式锁
RLock lock = redissonClient.getLock("myLock");
lock.lock();
try {
System.out.println("Hello, Redisson!");
} finally {
lock.unlock();
}
}
三、分布式锁(核心重点)
分布式锁是 Redisson 最重要的功能,也是大多数团队引入 Redisson 的首要原因。本章将从原理到实践,全面解析。
3.1 为什么分布式锁不像本地锁那么简单
本地锁(如 synchronized)依赖 JVM 进程内的共享内存,跨进程就失效了。分布式锁必须依赖外部存储来协调多个进程。
Redis 天然适合做分布式锁的底层存储:
- 单线程执行:Redis 命令是原子性的,不存在并发竞争
- 高性能:内存操作,延迟极低(亚毫秒级)
- 支持过期:
EX参数天然支持锁的自动释放
但 Redis 做分布式锁有一个根本性的矛盾:Redis 的设计目标是高性能,而不是强一致性。这意味着你需要理解它的局限性,才能用好它。
3.2 可重入锁(最常用,掌握这一个就够了)
可重入锁是 Redisson 最核心的锁实现,占据 90% 以上的使用场景。
基本使用
RLock lock = redissonClient.getLock("order:lock:123");
// 方式一:阻塞式获取(自动续期)
lock.lock();
try {
processOrder();
} finally {
lock.unlock();
}
// 方式二:带超时的非阻塞获取
boolean locked = lock.tryLock(3, 30, TimeUnit.SECONDS);
if (locked) {
try {
processOrder();
} finally {
lock.unlock();
}
}
底层原理:Lua 脚本保证原子性
Redisson 的加锁操作通过 Lua 脚本 在 Redis 端原子执行,确保「检查 + 设置」不会被打断:
-- 加锁 Lua 脚本(简化版)
-- KEYS[1] = 锁名称
-- ARGV[1] = 锁过期时间
-- ARGV[2] = 客户端标识(线程ID)
if (redis.call('exists', KEYS[1]) == 0) then
-- 锁不存在,获取锁,设置重入计数为 1
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
-- 锁存在且是自己的,重入计数 +1(可重入)
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
-- 锁被别人持有,返回剩余过期时间
return redis.call('pttl', KEYS[1]);
关键设计:
- 使用 Hash 结构存储锁,field 是客户端线程标识,value 是重入次数
- Lua 脚本保证了「判断是否存在 → 加锁/重入 → 设置过期」的原子性
- 通过客户端标识(
clientId:threadId)保证只有锁的持有者才能释放锁
Redis 中的数据结构:
Key: "order:lock:123"
Value (Hash):
"a1b2c3d4:101" → "1" ← 客户端ID:线程ID → 重入次数
3.3 看门狗机制(自动续期)
看门狗是 Redisson 最巧妙的设计之一,解决了「锁的过期时间设多少合适」这个老大难问题。
问题场景
时间线:
0s 10s 20s
↓ ↓ ↓
[获取锁,30s] → [业务还在执行...] → [锁过期!其他线程获取锁] ← 并发问题!
如果业务执行时间超过锁的过期时间,锁会被提前释放,导致并发安全问题。
看门狗的工作机制
当你不指定 leaseTime 时(如使用无参 lock.lock()),Redisson 会启动一个定时任务,自动为锁续期:
时间线:
0s 10s 20s 30s 40s
↓ ↓ ↓ ↓ ↓
[获取锁] [续期到30s] [续期到30s] [续期到30s] [unlock,停止续期]
看门狗定时任务:每 lockWatchdogTimeout/3(默认10秒)检查一次
→ 如果当前线程仍持有锁,就重置过期时间为 lockWatchdogTimeout(默认30秒)
→ 如果线程已结束或调用 unlock(),停止续期
核心参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
lockWatchdogTimeout |
30 秒 | 锁的续期目标时间(也是默认过期时间) |
| 续期间隔 | 10 秒 | lockWatchdogTimeout / 3 |
⚠️ 关键提醒:看门狗仅在未显式指定 leaseTime 时生效。一旦你指定了 leaseTime(如
lock.lock(30, TimeUnit.SECONDS)或lock.tryLock(3, 30, TimeUnit.SECONDS)),看门狗将不会启动,锁会在指定时间后自动释放。
// ✅ 看门狗生效:自动续期
lock.lock();
lock.tryLock(-1, -1, TimeUnit.SECONDS);
// ❌ 看门狗不生效:固定过期时间
lock.lock(30, TimeUnit.SECONDS);
lock.tryLock(3, 30, TimeUnit.SECONDS);
看门狗的风险
- 程序崩溃时:看门狗线程也会随之终止,锁会在
lockWatchdogTimeout(默认30秒)后自动过期释放。这个时间通常是可接受的。 - 进程假死时:如果 JVM 发生 Full GC 或 CPU 飙升导致进程假死但未崩溃,看门狗可能仍在续期。这种情况需要配合监控来发现和处理。
3.4 锁参数完全指南
参数说明
| 参数 | 含义 | 推荐值 | 说明 |
|---|---|---|---|
waitTime |
获取锁的最大等待时长 | 0~10秒 | 0 = 不等待,-1 = 永久等待 |
leaseTime |
锁的自动释放时长 | 10~60秒 | -1 = 看门狗自动续期 |
参数组合推荐
| 场景 | waitTime | leaseTime | 看门狗 | 理由 |
|---|---|---|---|---|
| 防重复提交 | 0秒 | 30秒 | ❌ | 快速失败,不阻塞用户 |
| 库存扣减 | 3秒 | 10秒 | ❌ | 快速操作,短锁即可 |
| 用户交互操作 | 5秒 | 30秒 | ❌ | 允许短暂等待 |
| 批量数据导入 | 10秒 | -1 | ✅ | 执行时间不可预估 |
| 定时任务 | -1 | -1 | ✅ | 后台任务,必须执行成功 |
| 文件生成 | 30秒 | 60秒 | ❌ | 耗时但时间可预估 |
常见错误配置
// ❌ 错误1:waitTime 过长,用户等1分钟
lock.tryLock(60, 30, TimeUnit.SECONDS);
// 建议:waitTime 设为 3-5 秒
// ❌ 错误2:leaseTime 过短,业务还没做完锁就释放了
lock.tryLock(3, 3, TimeUnit.SECONDS);
// 建议:leaseTime = 业务最大执行时间 × 1.5
// ❌ 错误3:以为指定了 leaseTime 还有看门狗
lock.lock(30, TimeUnit.SECONDS);
// 事实:看门狗不会启动,30秒后锁一定释放
// ❌ 错误4:直接 unlock 不检查
lock.unlock(); // 可能抛出 IllegalMonitorStateException
// 建议:
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
3.5 锁类型全景
除了可重入锁,Redisson 还提供了多种特殊用途的锁:
公平锁(Fair Lock)
按请求顺序获取锁,先到先得(FIFO),避免「饥饿」现象:
RLock fairLock = redissonClient.getFairLock("fairLock");
fairLock.lock();
try {
// 按请求顺序执行
} finally {
fairLock.unlock();
}
适用场景:抢红包、任务队列等需要严格顺序的场景。性能略低于普通锁。
读写锁(ReadWrite Lock)
读锁共享,写锁排他,读写互斥:
RReadWriteLock rwLock = redissonClient.getReadWriteLock("data:lock");
// 读操作(多线程可同时持有读锁)
rwLock.readLock().lock();
try {
return readData();
} finally {
rwLock.readLock().unlock();
}
// 写操作(独占)
rwLock.writeLock().lock();
try {
updateData();
} finally {
rwLock.writeLock().unlock();
}
适用场景:读多写少的缓存更新、配置中心。
联锁(MultiLock)
同时锁定多个资源,全部获取成功才算成功,避免部分加锁导致的死锁:
RLock lock1 = redissonClient.getLock("resource1");
RLock lock2 = redissonClient.getLock("resource2");
RLock multiLock = redissonClient.getMultiLock(lock1, lock2);
multiLock.lock();
try {
// 同时操作两个资源
transfer(fromAccount, toAccount);
} finally {
multiLock.unlock();
}
适用场景:跨账户转账(同时锁 A 和 B)、跨业务操作。
为什么联锁能避免死锁?
传统方式:
线程1: lock(A) → 等待 lock(B)
线程2: lock(B) → 等待 lock(A) ← 死锁!
联锁方式:
线程1: lock(A, B) → 同时获取,要么全成功要么全失败
线程2: lock(A, B) → 等线程1释放后再尝试
红锁(RedLock)
在多个独立的 Redis 节点上同时加锁,大多数节点成功即算成功:
RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RLock lock3 = client3.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
try {
// 在多个 Redis 节点上加锁
} finally {
redLock.unlock();
}
⚠️ 争议:RedLock 算法由 Redis 作者 Antirez 提出,但分布式系统专家 Martin Kleppmann 在 How to do distributed locking 一文中指出了其在时钟跳跃和网络分区下的潜在问题。建议:大多数业务场景用单节点可重入锁就够了,仅在极端高可用需求时才考虑 RedLock。
锁类型对比
| 锁类型 | 性能 | 公平性 | 复杂度 | 推荐指数 | 典型场景 |
|---|---|---|---|---|---|
| 可重入锁 | ⭐⭐⭐⭐⭐ | ❌ | ⭐ | ⭐⭐⭐⭐⭐ | 90% 的场景 |
| 公平锁 | ⭐⭐ | ✅ | ⭐ | ⭐⭐⭐ | 严格顺序要求 |
| 读写锁 | ⭐⭐⭐⭐ | ❌ | ⭐⭐ | ⭐⭐⭐⭐ | 读多写少 |
| 联锁 | ⭐⭐⭐⭐ | ❌ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 多资源操作 |
| 红锁 | ⭐⭐ | ❌ | ⭐⭐⭐⭐ | ⭐⭐ | 极端高可用 |
3.6 锁的高级用法
RLock lock = redissonClient.getLock("myLock");
// 状态检查
lock.isLocked(); // 锁是否被任何线程持有
lock.isHeldByCurrentThread(); // 当前线程是否持有
lock.getHoldCount(); // 重入次数
// 异步获取锁
RFuture<Boolean> future = lock.tryLockAsync(3, 30, TimeUnit.SECONDS);
future.whenComplete((locked, ex) -> {
if (locked) {
try { doWork(); } finally { lock.unlock(); }
}
});
// 可中断的锁获取(响应线程中断信号)
lock.lockInterruptibly();
// 强制释放锁(⚠️ 仅用于异常恢复,严禁正常业务使用)
lock.forceUnlock();
3.7 实战案例:电商扣库存
以下是一个完整的扣库存方案,包含锁获取、幂等校验、库存扣减和异常处理:
@Service
@RequiredArgsConstructor
public class StockService {
private final RedissonClient redissonClient;
private final StockMapper stockMapper;
private final OrderMapper orderMapper;
/**
* 扣减库存(防止超卖 + 防止重复扣减)
*/
@Transactional
public boolean deductStock(String orderId, String productId, int quantity) {
String lockKey = "lock:stock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// waitTime=5秒等待,leaseTime=10秒自动释放(数据库操作很快)
boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!locked) {
log.warn("获取库存锁失败: productId={}", productId);
throw new BusinessException("系统繁忙,请稍后再试");
}
try {
// 1. 幂等校验:检查订单是否已扣减
if (orderMapper.isDeducted(orderId)) {
throw new BusinessException("请勿重复提交");
}
// 2. 查询当前库存
int stock = stockMapper.getStock(productId);
if (stock < quantity) {
throw new BusinessException("库存不足");
}
// 3. 扣减库存
stockMapper.deduct(productId, quantity);
// 4. 记录扣减状态
orderMapper.markDeducted(orderId);
return true;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("操作被中断");
}
}
}
四、分布式限流
4.1 信号量(RSemaphore)
信号量控制同时访问某资源的并发数量,就像停车场的车位管理:
RSemaphore semaphore = redissonClient.getSemaphore("export:semaphore");
// 初始化(设置总车位)
semaphore.trySetPermits(20);
// 进入停车场
semaphore.acquire(); // 阻塞等待
boolean ok = semaphore.tryAcquire(); // 非阻塞
// 离开停车场
semaphore.release();
// 查看空余车位数
int available = semaphore.availablePermits();
实际场景:Excel 导出限流
@PostConstruct
public void init() {
redissonClient.getSemaphore("export:semaphore").trySetPermits(20);
}
public void exportExcel(HttpServletResponse response) {
RSemaphore semaphore = redissonClient.getSemaphore("export:semaphore");
try {
if (!semaphore.tryAcquire(1, 3, TimeUnit.SECONDS)) {
throw new BusinessException("导出任务繁忙,请稍后再试");
}
try {
doExport(response);
} finally {
semaphore.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
许可证数量设置公式:
许可证数量 = 服务实例数 × 单实例最大并发 × 安全系数(0.8)
4.2 过期许可信号量(PermitExpirableSemaphore)
每个许可证有独立的过期时间,防止因程序崩溃导致许可证永久占用:
RPermitExpirableSemaphore semaphore = redissonClient
.getPermitExpirableSemaphore("task:semaphore");
// 获取许可证,8秒后自动释放
String permitId = semaphore.acquire(8, TimeUnit.SECONDS);
try {
doTask();
} finally {
semaphore.release(permitId);
}
4.3 限流器(RRateLimiter)
基于令牌桶算法的限流器,支持按时间维度精确控制速率:
RRateLimiter rateLimiter = redissonClient.getRateLimiter("api:limiter");
// 设置速率:每秒最多100个请求
rateLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS);
// 尝试获取1个许可
boolean allowed = rateLimiter.tryAcquire(1);
if (!allowed) {
throw new BusinessException("请求过于频繁,请稍后再试");
}
RateType 说明:
RateType.OVERALL:全局限流(所有客户端共享配额)RateType.PER_CLIENT:单客户端限流(每个 RedissonClient 独立配额)
信号量 vs 限流器对比:
| 维度 | RSemaphore | RRateLimiter |
|---|---|---|
| 限流维度 | 并发数 | 时间速率(每秒/每分/每小时) |
| 原理 | 许可证计数 | 令牌桶算法 |
| 典型场景 | 控制「同时在执行」的数量 | 控制「单位时间内」的请求数 |
| 示例 | 最多20个同时导出 | 每秒最多100次API调用 |
五、分布式数据结构
Redisson 提供了与 Java 集合 API 几乎一致的分布式数据结构,数据存储在 Redis 中,可跨进程共享。
5.1 RMap(分布式 Map)
RMap<String, User> userMap = redissonClient.getMap("user:map");
// 与 ConcurrentHashMap 几乎一样的 API
userMap.put("user:1", new User("1", "张三"));
User user = userMap.get("user:1");
userMap.putIfAbsent("user:2", newUser); // 不存在才写入
userMap.fastPut("user:3", newUser); // 快速写入(不返回旧值)
userMap.expire(1, TimeUnit.HOURS); // 设置过期时间
本地缓存优化:RLocalCachedMap 在 JVM 内存中维护一份缓存副本,减少网络开销:
RLocalCachedMap<String, User> cachedMap = redissonClient
.getLocalCachedMap("user:cache", LocalCachedMapOptions.defaults()
.cacheSize(1000) // 本地缓存最大条目数
.timeToLive(10, TimeUnit.MINUTES) // 生存时间
.evictionPolicy(EvictionPolicy.LRU) // 淘汰策略
);
cachedMap.put("user:1", user);
User u = cachedMap.get("user:1"); // 优先从本地获取,未命中再查 Redis
5.2 RSet(分布式 Set)
RSet<String> set = redissonClient.getSet("tags:set");
set.add("java");
set.contains("java");
// 集合运算
RSet<String> set1 = redissonClient.getSet("set1");
set1.readIntersection("set2"); // 交集(共同好友)
set1.readUnion("set2"); // 并集
set1.readDiff("set2"); // 差集
5.3 RList(分布式 List)
RList<String> list = redissonClient.getList("messages:list");
list.add("message1");
list.add(0, "message2"); // 插入到指定位置
String msg = list.get(0);
list.range(0, 2); // 获取前3个元素
list.trim(0, 4); // 只保留前5个
5.4 RScoredSortedSet(分布式有序集合)
RScoredSortedSet<String> sortedSet = redissonClient.getScoredSortedSet("ranking");
sortedSet.add(100.5, "user1");
sortedSet.add(95.3, "user2");
Collection<String> top10 = sortedSet.valueRange(0, 9); // 前10名
int rank = sortedSet.rank("user1"); // 排名
Double score = sortedSet.getScore("user1"); // 分数
sortedSet.addScore("user1", 5.0); // 加分
适用场景:排行榜、优先级队列、延迟队列。
5.5 布隆过滤器(RBloomFilter)
高效判断「某个元素一定不存在或可能存在」,用极小的内存开销过滤大量数据:
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("user:bloom");
// 初始化:预计100万个元素,误判率0.01%
bloomFilter.tryInit(1_000_000, 0.0001);
bloomFilter.add("user:1");
bloomFilter.contains("user:1"); // true(可能存在)
bloomFilter.contains("user:999"); // false(一定不存在)
典型场景:防止缓存穿透
请求 → 布隆过滤器判断 → 不存在 → 直接返回(不查数据库)
→ 可能存在 → 查缓存 → 命中 → 返回
→ 未命中 → 查数据库 → 写入缓存 → 返回
六、分布式协调与通信
6.1 发布订阅(RTopic)
RTopic topic = redissonClient.getTopic("order:events");
// 发布消息
topic.publish(new OrderEvent("ORDER_CREATED", "123"));
// 订阅消息
int listenerId = topic.addListener(OrderEvent.class, (channel, event) -> {
System.out.println("收到消息: " + event);
});
// 取消订阅
topic.removeListener(listenerId);
可靠消息订阅(RReliableTopic):消息持久化到 Redis,确保订阅者离线期间不丢消息。
6.2 闭锁(CountDownLatch)
分布式版本的 java.util.concurrent.CountDownLatch:
RCountDownLatch latch = redissonClient.getCountDownLatch("task:latch");
latch.trySetCount(5); // 设置计数器
// 各个工作节点完成后
latch.countDown();
// 主节点等待所有工作完成
latch.await();
6.3 分布式队列
// 基本队列(FIFO)
RQueue<String> queue = redissonClient.getQueue("task:queue");
queue.offer("task1");
String task = queue.poll();
// 阻塞队列
RBlockingQueue<String> bq = redissonClient.getBlockingQueue("task:queue");
bq.offer("task1");
String task = bq.take(); // 阻塞等待
// 延迟队列
RDelayedQueue<String> dq = redissonClient.getDelayedQueue(bq);
dq.offer("delayedTask", 10, TimeUnit.SECONDS); // 10秒后可被消费
适用场景:异步任务处理、削峰填谷、延迟任务。
七、生产实践
7.1 序列化选型
| 序列化方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| FstCodec(默认) | 高性能、体积小 | 不可读、跨语言不支持 | 内部系统 |
| JsonJacksonCodec | 可读性好、跨语言 | 性能一般、体积大 | 需要人工查看或跨语言 |
| KryoCodec | 性能最佳 | 跨版本兼容性差 | 超高性能场景 |
| MarshallingCodec | JBoss 系列 | 依赖多 | JBoss 生态 |
Config config = new Config();
config.setCodec(new JsonJacksonCodec()); // 全局设置序列化方式
与 Spring Data Redis 共存:两者默认使用不同序列化,建议各管各的 Key,互不交叉读写。如需共享数据,统一使用
JsonJacksonCodec。
7.2 降级方案
Redis 故障时分布式功能失效,需要设计降级策略:
// 降级方案:分布式锁 → 本地锁
private final ConcurrentHashMap<String, ReentrantLock> localLocks = new ConcurrentHashMap<>();
public boolean tryLockWithFallback(String lockKey, long waitTime, long leaseTime) {
try {
RLock lock = redissonClient.getLock(lockKey);
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (Exception e) {
log.warn("Redis不可用,降级到本地锁: {}", lockKey);
ReentrantLock localLock = localLocks.computeIfAbsent(lockKey, k -> new ReentrantLock());
try {
return localLock.tryLock(waitTime, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return false;
}
}
}
⚠️ 注意:本地锁只能保证单 JVM 内的互斥,降级方案仅作为兜底策略。
7.3 Key 命名规范
类型前缀:业务模块:业务操作:资源标识
| 类型 | 示例 | 说明 |
|---|---|---|
| 分布式锁 | lock:order:create:123 |
订单创建锁 |
| 信号量 | semaphore:export:excel |
导出限流 |
| 限流器 | limiter:api:query |
API限流 |
| 分布式Map | cache:user:info |
用户缓存 |
| 布隆过滤器 | bloom:user:ids |
用户ID去重 |
原则:冒号分层、小写字母、见名知意、不超过100字节。
7.4 监控与告警
| 监控项 | 说明 | 建议告警阈值 |
|---|---|---|
| 锁获取失败次数 | 反映并发竞争程度 | >10次/分钟 |
| 锁持有时间 | 反映业务执行效率 | >配置值的80% |
| 信号量可用率 | 反映资源利用率 | 可用 <20% |
| Redis 连接数 | 反映连接池压力 | >最大连接数的80% |
| Redis 响应时间 | 反映网络和负载 | >100ms |
7.5 连接池优化
| 并发量 | max-active | min-idle | max-idle |
|---|---|---|---|
| 低(<100) | 8-16 | 2-4 | 4-8 |
| 中(100-500) | 16-32 | 4-8 | 8-16 |
| 高(>500) | 32-64 | 8-16 | 16-32 |
八、总结
8.1 组件选择速查表
| 我需要… | 用这个 | 一句话说明 |
|---|---|---|
| 防止并发 | RLock |
可重入锁,90%场景首选 |
| 限流(并发数) | RSemaphore |
控制同时在执行的数量 |
| 限流(速率) | RRateLimiter |
控制每秒/每分钟的请求数 |
| 读多写少 | RReadWriteLock |
读锁共享,写锁排他 |
| 同时锁多个资源 | MultiLock |
全部获取才算成功 |
| 缓存 | RMap / RLocalCachedMap |
后者带本地缓存优化 |
| 去重 | RBloomFilter |
超省内存,有误判率 |
| 排行榜 | RScoredSortedSet |
自动按分数排序 |
| 异步任务 | RBlockingQueue / RDelayedQueue |
后者支持延迟执行 |
| 消息通知 | RTopic |
发布订阅模式 |
8.2 最佳实践清单
开发:
测试:
生产:

浙公网安备 33010602011771号