Redisson
核心API
Redisson 最值得掌握的 API,可以按用途分成以下几类。
| 业务场景 | 核心 API | 典型用途 |
|---|---|---|
| 普通缓存、对象存储 | RBucket |
缓存用户信息、开关配置、幂等标记 |
| Hash、带过期时间的 Map | RMap、RMapCache |
用户属性、分布式缓存 |
| Set、List、排序集合 | RSet、RList、RScoredSortedSet |
去重、列表、排行榜 |
| 原子计数 | RAtomicLong |
计数器、序号 |
| 分布式锁 | RLock |
防止并发重复操作 |
| 公平锁、读写锁 | RFairLock、RReadWriteLock |
FIFO 加锁、读写互斥 |
| 信号量 | RSemaphore |
限制同时执行的任务数 |
| 分布式限流 | RRateLimiter |
接口限流、控制调用频率 |
| 队列 | RQueue、RBlockingQueue 等 |
任务传递、生产者消费者 |
| 发布订阅 | RTopic |
消息通知、事件广播 |
| 分布式计数与同步 | RCountDownLatch |
多个任务汇合、等待初始化 |
这里有一个重要区别:分布式数据结构解决数据共享问题,分布式同步器解决多个应用实例之间的并发协调问题。 例如,RMap 适合保存共享数据,而 RLock 适合协调多个服务实例对同一业务资源的操作。
二、引入依赖与连接 Redis
2.1 Maven 依赖
如果只想使用 Redisson 核心 API,推荐先引入 redisson,自己管理客户端 Bean,适合学习以及需要精确控制 Redis 连接的 Spring Boot 项目。
1 | <dependency> |
这里用的是 Redisson 3.x 的示例版本。实际项目应根据 Java、Spring Boot、Spring Data Redis 版本选择兼容版本,不要直接把最新版 starter 塞进旧项目。Redisson 官方将其 Spring Data 适配模块分别对应 Spring Boot 2.x、3.x 和 4.x。
如果希望 Spring Boot 自动装配,可以使用 redisson-spring-boot-starter,但要注意其版本与项目现有依赖的兼容性。
2.2 配置 RedissonClient
最基础也最方便调试的方式是手动创建客户端。
1 | import org.redisson.Redisson; |
上面的线程数和连接池大小只是示例,应根据业务的并发量、Redis 实例规格和压测结果调整。
这里的关键是:
RedissonClient应当作为单例 Bean 复用,不要每个请求创建一个客户端。shutdown()应当在应用关闭时调用。redis://表示普通 Redis 连接,启用 TLS 时通常使用rediss://。setDatabase(0)指定 Redis 逻辑数据库,Redis Cluster 场景不能照搬多数据库的配置思路。
Redisson 支持单机、主从、Sentinel 和 Cluster 等部署方式。
2.3 Redis Cluster 配置
如果 Redis 使用集群模式,需要使用 useClusterServers(),而不是 useSingleServer()。
1 | Config config = new Config(); |
实际部署时,节点地址、密码以及 TLS 配置需要使用环境配置或密钥管理系统注入,不要硬编码在代码仓库。
三、基本数据类型与集合操作
3.1 RBucket:操作普通对象
RBucket<T> 可以理解为 Redis 中一个能够存放序列化 Java 对象的值容器。它适合普通缓存、配置、标记和单值数据。
1 | import org.redisson.api.RBucket; |
如果需要缓存 Java 对象:
1 | RBucket<User> bucket = |
需要确保使用的 Codec 能够正确序列化和反序列化 User。
常用方法速查:
| 方法 | 作用 |
|---|---|
get() |
获取对象 |
set(value) |
设置值 |
set(value, time, unit) |
设置值并设置过期时间 |
delete() |
删除键 |
expire(time, unit) |
设置过期时间 |
setIfAbsent(value) |
仅当键不存在时设置 |
getAndSet(value) |
设置新值并返回旧值 |
compareAndSet(expect, update) |
满足预期值时原子更新 |
其中 setIfAbsent 在部分较老的 Redisson 版本中需要换成 trySet。这些条件更新操作由 Redis 原子执行,不需要为了单键的简单写入再额外加分布式锁。
例如,用于防止同一个请求短时间内重复提交:
1 | String key = "submit:token:" + requestId; |
注意:这只是 Redis 层面的幂等标记示例。如果涉及真实订单、金额或重要状态变更,仍然需要考虑数据库唯一约束、事务和失败重试等问题。
3.2 RMap:操作类似 Hash 的数据
RMap<K,V> 是分布式 Map,适用于共享属性、按字段读写的数据。
1 | import org.redisson.api.RMap; |
如果不需要获取更新前的旧值,可以使用 fastPut()、fastRemove() 这类方法,减少不必要的返回数据。
注意:RMap 本身不是普通 Java 内存中的 HashMap,每次操作都可能涉及网络通信。
3.3 RMapCache:每个字段单独设置过期时间
RMapCache 特别适合这种场景:
用户维度的数据保存在同一张 Map 中,但每条数据的过期时间各不相同。
1 | import org.redisson.api.RMapCache; |
普通 RMap 不提供相同形式的每条记录独立 TTL 操作;需要这种语义时,可以使用 RMapCache。
适用场景包括临时会话、短期状态和有独立过期规则的数据。具体的过期清理实现和功能限制应结合使用的 Redisson 版本查看官方文档。
3.4 RSet、RList、RQueue
这几个接口与 Java 集合的使用思路类似,但数据共享在 Redis 中。
1 | import org.redisson.api.RSet; |
集合内部的具体实现和复杂度仍由对应的 Redis 结构决定,不要把分布式集合误认为一个没有网络成本的本地集合。
3.5 RScoredSortedSet:排行榜
例如,根据用户积分创建排行榜:
1 | import org.redisson.api.RScoredSortedSet; |
需要注意排名索引通常从 0 开始;如果集合不足 10 个元素,返回结果也会少于 10 个。官方集合 API 还包含 Set、SortedSet、Map、List 等分布式数据结构。
3.6 RAtomicLong:分布式原子计数器
1 | import org.redisson.api.RAtomicLong; |
计数器存储在 Redis 中,可以被多个 JVM 共享。像 incrementAndGet() 这样的原子操作适合计数、简单序号等场景。
但是,多个不同的 Redis 操作组合起来,并不自动构成一个整体原子事务。例如先检查库存,再扣减库存,不能仅仅因为使用了 RAtomicLong 就认为整个流程没有竞态。
四、分布式锁:Redisson 最重要的功能
在单体应用中,synchronized 可以协调同一个 JVM 内的线程,但不能直接协调部署在不同机器上的 JVM。
Redisson 的 RLock 提供基于 Redis 的分布式可重入锁,并支持通过发布订阅唤醒等待者。
4.1 最常见的 RLock 用法
1 | import org.redisson.api.RLock; |
这里使用:
1 | lock.tryLock(2, TimeUnit.SECONDS); |
它表示最多等待 2 秒获取锁,成功后由 Redisson 的看门狗机制维护租约。
如果需要锁定固定时长,也可以使用另一个重载:
1 | boolean locked = lock.tryLock( |
它表示最多等待 2 秒,获得锁后最多持有 30 秒,到期自动释放。固定租约并不保证业务执行期间锁一定仍然有效:如果业务运行超过 30 秒,锁可能提前释放,因此第二种写法不能机械地套用到耗时不确定的业务中。
4.2 看门狗机制是什么?
Redisson 默认的锁看门狗超时值为 30 秒。当客户端持有锁并且仍然正常运行时,看门狗会续期,避免锁仅因初始 TTL 到期而被释放。具体超时参数可以由 lockWatchdogTimeout 配置。
理解这几个概念:
| 概念 | 含义 |
|---|---|
| 等待时间 | 调用方愿意等待多久来获取锁 |
| 租约时间 | 锁最多允许持有多久 |
| 看门狗 | 对未指定固定租约的锁自动续期 |
unlock() |
释放当前线程持有的锁 |
锁必须在 finally 中按持有情况释放。RLock 的所有权与线程关联,其他线程不能直接替持有者解锁。
4.3 不同锁的选择
以下代码均假设已有 redissonClient。
普通可重入锁
1 | RLock lock = redissonClient.getLock("lock:resource"); |
适合大部分临界区,需要防止多个实例同时修改同一个业务资源时优先考虑。
公平锁
1 | RLock lock = redissonClient.getFairLock("lock:resource"); |
适合希望等待者大体按照排队顺序获取锁的业务。公平性通常伴随额外调度成本,不建议无差别使用。
读写锁
1 | import org.redisson.api.RReadWriteLock; |
需要修改数据时,使用写锁:
1 | writeLock.lock(); |
多个读锁可以同时持有,而写锁具有独占性。
MultiLock 与 RedLock
Redisson 还提供组合锁和 RedLock 等方案,用于多个锁实例的协调。但它们有不同的拓扑和可靠性假设,不能简单理解为“锁的数量越多就一定越安全”。如果锁保护的是外部存储中的关键写入,还应考虑 fencing token,让资源自身拒绝过期持有者的写操作。
4.4 分布式锁的几个高频错误
错误一:锁名称没有绑定业务资源。
1 | redissonClient.getLock("lock:order"); |
如果所有订单共用这把锁,就会使无关订单相互阻塞。更合理的命名是:
1 | redissonClient.getLock("lock:order:" + orderId); |
错误二:直接使用固定租约,却不考虑耗时。
如果业务运行超过固定租约,其他实例可能再次拿到锁。业务必须考虑租约、执行时间和并发安全问题。
错误三:在不持有锁的情况下解锁。
这可能触发 IllegalMonitorStateException。始终在 finally 中先判断当前线程是否持有锁。
错误四:认为加锁能代替数据库事务。
分布式锁不是数据库事务。数据库仍然需要正确的事务边界、唯一约束及必要的条件更新。
五、分布式限流:RRateLimiter
例如,需要对所有服务实例共享的登录接口进行限流,每秒最多允许 100 次请求。
1 | import org.redisson.api.RRateLimiter; |
Redisson 的限流器将状态保存在 Redis 中,因此可跨多个客户端共享限制;OVERALL 是整体共享限额,PER_CLIENT 则按客户端分别计量。
关键 API:
| 方法 | 语义 |
|---|---|
trySetRate(...) |
仅在尚未配置时初始化 |
setRate(...) |
覆盖限流配置 |
tryAcquire() |
立即尝试获取许可 |
tryAcquire(n) |
一次申请 n 个许可 |
acquire() |
等待许可可用后再继续 |
availablePermits() |
查询当前可用许可数量 |
生产环境中需要注意:
第一,初始化时使用 trySetRate() 通常更合适。若每次请求都用 setRate() 重设规则,可能覆盖既有配置并影响已经消耗的许可。
第二,按用户限流时,应该使用用户维度的限流器名称,例如:
1 | String key = "limit:user:" + userId; |
否则所有用户会共用一个限额。
第三,Web 请求通常适合用 tryAcquire() 快速拒绝超限请求,然后通过 HTTP 429 返回限流结果。acquire() 可能阻塞调用线程,因此不要不加判断地用在高并发请求链路中。
六、队列:异步任务与延迟处理
6.1 RBlockingQueue:阻塞队列
1 | import org.redisson.api.RBlockingQueue; |
put()、take() 适合生产者消费者模式;offer()、poll() 适合不希望一直等待的场景。
在 Spring Boot 项目中,实际使用时应将生产者、消费者分开设计,避免在 HTTP 请求线程里直接无限阻塞消费。
6.2 延迟队列
需要让某个任务等待一段时间再进入可消费队列时,可以使用 RDelayedQueue:
1 | import org.redisson.api.RBlockingQueue; |
然后由消费者从 readyQueue 获取到期任务。
典型场景包括延迟通知、超时检查和定时触发。但是,延迟队列本身并不意味着业务只执行一次。如果任务处理失败、消费者重启或消息被重复处理,仍然需要幂等性设计。
对于需要严格投递保证、复杂重试、死信队列和丰富监控的企业级消息系统,应结合业务可靠性要求评估专门的消息中间件。
七、发布订阅:RTopic
适合跨实例广播简单通知,例如配置刷新提醒、事件通知。
1 | import org.redisson.api.RTopic; |
Redisson 为 Redis 提供了 Topic 和其他消息相关 API。
这里最容易混淆的是:普通 Redis Pub/Sub 不是可靠消息队列。 离线的订阅者不会自动收到它离线期间错过的历史消息。因此它适合广播通知,不适合独自承担不能丢失的业务任务。
八、信号量与分布式任务同步
8.1 RSemaphore:限制并发数
例如,一个外部服务最多允许 10 个并发任务访问。
1 | import org.redisson.api.RSemaphore; |
RSemaphore 适合限制整体并发任务数,而不只是请求频率。Redisson 也提供带租期的许可信号量等同步器。
注意:共享许可的初始化应当只由受控流程完成;不能每次请求到来时都重新初始化许可数,否则就会破坏并发限制。
另外,与 RLock 不同,普通信号量的许可通常可以由另一个线程释放。但这也意味着必须确保每一次成功申请最终只释放一次,否则会多出不存在的并发容量。
8.2 RCountDownLatch:等待多个任务完成
1 | import org.redisson.api.RCountDownLatch; |
三个工作任务完成后,分别执行:
1 | latch.countDown(); |
需要等待任务全部完成的线程执行:
1 | latch.await(); |
这适合等待多个初始化任务、并行任务汇合等场景。
需要注意分布式环境中的重复执行、任务失败和计数未归零等问题。Latch 是协调原语,不会自动替你恢复业务状态。
九、Spring Cache 注解整合
如果你的项目已经习惯使用 @Cacheable、@CachePut 和 @CacheEvict,可以将缓存抽象与 Redisson 结合,而不需要在所有 Service 中直接编写 Redisson 操作。
首先启用 Spring Cache:
1 | @EnableCaching |
前提是 Spring 容器已经配置了一个以 Redisson 为后端的 CacheManager,具体适配类和依赖需与所用 Redisson 版本匹配;可参考官方 Spring 集成文档。
然后业务代码可以这么写:
1 |
|
几个注解的含义:
@Cacheable:缓存命中时直接返回缓存结果。@CachePut:执行方法并将返回结果写入缓存。@CacheEvict:移除缓存。
这里有个非常重要的前提:缓存注解通常通过 Spring AOP 代理生效。对象内部通过 this.getUser() 调用同一个类的方法时,可能绕过代理,导致缓存注解不生效。
另外,@CachePut 不应被误认为自动提供数据库与 Redis 的分布式事务。缓存更新失败、并发写入和缓存一致性依然需要独立考虑。
十、布隆过滤器:RBloomFilter
当需要判断某个元素是否可能出现过,并且可以接受一定误判时,可以使用布隆过滤器。
典型用途:在查询数据库前,快速判断一个 ID 是否可能存在,从而减少部分无效查询。
1 | import org.redisson.api.RBloomFilter; |
需要准确理解布隆过滤器的性质:
false通常意味着元素不在集合中。true表示元素可能存在,不代表一定存在。不能把它作为数据库唯一性约束。
要根据容量和期望误判率进行初始化规划。
Redisson 的概率型数据结构还包括 HyperLogLog 等。

GitHub
+1
十一、异步 API 与批量操作
11.1 异步操作
Redisson 提供了异步 API,适合不希望同步等待 Redis 响应的调用链。
1 | RBucket<String> bucket = |
不同 API 对应的返回类型有所区别,例如 RFuture<T>。在使用异步锁时还需要额外注意线程所有权:一个逻辑操作可能跨越多个线程,所以获取锁与释放锁时必须正确处理线程 ID。
11.2 RBatch:批量执行
当需要对 Redis 执行多条独立操作时,可以考虑批处理,减少逐条请求的网络往返。
1 | import org.redisson.api.RBatch; |
批处理适合批量读写,不过需要注意:
批处理不等于业务事务。 如果多条操作必须作为整体提交,需要另外评估 Redisson 的事务 API、Redis 的执行语义,以及与 MySQL 之间的一致性需求。
十二、生产环境必须注意的问题
- 客户端生命周期
让 RedissonClient 由 Spring 管理并在应用退出时关闭。避免在 Service 方法、请求入口或定时任务内部反复 Redisson.create()。
- 锁的粒度和释放
锁名应当与具体业务资源绑定,临界区应尽量短;等待超时、固定租约、看门狗续期和异常释放需要一起考虑。
- 缓存与数据库一致性
Redis 里的缓存对象不是数据库事实来源的替代品。要设计过期、更新、删除、并发写入和缓存击穿等问题的处理方式。
- 连接和阻塞控制
关注 Redis 延迟、连接池利用率、超时、重连和线程池占用。阻塞队列、acquire() 以及同步等待都可能影响业务线程的可用性。
- 序列化与版本兼容
跨服务共享对象时,要确保 Codec 与对象结构兼容。谨慎处理类名、字段变更以及不同部署版本之间的数据格式差异。
这些并不是 Redisson 特有的全部问题,而是将 Redis 用在分布式系统里必须面对的工程问题。Redisson 提供了丰富的原语,但不意味着用了 API 就自动获得业务层的强一致性或可靠投递保证。
十三、常用 API 速查表
| 需求 | 推荐 API / 方法 |
|---|---|
| 保存单个对象 | getBucket(key).set(value) |
| 带 TTL 缓存 | RBucket.set(value, ttl, unit) |
| 防止重复提交 | RBucket.setIfAbsent(...) |
| 按字段存储 | getMap(key) |
| Map 字段独立过期 | getMapCache(key) |
| 原子递增 | getAtomicLong(key).incrementAndGet() |
| Set 去重 | getSet(key).add(value) |
| 排行榜 | getScoredSortedSet(key) |
| 分布式互斥锁 | getLock(key) |
| 公平锁 | getFairLock(key) |
| 读写锁 | getReadWriteLock(key) |
| 整体并发限制 | getSemaphore(key) |
| 按频率限流 | getRateLimiter(key) |
| 阻塞队列 | getBlockingQueue(key) |
| 延迟任务 | getDelayedQueue(queue) |
| 广播消息 | getTopic(key) |
| 多任务汇合 | getCountDownLatch(key) |
| 概率型成员检测 | getBloomFilter(key) |
| 批量执行 | createBatch() |