实战指南:基于 RESP3 Client Tracking 的本地读取加速)
Redisson 客户端缓存Client Side Caching实战指南基于 RESP3 Client Tracking 的本地读取加速【免费下载链接】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 提供的 Client Side Caching客户端缓存能力展开讲解如何借助 Valkey / Redis 的 RESP3 Client Tracking 机制在 Redisson 客户端本地缓存数据、消除读操作网络往返并深入剖析其按对象整体失效的局限以及面向 Map/Hash 等结构的按条目失效本地缓存Local Cache进阶方案。读完本文你将掌握RClientSideCaching的配置、使用、选项调优与销毁流程并理解何时应切换到 Local Cached Map 等进阶实现。一、什么是 Redisson 客户端缓存客户端缓存Client Side Caching是 Redisson 提供的一种读性能优化技术。它的核心思想是把远端 Valkey / Redis 中的数据条目缓存到 Redisson 客户端本地JVM 内存中使读操作不再需要每次都走网络往返roundtrip从而显著降低延迟与服务器负载。从实现原理看Redisson 的客户端缓存基于client tracking listener实现依赖RESP3 协议这一能力在 Valkey 和 Redis 中均可使用。客户端通过订阅服务端推送的失效通知来维护本地缓存的正确性详见下文源码分析。由于数据缓存在 Redisson 侧读操作可以完全在本地完成。原文档给出的对比结论是相比常规实现读操作最高可快45 倍该数据为 Redisson 官方文档声明的性能优化幅度实际提升幅度取决于数据结构、命中率与网络环境。前提条件客户端缓存要求协议配置为 RESP3。Redisson 默认协议为 RESP2需在配置中显式开启见 configuration.mdprotocol可选值为RESP2、RESP3。二、适用的数据结构与能力边界客户端缓存可用于以下 Redisson 对象对应 RClientSideCaching 接口的工厂方法对象获取方法RBucket对象持有者 / Object HoldergetBucket(name)/getBucket(name, codec)RStream流getStream(name)/getStream(name, codec)需 Redis 5.0RSet集合getSet(name)/getSet(name, codec)RMap映射getMap(name)/getMap(name, codec)RScoredSortedSet有序集合getScoredSortedSet(name)/getScoredSortedSet(name, codec)RList列表getList(name)/getList(name, codec)RQueue队列getQueue(name)/getQueue(name, codec)RDeque双端队列getDeque(name)/getDeque(name, codec)RBlockingQueue阻塞队列getBlockingQueue(name)/getBlockingQueue(name, codec)RBlockingDeque阻塞双端队列getBlockingDeque(name)/getBlockingDeque(name, codec)RGeo地理位置getGeo(name)/getGeo(name, codec)每个工厂方法都提供了默认编解码器与自定义编解码器两个重载方便按对象控制序列化方式。对象各自的详细语义可参见 objects.md、collections.md 与 queues.md。重要局限务必先阅读客户端缓存的失效粒度是整个对象。当某个条目发生变化时服务端推送的失效消息只包含对象名不包含具体被改动的条目信息因此 Redisson 只能清除该对象在客户端缓存的全部数据无法做到精确到条目的失效。对于Map/Hash这类高频读写、单条目更新的数据结构这意味着任何一次写入都会引发整对象缓存清空缓存命中率与收益都大打折扣效果并不理想。因此如果业务以 Map 为核心数据结构请直接使用下文四、进阶实现按条目失效的本地缓存中的 Local Cache 方案而不是原生客户端缓存。三、原生客户端缓存的使用RClientSideCaching是一个有状态的对象每一个实例都会创建属于自己的一份独立缓存。这意味着不同实例之间的缓存互不共享需要按应用实际场景决定实例个数与生命周期。3.1 基本使用流程来自 client-side-caching.md 的官方示例RClientSideCaching csc redisson.getClientSideCaching(ClientSideCachingOptions.defaults()); RBucketString b csc.getBucket(test); // 读取数据此后该对象的变化开始被跟踪 b.get(); // ... 业务处理 ... // 当该对象及其关联对象不再使用时调用 destroy() 释放资源 csc.destroy();使用要点通过RedissonClient.getClientSideCaching(options)创建实例传入ClientSideCachingOptions通过RClientSideCaching获取各类带缓存能力的对象如getBucket、getSet、getList等首次对对象执行读方法后该对象才进入跟踪状态业务结束、对象不再使用时务必调用destroy()释放订阅资源详见下文源码解析。3.2 前置校验协议必须为 RESP3如果当前 Redisson 客户端协议不是 RESP3创建客户端缓存会直接抛出异常。相关校验位于 Redisson.javapublic RClientSideCaching getClientSideCaching(ClientSideCachingOptions options) { if (!getServiceManager().isResp3()) { throw new IllegalStateException(protocol config setting should be set to RESP3 value. ...); } return new RedissonClientSideCaching(commandExecutor, options); }因此使用前必须先把协议切到 RESP3。以编程方式配置为例Config config new Config(); config.useSingleServer().setAddress(redis://127.0.0.1:6379); config.setProtocol(Protocol.RESP3); // 关键开启 RESP3 RedissonClient redisson Redisson.create(config);protocol配置项的取值与默认值见 configuration.md默认RESP2可选RESP2/RESP3。YAML 配置方式同样支持例如singleServerConfig: address: redis://127.0.0.1:6379 protocol: RESP33.3 缓存行为验证结合测试用例仓库测试 RedissonClientSideCachingTest.java 直观演示了缓存命中与失效跟踪的完整链路testBucket用例Config c redisson.getConfig(); c.setProtocol(Protocol.RESP3); RedissonClient rs Redisson.create(c); RClientSideCaching csc rs.getClientSideCaching(ClientSideCachingOptions.defaults()); RBucketString b csc.getBucket(test1); Assertions.assertThat(b.get()).isNull(); // 第一次读取触发网络请求结果为 null Assertions.assertThat(b.get()).isNull(); // 第二次读取命中本地缓存 // 另一个客户端不经过客户端缓存写入 RBucketObject b2 rs.getBucket(test1); b2.set(123); // 通过 client tracking 收到失效通知后再次读取拿到最新值 Assertions.assertThat(b.get()).isEqualTo(123); csc.destroy();这个用例覆盖了三条关键语义首次读走网络、重复读命中本地缓存、远端写入后通过失效通知保证最终一致。这正是客户端缓存以失效通知换读性能的机制闭环。四、进阶实现按条目失效的本地缓存Local Cache原生客户端缓存按对象整体失效的缺陷催生了 Redisson 自己的本地缓存local cache实现。它针对键值型/集合型结构实现了按条目entry精确失效避免了一次写入清空全部缓存的浪费。原文档明确建议对 Map 或 Hash 类结构应当使用本地缓存实现而非原生客户端缓存。仓库中可用的本地缓存能力覆盖了以下场景Map 的本地缓存含逐出、数据分区JSON StoreRedissonJsonBucket 等 JSON 对象的本地缓存JCache 的本地缓存Spring Cache 的本地缓存Hibernate Cache 的本地缓存MyBatis Cache 的本地缓存Quarkus Cache 的本地缓存Micronaut Cache 的本地缓存其中核心的RMapCache/RLocalCachedMap实现位于 RedissonLocalCachedMap.java并通过Redisson.getLocalCachedMap(...)、Redisson.getMapCache(...)对外提供。选择建议高频单条目读写的 Map / Hash→ 使用 Local Cached Map按条目失效其他对象Bucket、Set、List、Queue、ScoredSortedSet 等→ 原生客户端缓存已足够且使用更简单。五、ClientSideCachingOptions 选项详解ClientSideCachingOptions接口ClientSideCachingOptions.java提供了客户端缓存的全部调优参数默认实现为ClientSideCachingParamsClientSideCachingParams.java。5.1 逐出策略 evictionPolicy取值语义NONE不做逐出但timeToLive与maxIdleTime参数依然生效默认值LRU最近最少使用Least Recently Used逐出LFU最不经常使用Least Frequently Used逐出SOFT值使用软引用JVM 内存紧张时由 GC 回收WEAK值使用弱引用对象变为弱可达时由 GC 回收从 RedissonClientSideCaching.java 的构造逻辑可以看到策略与底层缓存容器的对应关系if (params.getEvictionPolicy() ClientSideCachingOptions.EvictionPolicy.NONE) { cache new NoneCacheMap(params.getTtl(), params.getIdleTime()); } if (params.getEvictionPolicy() ClientSideCachingOptions.EvictionPolicy.LRU) { cache new LRUCacheMap(params.getSize(), params.getTtl(), params.getIdleTime()); } if (params.getEvictionPolicy() ClientSideCachingOptions.EvictionPolicy.LFU) { cache new LFUCacheMap(params.getSize(), params.getTtl(), params.getIdleTime()); } if (params.getEvictionPolicy() ClientSideCachingOptions.EvictionPolicy.SOFT) { cache ReferenceCacheMap.soft(params.getTtl(), params.getIdleTime()); } if (params.getEvictionPolicy() ClientSideCachingOptions.EvictionPolicy.WEAK) { cache ReferenceCacheMap.weak(params.getTtl(), params.getIdleTime()); }即NONE→NoneCacheMap、LRU→LRUCacheMap、LFU→LFUCacheMap、SOFT/WEAK→基于引用的ReferenceCacheMap。这些容器类位于org.redisson.cache包下。5.2 缓存大小 sizesize 0默认缓存无上限unboundedsize -1缓存始终为空不存储任何数据可用来关闭实际缓存仅保留跟踪/失效语义其他正整数限定缓存容量配合LRU/LFU策略生效。5.3 过期与空闲时间方法说明timeToLive(Duration ttl)每个缓存条目的存活时间毫秒0表示不限制maxIdle(Duration idleTime)每个缓存条目的最大空闲时间毫秒0表示不限制注意即使逐出策略为NONE这两项超时参数依然生效见ClientSideCachingOptions的 javadoc 及 ClientSideCachingParams.java 中ttl、idleTime默认均为Duration.ZERO。5.4 完整配置示例RClientSideCaching csc redisson.getClientSideCaching( ClientSideCachingOptions.defaults() .evictionPolicy(ClientSideCachingOptions.EvictionPolicy.LFU) // 逐出策略 .size(10_000) // 容量上限 .timeToLive(Duration.ofMinutes(30)) // 30 分钟 TTL .maxIdle(Duration.ofMinutes(5)) // 5 分钟最大空闲 );六、源码级工作原理6.1 读方法拦截与缓存填充RClientSideCaching的读缓存能力并非简单包装而是通过动态代理实现的。在 RedissonClientSideCaching.java 中InvocationHandler handler (proxy, method, args) - { if (!method.getName().contains(read)) { return method.invoke(instance, args); } // 提取对象名维护 name - 缓存键 的映射 String name (String) Arrays.stream(args).filter(r - r instanceof String).findFirst().orElse(null); CacheKeyParams key new CacheKeyParams(args); SetCacheKeyParams values name2cacheKey.computeIfAbsent(name, ...); values.add(key); // 命中缓存直接返回未命中则执行真实调用并填充缓存 return cache.computeIfAbsent(key, k - method.invoke(instance, args)); };可以推断其执行流程为只拦截名称包含read的读方法 → 以调用参数构造CacheKeyParams作为缓存键 → 记录对象名 → 缓存键集合的映射name2cacheKey→ 缓存未命中时执行真实远程调用并写入本地缓存。写方法不包含read则直接透传不经过缓存。6.2 失效通知与缓存清空对象级失效通过订阅服务端的 flush 监听实现RedissonClientSideCaching.javaPublishSubscribeService subscribeService this.commandExecutor.getConnectionManager().getSubscribeService(); CompletableFutureInteger r subscribeService.subscribe(this.commandExecutor, this::clearCache); listenerId r.join();收到失效消息时回调clearCache(name)public void clearCache(String name) { SetCacheKeyParams keys name2cacheKey.remove(name); if (keys null) return; for (CacheKeyParams key : keys) { cache.remove(key); } }也就是把该对象名下的所有缓存键从本地缓存中移除——这正是整个对象失效的语义来源。6.3 生命周期管理创建RedissonClientSideCaching构造时会向ServiceManager注册自身addClientSideCaching并把 command executor 复制为跟踪模式commandExecutor.copy(true)见 RedissonClientSideCaching.java销毁destroy()会移除 flush 监听removeFlushListenerAsync并从ServiceManager注销removeClientSideCaching见 RedissonClientSideCaching.java。ServiceManager中的相关方法ServiceManager.java维护了所有存活实例的集合并对外提供evictClientSideCaching(String name)用于按对象名批量触发失效——这通常是服务端推送失效消息时的落地入口。七、适用场景与最佳实践推荐使用原生客户端缓存的场景RBucket等单值对象的读多写少场景如配置项、开关、热点单值RSet、RList、RQueue、RScoredSortedSet等集合/队列对象的读加速希望以最简 APIdefaults() 一个工厂方法快速获得本地读缓存。不推荐、应切换到 Local Cache 的场景Map/Hash的高频单条目更新——每次条目变更都会清空整个 Map 的客户端缓存收益极低需要按条目精确失效、内存可控、多实例一致性更强的场景。通用建议始终先确认protocol已配置为RESP3否则getClientSideCaching会抛出IllegalStateException结合业务读写比、数据规模设置size与evictionPolicy避免无界缓存造成 OOM为缓存条目设置合理的timeToLive/maxIdle在一致性要求与命中率之间取得平衡客户端缓存实例使用完毕务必调用destroy()及时注销失效监听避免资源泄漏生产环境强烈建议先用 RedissonClientSideCachingTest.java 这类用例在本地验证失效通知行为再接入业务。八、小结Redisson 的客户端缓存基于 RESP3 Client Tracking 实现把热点数据下沉到客户端 JVM可大幅降低读操作延迟与网络往返但其按对象整体失效的机制决定了它更适合 Bucket、Set、List、Queue 等结构而 Map/Hash 场景应转向按条目失效的 Local Cached Map、JSON Store 等进阶本地缓存实现。结合ClientSideCachingOptions的逐出策略、容量与 TTL 参数以及destroy()生命周期管理即可在生产中安全、高效地使用这一能力。【免费下载链接】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),仅供参考