ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Redisson RLocalCachedMap 事务下本地缓存不一致?从两条链路的交汇点找答案

2026/9/8 17:36:03 拓冰建站 浏览量
Redisson RLocalCachedMap 事务下本地缓存不一致?从两条链路的交汇点找答案 Redisson RLocalCachedMap 事务下本地缓存不一致从两条链路的交汇点找答案【免费下载链接】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一个反直觉的事实在 Redisson 里RLocalCachedMap的事务和缓存刷新走的是两条完全不相干的代码路径。前者通过RTransaction攒操作、commit()时统一落库隔离级别为READ_COMMITTED后者靠订阅 Redis 的 key 变更消息来刷新每个 JVM 内的本地缓存。两条链路各自都没问题但交汇点恰好存在一个时间窗口——事务已经提交、Redis 主库已更新而本地缓存的失效/同步消息还在路上甚至根本不会到达。Redisson 本地缓存一致性问题的根源就在这个窗口里。RLocalCachedMap 事务场景下出现的脏读、旧值命中不是谁写坏了数据而是这两条链路的时序错位。两条各自成立、交汇就出事的链路先看事务侧。RTransaction的工作方式比较朴素你在事务里拿到的 Map、Bucket 对象写操作并不会立即发给 Redis而是进入一个待执行列表commit()时整体提交rollback()时整体丢弃锁在提交或回滚后才释放。这个机制保证了 ACID但它意味着事务内的所有写操作对外部世界是不可见的——包括那些盯着同一个 key 的本地缓存订阅者。再看缓存侧。RLocalCachedMap在 map 数据变化时会向订阅了该 key 的所有RLocalCachedMap实例推送变更。推送有粒度可配只发 key失效还是连 value 一起发更新。这个机制单独用很好但它的触发时机是Redis 里数据真的变了而不是某个事务打算变。两条链各自成立事务保证落库的原子性缓存订阅保证跨实例的收敛。问题出在交汇——缓存链路完全感知不到事务的存在。为什么事务提交后本地缓存会慢半拍把上面的机制叠起来能还原出脏读发生的完整时间线事务 A 在事务对象上对 map 执行put数据进入待提交列表Redis 未变此时其他实例的本地缓存里还是旧值——这是正常且符合READ_COMMITTED的commit()执行写操作落入 Redis缓存失效/同步消息开始广播在消息送达并处理完成之前任何直接走本地缓存的读都会命中旧值。第 4 步的窗口通常只有几毫秒到几十毫秒但库存扣减这类读旧值再写的业务逻辑恰恰会在这个窗口内被并发请求反复踩中。另外还有一个更隐蔽的情况如果你的读请求也是通过事务对象发起的那么读的是事务内部攒的数据和本地缓存完全是两个数据面两边谁也不保证和谁一致。所以排查这类问题时先确认一件事脏读窗口发生在 commit 之后的消息传播期还是发生在事务内部的读取期。两者指向不同的修复位置。缓存刷新时机的三种同步策略怎么选LocalCachedMapOptions里的SyncStrategy决定了变更消息的形态这是调缓存刷新时机最直接的旋钮源码见 LocalCachedMapOptions文档见 客户端缓存配置LocalCachedMapOptionsString, Integer options LocalCachedMapOptions.String, Integerdefaults() .syncStrategy(SyncStrategy.UPDATE) .cacheSize(1000);UPDATE变更时把新 value 随消息推给各实例本地直接写入。读密集、值不大几百字节以内的场景选它能把 commit 后的窗口期的读偏差直接覆盖掉值很大或写频繁时别选每次变更都多序列化一遍 payload网络开销会很快显现。INVALIDATE默认只推 key本地删条目下次读穿透回 Redis。写多读少、或 value 体积大的场景选它代价是窗口期内那次穿透读本身就可能读到一个刚被并发事务覆盖的旧快照——对一致性要求高的扣减类业务单独用它不够。NONE不订阅变更只靠 TTL 过期兜底。只在读到旧值也能忍的统计类场景选它任何涉及金额、库存的键都别碰。一个表格放一下取舍逻辑策略窗口期表现适合的前提别选它的前提UPDATE消息送达即拿到新值读多写少、value 体积小value 大或写 QPS 高INVALIDATE需多一次穿透读写多读少、value 大要求窗口期零旧值命中NONE最长读到 TTL 过期容忍旧值的统计场景任何强一致业务需要说明无论哪种策略commit 到消息送达之间都可能有极短传播延迟策略只是决定延迟结束后你拿到的是新值还是再读一次。事务内的 RLocalCachedMap 怎么拿才不出错这里有个容易被忽略的 API 细节事务里的 local cached map 不是按名字新建的而是从你已有的实例派生一个事务代理——RTransaction的getLocalCachedMap(RLocalCachedMap fromInstance)接收的就是一个现成的非事务实例定义见 RTransactionRLocalCachedMapString, Integer local redisson.getLocalCachedMap(stock, options); RTransaction tx redisson.createTransaction(); RLocalCachedMapString, Integer txMap tx.getLocalCachedMap(local); txMap.put(product_1001, 99); // 写入只进事务待提交列表Redis 不动 tx.commit(); // 此刻数据落库缓存变更消息从这里开始广播关键在倒数第二行事务代理的写不会触发本地缓存的任何刷新所以同一个 JVM 里我刚在事务里 put 的值在 commit 前通过非事务的local读出来仍然是旧值。如果你依赖事务内写完立刻本地读回这种模式要么改成 commit 之后再读要么把这段逻辑整体放在事务对象上操作。事务参数如timeout、retryAttempts的完整清单见 事务机制说明具体配置以官方文档为准。写多场景用缓存版本号把窗口期变成硬校验上面策略调的是旧值存活多久但库存扣减这类场景真正需要的是旧值别被当成有效值用掉。这时加一层版本号把时序问题转成显式的冲突检测// value 形如 99:7库存:版本号 String raw local.get(product_1001); int stock Integer.parseInt(raw.split(:)[0]); int version Integer.parseInt(raw.split(:)[1]); local.put(product_1001, (stock - 1) : (version 1)); // 写回带新版本这段代码的防线在于即使某个实例在 commit 后窗口期里读到了旧值两个并发扣减写回同一 key 时版本号必然冲突后写者可以通过对比或配合分布式锁串行化识别出我的依据已经过期而不是像无版本号的put一样悄悄把别人的扣减覆盖掉。前提是你的写入路径能感知冲突——纯put覆盖写感知不到需要换成条件更新或加锁代价是每次扣减多一次解析和一次冲突判定。窗口期短、写 QPS 不高时UPDATE策略加穿透读重试往往就够了别为此多一层复杂度。上线前自检清单确认SyncStrategy与读写比例匹配读多写少选UPDATE写多或 value 大选INVALIDATENONE只出现在能容忍旧值的键上确认所有读路径知道commit 到缓存收敛之间有窗口期扣减类业务在该窗口内不做无校验的读改写事务内的读写统一走tx.getLocalCachedMap(非事务实例)派生的代理不混用事务内对象和非事务对象的读回强一致键库存、额度带版本号或条件更新冲突可被检测而不是被覆盖灰度环境压测验证并发事务写同一 key监控各实例本地缓存值与 Redis 主库值的最大偏差和收敛时长如果只能记住一件事Redisson 事务保证的是落库的原子性RLocalCachedMap保证的是缓存最终收敛两者之间那段路得你自己用同步策略、版本号或读路径设计去铺。【免费下载链接】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),仅供参考