
Redisson RLocalCachedMap 事务提交后本地缓存还是旧值3 种实战解法【免费下载链接】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/redissonRedisson 是 Valkey/Redis 的 Java 客户端与实时数据平台其中 RLocalCachedMap 把热点读数据缓存在 JVM 内存里省掉一次次网络往返。本文聚焦 Redisson RLocalCachedMap 与事务混用时的问题事务提交成功、Redis 里的值也改了但各实例的本地缓存仍读到旧值导致下游决策出错。文章以 API 网关的接口配额扣减为例讲清根因并给出三种可落地的解法与选型依据。先对照三种最常见的滞后现象排查前先确认你遇到的是哪一类本实例读旧值commit()正常返回紧接着get()拿到的仍是提交前的值跨实例读旧值节点 A 提交了写入节点 B 持续读到旧值直到该键被逐出或本地缓存整体过期才恢复断线重连后大面积旧值网络抖动恢复后本地缓存里存着一批早就被远端改过的条目。⚠️ 如果你把StoreMode配成了LOCALCACHE数据只存本地、不写 Redis那么以上现象会变成永久不一致这是配置错误而不是同步延迟优先检查 LocalCachedMapOptions.java 里的相关枚举定义。事务与本地缓存为什么会错位把两个机制拆开看错位就很好理解了。事务是暂存 延迟落库。Redisson 的RTransaction对写操作先加锁操作本身只在客户端排队直到commit()才把整批写入真正打到 Redis隔离级别为READ_COMMITTED源码注释见 RTransaction.java。这意味着提交之前Redis 里的值纹丝不动服务端自然也不会发出任何缓存变更通知。本地缓存靠广播消息保持新鲜。每当一个条目发生变化Redisson 通过 pub/sub 通道通知所有节点上对应RLocalCachedMap实例删除或覆盖本地条目。整条链路是commit()落库 → 服务端广播 → 各实例异步处理。从提交到覆盖完成之间存在一个异步窗口落在窗口里的读请求命中的是内存里的旧值。可以打个比方事务像先签单、稍后送货本地缓存像家里的冰箱——冰箱不知道你家的快递几点到它只会按最后一次收到的通知更新货架。另外节点断线期间错过的广播不会补发所以ReconnectionStrategy的选择直接决定重连后本地是清仓重来还是带着旧货继续卖。三种实战方案及各自的代价方案 A把 SyncStrategy 改成 UPDATE重连策略选 LOADLocalCachedMapOptionsString, Long options LocalCachedMapOptions.String, Longdefaults() .syncStrategy(LocalCachedMapOptions.SyncStrategy.UPDATE) // 默认 INVALIDATE 只广播 16 字节哈希让各实例删键下次读回源 // UPDATE 直接携带完整键值各实例原地覆盖少一次回源 .reconnectionStrategy(LocalCachedMapOptions.ReconnectionStrategy.LOAD) .cacheSize(50000); RLocalCachedMapString, Long quota redisson.getLocalCachedMap(api:quota, options);LOAD会把断线期间被失效的条目哈希记入失效日志保留 10 分钟重连后据此恢复本地条目断线更久则整体清空本地缓存避免带着旧值恢复服务。✅ 对所有业务代码透明多实例读同一键的延迟窗口最小❌ 广播携带完整键值值大或写频繁时 pub/sub 流量与内存开销明显上升它仍然是异步通知窗口只是被压缩没有消失。方案 B提交后立即广播清空全部本地缓存RTransaction tx redisson.createTransaction(TransactionOptions.defaults()); // 事务只能包装已存在的 RLocalCachedMap 实例不能按字符串名新建 RLocalCachedMapString, Long txQuota tx.getLocalCachedMap(quota); txQuota.put(key-1001, 9999L); tx.commit(); // clearLocalCache() 会广播指令让所有实例清空本地缓存见接口注释 quota.clearLocalCache();注意clearLocalCache()不是只清本机而是向所有实例广播清空指令这正是它的价值。如果只想在本机预热而不是全局清空可以改用preloadCache()它按批从 Redis 重新拉取条目填充本地。✅ 结果确定清完之后任何读都必须回源不依赖广播到达时序❌ 每次清空都会引发一批回源读写事务越频繁Redis 侧压力越大且提交之后、清空指令生效之前仍存在短窗口。方案 C读写分离写路径绕过本地缓存走原子 CAS写路径根本不走RLocalCachedMap而是用普通RMap做原子比较并设置CAS读路径继续用本地缓存加速// 写路径普通 Map 原子 CAS旧值被他人改动时 fastPutIfAbsent 返回 false while (true) { Long current map.get(key); // map 为 redisson.getMap(api:quota) if (current null || current 0) { throw new QuotaExceededException(key); } if (map.fastPutIfAbsent(key, current - 1)) { break; // CAS 成功扣减生效 } // 失败说明并发修改循环重试 }优点写路径天然无旧值问题并发控制在 Redis 侧一次完成读路径继续享受本地缓存的低延迟缺点维护两套访问入口必须约束所有写入都走这个口子热点键竞争激烈时 CAS 重试次数上升吞吐反而下降。选型决策一张表对齐四种关注点方案一致性性能代价代码复杂度适用边界AUPDATE 同步高异步短窗口低值小写少时低读多写少、多实例共享同一批键B提交后全局清空高清空后确定中每次清空触发批量回源低写频率低、但要求提交后立刻全对C读写分离 CAS最高写路径无旧值高热点键重试高高并发扣减、配额、额度类热点写一句话结论多实例读为主选 A低频写但要提交即正确选 B写本身就是热点选 C此时别再让RLocalCachedMap出现在写链路上。上线前逐项核对清单TransactionOptions按场景配置responseTimeout、timeout、retryAttempts避免慢提交长期占用对象锁详见 docs/transactions.md集群模式下事务涉及多个键时用{}哈希标签让键落在同一 slot否则会抛 CROSSSLOT 错误ReconnectionStrategy至少用CLEAR断线恢复频繁的场景直接用LOAD测试阶段用cachedKeySet()/getCachedMap()打印本地缓存实际内容验证同步是否按预期到达更多细节见 docs/client-side-caching.md确认写热点路径不经过本地缓存实例。进阶阅读docs/transactions.mdRedisson 事务的隔离级别、选项与集群哈希标签docs/client-side-caching.mdRLocalCachedMap 的同步策略与失效机制全解docs/integration-with-spring.md在 Spring 事务管理器下协调本地缓存与事务提交时序。【免费下载链接】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),仅供参考