
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 事务显示扣减成功Redis 主库的库存值也对了但业务线程再从RLocalCachedMap读出来还是旧库存同一个商品被重复下单。这不是偶发 bug而是本地缓存和事务两套机制在时序上天然错开不处理就会一直有超卖窗口。读完这篇你能带走本地缓存读到旧值的完整机制链路四层拆开讲清楚一张选型地图按你的写模式直接定路线三档可运行的落地代码 上线前自查清单症状定位Redis 是对的本地缓存是旧的先把现象说准方便你对照排查事务commit()正常返回没有抛TransactionException用 redis-cli 或客户端直连查 Rediskey 的值已经是新值但同一台机器上的应用从本地缓存实例读返回旧值过几秒或下一次失效通知到达后旧值才消失如果你观察到第 4 点基本可以确定问题出在本地缓存的失效通知链路上而不是数据本身写错了。机制拆解一层一层看本地缓存为什么会落后别急着上方案先把四层机制看清楚。第 3、4 层就是超卖窗口的来源。第 1 层数据实际存了两份RLocalCachedMap Redis 里的 Hash 每个 JVM 进程内的一份本地缓存。读的时候先查本地命中就不走网络写的时候两份都要更新。所以一致性问题的本质是这两份数据在时间上永远可能不同步。第 2 层失效通知只在真实写入那一刻触发本地缓存靠发布/订阅通道同步别的连接真正改写了这个 keyRedis 端才会广播一条失效或更新消息给所有订阅实例。消息没发本地缓存不知道。第 3 层事务把写入扣住了commit 才真正落库这是关键。Redisson 事务隔离级别READ_COMMITTED的工作方式是写操作先登记在事务里并持有对应锁直到commit()才批量执行、释放锁。也就是说事务内的put在提交前不会触发第 2 层那条失效广播——因为 Redis 里的数据还没变。事务内要用到本地缓存 Map官方 API 也只有一个入口transaction.getLocalCachedMap(原实例)见 RTransaction 源码。事务视图和业务侧的非事务实例是两套对象各自的本地缓存视图互不感知直到提交。第 4 层commit 之后通知还要送达每个实例提交后广播发出但有两个漏点接收端恰好断线重连错过这一条广播 → 需要靠ReconnectionStrategy补救本地缓存配置了SyncStrategy.NONE不同步→ 本地缓存只能等 TTL 过期 结论超卖窗口 事务提交前本地读旧值 提交后通知未送达的时间差。方案选型就是围绕怎么压缩这两个窗口做的。选型地图先按写模式定路线不同场景走完全不同的路先对号入座你的情况路线读根本不在事务里本地缓存只是加速只需调同步策略走【档位二】写入频率低、key 数量少提交后显式失效走【档位一】读多写少、value 不大切 UPDATE 同步走【档位二】多进程抢写同一个 key别走事务锁版本号走【档位三】事务只涉及RMap/RBucket不碰本地缓存事务不影响本地缓存无需处理判断标准很简单写是低频可容忍还是高频强一致。低频用失效兜底最省高频就别指望通知链路了用锁把并发串行化。档位一低频更新——提交后显式失效本地缓存适合key 少、更新不频繁。思路是接受提交前本地是旧值但提交后立刻手动把本地缓存打掉下一次读必然回源 Redis。下面这段代码演示完整流程先建好带本地缓存的 Map事务内通过原实例拿事务视图写入提交后清掉本机本地缓存。// 业务侧非事务实例本地缓存上限 1000 条满后按 LRU 淘汰 LocalCachedMapOptionsString, Integer options LocalCachedMapOptions.String, Integername(product_stock) .cacheSize(1000) .evictionPolicy(EvictionPolicy.LRU) .syncStrategy(SyncStrategy.INVALIDATE); // 默认策略远端变更只失效本地条目 RLocalCachedMapString, Integer stock redisson.getLocalCachedMap(options); RTransaction tx redisson.createTransaction(TransactionOptions.defaults()); // 事务内使用本地缓存 Map 的唯一入口传入原实例 RLocalCachedMapString, Integer txStock tx.getLocalCachedMap(stock); txStock.put(product_1001, 99); tx.commit(); // 提交后立即失效本机本地缓存下一次 get 回源 Redis stock.clearLocalCache();执行完这段代码本机再读product_1001一定拿到 99。其他机器靠正常的失效广播收敛有一个极短的延迟窗口。✅ 适用低频更新、key 数少、不想改业务结构❌ 不适用高频写每次提交都清缓存本地命中率归零、多机要求同时刻可见档位二读多写少——切换 UPDATE 同步策略适合读远多于写、value 体积小比如商品基础信息。核心动作只有一个把同步策略从默认的INVALIDATE只失效下次读回源换成UPDATE广播完整的新值各实例直接落进本地缓存。配置只需改两行LocalCachedMapOptionsString, Product options LocalCachedMapOptions.String, Productname(product) .cacheSize(5000) // 本地缓存上限 .evictionPolicy(EvictionPolicy.LRU) // 满了淘汰最久未用的 .syncStrategy(SyncStrategy.UPDATE) // 远端变更时广播新值并直接更新本地缓存 .reconnectionStrategy(ReconnectionStrategy.CLEAR); // 断线重连后清空本地缓存防脏值 RLocalCachedMapString, Product products redisson.getLocalCachedMap(options);这段配置落地后其他实例写入的瞬间你机器的本地缓存就会带上新值不用再等回源。注意代价每条变更要广播 keyvalue 的完整对象写放大和网络开销比INVALIDATE高value 是大对象几百字节以上时慎用。✅ 适用读多写少、value 小、对读到旧值零容忍❌ 不适用value 大、写密集广播风暴、且断线期间错过的更新仍会丢所以必须配CLEAR重连策略兜底档位三高并发写——把热点 key 挪出事务锁版本号适合多个进程同时扣减同一个库存。这条路线的立场是热点写别指望事务和本地缓存配合直接用分布式锁把并发串行化再用版本号做双保险。RLock.tryLock(waitTime, leaseTime, unit)会等最长 1 秒拿锁锁持有 30 秒后自动释放即使持锁进程挂掉也不会死锁RLock lock redisson.getLock(stock:product_1001); try { if (!lock.tryLock(1, 30, TimeUnit.SECONDS)) { throw new IllegalStateException(获取库存锁失败); } Stock s stockCache.get(product_1001); // 读当前值 if (s.getCount() 0) { throw new SoldOutException(); } s.setCount(s.getCount() - 1); s.setVersion(s.getVersion() 1); // 版本号1留痕可追溯 stockCache.put(product_1001, s); // 写回本地缓存与 Redis 同时更新 } finally { lock.unlock(); }跑完这段扣减在锁内完成本地缓存和 Redis 是同一时刻更新的不存在提交前旧值的窗口版本号保证即使日志排查也能对得上每一次变更。✅ 适用同 key 高频竞争写、扣减/秒杀类操作❌ 不适用写非常分散锁粒度太细会碎、太粗会互相阻塞、能接受短暂旧值的场景杀鸡用牛刀对比表一张表定方案维度提交后显式失效UPDATE 同步锁 版本号一致性窗口提交后一次失效极短广播丢失/断线期间有窗口锁内读写无窗口读性能高命中本地最高中锁竞争写性能不受影响广播开销增加最低改动量小加一行清理仅配置业务代码改造适用场景低频更新、key 少读多写少、value 小同 key 高并发竞争写 经验组合档位二做底座所有本地缓存实例统一UPDATECLEAR重连策略热点 key 单独走档位三低频 key 用档位一兜底。三者不冲突。落地检查清单上线前逐项自查确认每个RLocalCachedMap实例的syncStrategy默认INVALIDATE的实例在读多写少场景已改UPDATE所有本地缓存都设置了cacheSizeevictionPolicy(LRU)避免无界增长撑爆堆内存有断线重连可能的服务已配置reconnectionStrategy(CLEAR)事务内使用本地缓存 Map 时只通过transaction.getLocalCachedMap(原实例)获取没有混用两个实例高竞争热点 key 已从事务路径迁出改为锁 版本号集群模式下事务涉及的多个 key 已用{hash tag}归到同一 slot参考 docs/transactions.md 的 CROSSSLOT 说明压测环境已用并发脚本复现扣减场景确认无超卖延伸阅读docs/transactions.md — 事务机制、TransactionOptions各参数与默认值docs/client-side-caching.md — 客户端缓存与本地缓存的实现原理docs/data-and-services/locks-and-synchronizers.md —RLock分布式锁用法docs/integration-with-spring.md — Spring 事务管理器集成redisson/src/main/java/org/redisson/transaction/ — 事务实现源码含RedissonTransactionalLocalCachedMapredisson/src/main/java/org/redisson/RedissonLocalCachedMap.java — 本地缓存 Map 实现源码【免费下载链接】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),仅供参考