Redisson 如何用 RTransaction 对 RMap 与 RSet 做 ACID 事务提交与回滚
【免费下载链接】redissonRedisson: Valkey & Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache..项目地址: https://gitcode.com/GitHub_Trending/re/redisson
如果你的业务要求「先改 Map、再往 Set 里加值,两步要么同时生效、要么全部撤销」,用 Redisson 客户端逐条执行命令做不到这一点:中途失败会留下半套数据。RTransaction就是为此设计的:RMap、RSet(以及RMapCache、RLocalCachedMap、RSetCache、RBucket)可以参与一个具有 ACID 特性的事务,写操作会先加锁,所有数据修改操作先记入列表,直到调用commit()才真正应用;commit()或rollback()之后锁被释放。事务隔离级别为READ_COMMITTED。
下面基于仓库中的 docs/transactions.md 与事务实现、测试代码,走一遍从创建事务到提交/回滚并验证的完整路径。
事务行为与异常边界:先弄清哪些操作受保护
在写代码之前,先明确RTransaction的三个关键行为(见 docs/transactions.md):
- 事务对象从客户端创建,所有对象实例必须从该事务对象上获取(
transaction.getMap(...)、transaction.getSet(...)),而不是从RedissonClient直接拿——从RedissonClient拿到的对象不受该事务约束。 - 修改操作在
commit()之前只记录在事务内,不会落到服务端的原对象上。 - 如果在
commit()或rollback()执行过程中出错,会抛出org.redisson.transaction.TransactionException,需要捕获后决定处理策略。
接口定义见 RTransaction.java:除getMap(String name)、getSet(String name)外,还有带Codec的重载,以及commit()/commitAsync()/rollback()/rollbackAsync()四个终结方法。
配置事务选项:TransactionOptions
事务通过redisson.createTransaction(TransactionOptions)创建。默认值可以直接用TransactionOptions.defaults();需要调整时可用以下选项(默认值同时出自 docs/transactions.md 注释与 TransactionOptions.java 源码,两者一致):
TransactionOptions options = TransactionOptions.defaults() // 事务主节点与其副本之间的数据同步超时,默认 5000 毫秒 .syncSlaves(1, 5, TimeUnit.SECONDS) // 事务成功发出后的响应超时,默认 3000 毫秒 .responseTimeout(3, TimeUnit.SECONDS) // 事务尚未发出时,每次重发尝试之间的间隔,默认 1500 毫秒 .retryInterval(2, TimeUnit.SECONDS) // 事务尚未发出时的重发尝试次数,默认 3 次 .retryAttempts(3) // 若事务未在该超时内提交则自动回滚,默认 5000 毫秒;设为 -1 可禁用 .timeout(5, TimeUnit.SECONDS);注意timeout的两个作用:超时会自动回滚,并且它是写锁竞争时的上限——另一事务持有同一对象的锁时,你的事务等待超过该值就会抛出TransactionTimeoutException(测试用例testParallel正是这样验证的,见下文)。
文档示例中使用的是.syncSlavesTimeout(5, TimeUnit.SECONDS),该方法在源码中已标记@Deprecated,建议改用上面的syncSlaves(int slaves, long timeout, TimeUnit unit):slaves为0表示使用执行时刻可用的全部从节点,-1表示完全不做同步,同步能力要求 Redis 3.0+。
提交:RMap 与 RSet 的变更一起生效
主路径是 Sync 风格,完整取自 docs/transactions.md(示例中的键名myMap、mySet可替换为你自己的对象名):
RedissonClient redisson = Redisson.create(config); RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults()); RMap<String, String> map = transaction.getMap("myMap"); map.put("1", "2"); String value = map.get("3"); RSet<String> set = transaction.getSet("mySet"); set.add(value); try { transaction.commit(); } catch (TransactionException e) { transaction.rollback(); }同一段文档也给出了异步写法,适合不便阻塞的场景:
RFuture<Void> future = transaction.commitAsync(); future.exceptionally(exception -> { transaction.rollbackAsync(); return null; });验证提交与回滚:用非事务对象做前后对比
如何确认「提交前数据没变、提交后才变」以及「回滚后数据原样保留」?仓库的测试代码给出了可直接照搬的验证方法:用redisson.getMap(...)/redisson.getSet(...)拿到同一个键的非事务对象,在commit()/rollback()前后各读一次并对比。
Map 的提交验证(来自 RedissonBaseTransactionalMapTest.java 的testRemove用例,下面是简化后的等价逻辑):
RMap<String, String> m = redisson.getMap("test"); m.put("1", "2"); m.put("3", "4"); RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults()); RMap<String, String> map = transaction.getMap("test"); map.remove("3"); // commit 前:事务内已删除,但原对象不受影响 // 测试中该断言为:m.get("3") == "4" transaction.commit(); // commit 后:删除才真正生效,测试断言 m.get("3") == null回滚验证来自同一文件的testRollback用例:对map.remove("3")之后不调用commit()而调用transaction.rollback(),测试断言原对象保持m.get("1") == "2"且m.get("3") == "4"——即事务内的修改被完全丢弃。
Set 侧同理,RedissonTransactionalSetTest.java 的testAdd展示了完整对比:事务内set.add("4")之后,非事务对象s.contains("4")为false,事务内set.contains("4")为true;commit()之后s.size()从 2 变为 3。testRemove、testRemoveAll、testPutAll等用例覆盖了删除与批量操作的相同模式。
并发冲突与自动回滚:什么时候会得到 TransactionTimeoutException
RedissonBaseTransactionalMapTest的testParallel用例演示了锁竞争的判定方式:
- 事务 1 对键
put_test执行remove(1),timeout(10, TimeUnit.SECONDS); - 事务 2 对同一键执行
put(1, "aryan"),使用默认选项(timeout 默认 5000 毫秒); - 事务 2 先调用
commit(),由于事务 1 持有写锁且事务 2 等待超过其超时,commit()抛出TransactionTimeoutException,测试中即以此为预期结果; - 事务 1 随后
commit(),断言原 Mapm.size()为 0,说明只有持有锁的事务完成了写入。
由此可读出文档支持的处置方式:对commit()捕获TransactionTimeoutException(TransactionException的并发超时子类,见 transaction 包),按业务决定重试或放弃;另外timeout默认 5000 毫秒意味着长时间未提交的事务会自动回滚,长事务需要在TransactionOptions.timeout(...)中显式放大,或设-1禁用。
集群环境:用 hash tag 避免 CROSSSLOT
如果你的事务跨越多个键(如本文的myMap与mySet),在 Valkey 或 Redis 集群中必须让所有键落在同一 slot,否则服务端抛CROSSSLOT错误。文档给出的做法是用{}hash tag 括住相同部分:
RMap<String, String> map = transaction.getMap("myMap{user:1}"); map.put("1", "2"); String value = map.get("3"); RSet<String> set = transaction.getSet("mySet{user:1}"); set.add(value);{user:1}是本文的示例 tag,实际替换为你的业务分片键(如用户 ID)即可。
可选分支:Reactive / RxJava 与 Spring 事务管理器
非阻塞风格的等价写法同样在 docs/transactions.md 中给出,结构一致,只是提交与回滚通过Mono/Completable的 error 回调触发:
RedissonReactiveClient redisson = Redisson.create(config).reactive(); RTransactionReactive transaction = redisson.createTransaction(TransactionOptions.defaults()); RMapReactive<String, String> map = transaction.getMap("myMap"); map.put("1", "2"); Mono<String> value = map.get("3"); RSetReactive<String> set = transaction.getSet("mySet"); set.add("someValue"); Mono<Void> mono = transaction.commit(); mono.onErrorResume(exception -> transaction.rollback()).subscribe();如果项目已使用 Spring 声明式事务,可以接入RedissonTransactionManager(redisson-spring-transaction模块),在@Transactional方法内通过transactionManager.getCurrentTransaction()拿到RTransaction,提交与回滚由 Spring 管理,详见 integration-with-spring.md 的 Spring Transaction Manager 一节。
限制
- 只有
RMap、RMapCache、RLocalCachedMap、RSet、RSetCache、RBucket能参与事务;RList、RQueue、RSemaphore等其他对象不在事务支持范围内。 RTransaction接口只暴露getBucket/getMap/getSet/getSetCache/getMapCache/getLocalCachedMap,事务内能拿到的对象以这些方法为准。- 事务隔离级别固定为
READ_COMMITTED,不能配置为更高隔离级别。 - 文档中提到的 XA(JTA)事务仅在 Redisson PRO 版提供,社区版没有;本文路径不涉及 XA。
完成提交后,用非事务对象按「commit 前读一次、commit 后读一次」的方式即可核对数据是否按预期生效;若commit()抛出TransactionTimeoutException,说明写锁竞争超时,应结合TransactionOptions.timeout(...)的业务预期调整重试策略。
【免费下载链接】redissonRedisson: Valkey & Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache..项目地址: https://gitcode.com/GitHub_Trending/re/redisson
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考