news 2026/9/14 12:04:48

Redisson 如何用 RTransaction 对 RMap 与 RSet 做 ACID 事务提交与回滚

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redisson 如何用 RTransaction 对 RMap 与 RSet 做 ACID 事务提交与回滚

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就是为此设计的:RMapRSet(以及RMapCacheRLocalCachedMapRSetCacheRBucket)可以参与一个具有 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)slaves0表示使用执行时刻可用的全部从节点,-1表示完全不做同步,同步能力要求 Redis 3.0+。

提交:RMap 与 RSet 的变更一起生效

主路径是 Sync 风格,完整取自 docs/transactions.md(示例中的键名myMapmySet可替换为你自己的对象名):

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")truecommit()之后s.size()从 2 变为 3。testRemovetestRemoveAlltestPutAll等用例覆盖了删除与批量操作的相同模式。

并发冲突与自动回滚:什么时候会得到 TransactionTimeoutException

RedissonBaseTransactionalMapTesttestParallel用例演示了锁竞争的判定方式:

  • 事务 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()捕获TransactionTimeoutExceptionTransactionException的并发超时子类,见 transaction 包),按业务决定重试或放弃;另外timeout默认 5000 毫秒意味着长时间未提交的事务会自动回滚,长事务需要在TransactionOptions.timeout(...)中显式放大,或设-1禁用。

集群环境:用 hash tag 避免 CROSSSLOT

如果你的事务跨越多个键(如本文的myMapmySet),在 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 声明式事务,可以接入RedissonTransactionManagerredisson-spring-transaction模块),在@Transactional方法内通过transactionManager.getCurrentTransaction()拿到RTransaction,提交与回滚由 Spring 管理,详见 integration-with-spring.md 的 Spring Transaction Manager 一节。

限制

  • 只有RMapRMapCacheRLocalCachedMapRSetRSetCacheRBucket能参与事务;RListRQueueRSemaphore等其他对象不在事务支持范围内。
  • 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 12:04:16

SurfSense 前端 SVG 精度优化实战:用 SVGO 精简图标资源体积

SurfSense 前端 SVG 精度优化实战&#xff1a;用 SVGO 精简图标资源体积 【免费下载链接】SurfSense Open-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP…

作者头像 李华
网站建设 2026/9/14 12:03:11

有线通信标准全解析:从铜缆到光纤,从以太网到工业总线

我入行那会儿&#xff0c;师傅跟我说过一句话&#xff1a;无线是趋势&#xff0c;但有线才是底线。干了十几年通信和嵌入式相关的活儿&#xff0c;这话我越想越觉得对。数据中心里几百G的流量在跑&#xff0c;工厂产线上机械臂在分秒级联动&#xff0c;手术室里超高清内窥镜画面…

作者头像 李华
网站建设 2026/9/14 12:02:49

从零手写MCP Server:协议核心、开发部署与客户端接入全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华