- 后端
- 缓存抽象
【免费下载链接】jetcache
JetCache is a Java cache framework.
本文基于当前仓库 docs/EN/RedisWithRedisson.md 展开,讲解 JetCache 如何通过 Redisson 客户端访问 Redis,覆盖三种主流接入方式——Spring Boot 自动配置、非 Spring Boot 环境的 Java 配置类、以及脱离注解的 Builder API——并结合
jetcache-redisson模块源码(RedissonCache.java、RedissonCacheBuilder.java、RedissonAutoConfiguration.java)深入剖析其底层存储模型、键构造规则与广播同步机制。读完本文,你将能独立完成 Redisson 接入 JetCache 的依赖引入、配置编写、缓存构建与多实例同步配置,并理解每个配置项背后的实现原理。
为什么选择 Redisson:客户端选型与依赖引入
JetCache 的 Redis 支持面向多种 Java 客户端,官方文档明确列出:
- Jedis:JetCache 早期版本(2.2 之前)默认使用的客户端;
- Lettuce:从 JetCache 2.2 开始支持,提供异步访问与 Redis 集群支持;
- Redisson:从 JetCache 2.7 版本开始支持(依据 docs/CN/RedisWithRedisson.md 中的版本说明)。
Redisson 是功能最"重量级"的客户端,自带分布式对象、分布式锁、发布订阅(RTopic)等能力。在 JetCache 中选用 Redisson 时,需要引入以下 Maven Artifact 之一:
- 普通 Maven 项目:
com.alicp.jetcache:jetcache-redisson; - Spring Boot 项目:
com.alicp.jetcache:jetcache-starter-redisson。
jetcache-starter-redisson的依赖结构(见 jetcache-starter-redisson/pom.xml)包含四部分:jetcache-autoconfigure(自动配置)、jetcache-anno(注解支持)、jetcache-redisson(Redisson 实现)以及redisson-spring-boot-starter(由 Redisson 官方提供的 Spring Boot starter,负责根据spring.data.redis.*配置自动创建RedissonClientBean)。
而jetcache-redisson模块本身(见 jetcache-support/jetcache-redisson/pom.xml)只依赖jetcache-core和org.redisson:redisson,是纯客户端层面的实现,不依赖 Spring。
Spring Boot 环境下的 Redisson 集成
关键设计:客户端不通过配置创建,而是取 Spring Bean
与 Jedis、Lettuce 支持"通过配置项直接创建连接"不同,JetCache 的 Redisson 支持不提供任何连接相关的配置解析,Redisson 客户端实例必须作为 Spring Bean 存在,JetCache 通过容器查找来获得它。
这意味着在 Spring Boot 环境中,你只需要提供RedissonClientBean 的名称给 JetCache,即配置项redissonClient,其余连接参数(地址、密码、数据库、连接池等)全部由 Redisson 自身的配置体系负责——通常由redisson-spring-boot-starter依据spring.data.redis.host、spring.data.redis.port等标准配置自动装配。
application.yml 最小配置
以下配置摘引自官方文档(省去了 local 本地缓存相关配置):
jetcache: areaInCacheName: false remote: default: type: redisson redissonClient: redisClientBeanName broadcastChannel: projectA keyConvertor: fastjson2 #other choose:fastjson(same as fastjson2)/jackson/jackson3 defaultExpireInMillis: 10000 keyPrefix: spring-data-redis各配置项说明:
| 配置项 | 作用 | 备注 |
|---|---|---|
jetcache.areaInCacheName | 是否把 cache area 拼进远程缓存 key 前缀 | 仅用于兼容旧版本;从 2.7.0.RC 起默认false(见 GlobalCacheConfig.java) |
jetcache.remote.default.type | 远程缓存实现类型 | 固定写redisson,自动配置据此激活 Redisson 相关装配 |
redissonClient | Spring 容器中RedissonClientBean 的名称 | 仅当容器中存在多个RedissonClientBean 时必填(详见下文源码解析) |
broadcastChannel | 广播频道名称 | 用于多实例间本地缓存失效通知,留空则不启用广播 |
keyConvertor | Key 序列化转换器 | fastjson2(默认,fastjson与它等同)/jackson/jackson3 |
defaultExpireInMillis | 缓存默认过期时间(毫秒) | 这是 2.1 时代的兼容写法,等价于expireAfterWriteInMillis |
keyPrefix | 远程缓存 key 前缀 | 防止不同 Cache 实例的 key 互相冲突 |
关于
expireAfterWriteInMillis与defaultExpireInMillis的关系,见 AbstractCacheAutoInit.java:解析时优先读取expireAfterWriteInMillis,为空则回退读取defaultExpireInMillis,这是与 2.1 版本兼容的设计。另外还可以配置expireAfterAccessInMillis设置"访问后过期"时间。
仓库中 samples/spring-boot-sample/redisson/src/main/resources/application.yml 提供了一个更完整的可运行配置,同时配置了本地缓存与远程 Redisson 缓存,并显式指定了valueEncoder: java、valueDecoder: java(不配置时的默认值,见下文):
jetcache: statIntervalMinutes: 1 areaInCacheName: false decodeFilterAllowPatterns: - jetcache.samples. local: default: type: linkedhashmap keyConvertor: fastjson2 remote: default: type: redisson keyConvertor: fastjson2 broadcastChannel: projectA keyPrefix: projectA valueEncoder: java valueDecoder: java defaultExpireInMillis: 5000 spring: data: redis: host: 127.0.0.1 port: 6379 timeout: 1000注意这里的spring.data.redis.*正是交给redisson-spring-boot-starter使用的连接配置,JetCache 自身并不解析它们。
redissonClient配置的底层处理逻辑
Spring Boot 下的自动装配入口是 RedissonAutoConfiguration.java:
RedissonCondition extends JetCacheCondition,通过 JetCacheCondition.java 扫描jetcache.local.*与jetcache.remote.*配置树,只要某个 area 的type为redisson即激活该自动配置类;- 激活后注册
RedissonAutoInit(继承ExternalCacheAutoInit),在initCache()中完成客户端的解析(见 RedissonAutoConfiguration.java),其逻辑分三种情况:- 容器中没有
RedissonClientBean:直接抛出CacheConfigException("no RedissonClient in spring context"); - 容器中只有一个
RedissonClientBean:直接使用它,redissonClient配置项可省略; - 容器中有多个
RedissonClientBean:必须配置redissonClient指定 Bean 名称,否则抛CacheConfigException("redissonClient is required, because there is multiple RedissonClient in Spring context");若指定名称在容器中不存在,则抛CacheConfigException("there is no RedissonClient named ... in Spring context")。
- 容器中没有
拿到客户端后,RedissonAutoInit调用RedissonCacheBuilder.createBuilder().redissonClient(client),再交给ExternalCacheAutoInit.parseGeneralConfig()(见 ExternalCacheAutoInit.java)解析keyPrefix、broadcastChannel、valueEncoder、valueDecoder等通用远程配置;keyConvertor、过期时间等则由 AbstractCacheAutoInit.java 解析。
值序列化(valueEncoder / valueDecoder)
远程缓存的值最终以byte[]形式写入 Redis,因此必须指定序列化策略。源码 ExternalCacheConfig.java 给出了默认值:valueEncoder默认为JavaValueEncoder.INSTANCE,valueDecoder默认为DecoderMap.defaultJavaValueDecoder(),即默认使用Java 原生序列化。
在注解/自动配置体系中,序列化策略字符串由 DefaultEncoderParser.java 解析,支持:
java:Java 原生序列化,默认策略(见 SerialPolicy.java);kryo/kryo5:Kryo 序列化,从 2.8.0 起KRYO与KRYO5使用同一 Kryo5 实现;fastjson2、jackson3:编码器/解码器已有实现,但默认不注册(官方注释说明 JSON 不适合作为 Java 通用序列化工具,存在兼容性问题);- 同时支持以 URI 形式携带
useIdentityNumber=false参数,例如java?useIdentityNumber=false。
keyConvertor 的取值
Key 转换器由 DefaultKeyConvertorParser.java 解析:
fastjson/fastjson2:均映射到Fastjson2KeyConvertor.INSTANCE(从 2.8.0 起两者完全等同,见 KeyConvertor.java 中的注释说明);jackson:JacksonKeyConvertor.INSTANCE;jackson3:Jackson3KeyConvertor.INSTANCE;none:恒等转换(NONE_INSTANCE),即 key 不转换。
Fastjson2KeyConvertor的实现(Fastjson2KeyConvertor.java)很简单:String类型的 key 原样保留,其余对象通过JSON.toJSONString序列化为字符串。
非 Spring Boot 环境:Java 配置类集成
不使用 Spring Boot 时,可以在 Spring 配置类中手工组装GlobalCacheConfig。以下是官方文档给出的完整示例:
@Configuration @EnableMethodCache(basePackages = "com.company.mypackage") @EnableCreateCacheAnnotation @Import(JetCacheBaseBeans.class) //need since jetcache 2.7+ public class JetCacheConfig { @Bean public RedissonClient redissonClient(){ // build RedissonClient ... } //@Bean for jetcache <=2.6 //public SpringConfigProvider springConfigProvider() { // return new SpringConfigProvider(); //} @Bean public GlobalCacheConfig config(RedissonClient redissonClient){ Map localBuilders = new HashMap(); EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder .createLinkedHashMapCacheBuilder() .keyConvertor(Fastjson2KeyConvertor.INSTANCE); localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder); Map remoteBuilders = new HashMap(); RedissonCacheBuilder remoteCacheBuilder = RedissonCacheBuilder.createBuilder() .keyConvertor(Fastjson2KeyConvertor.INSTANCE) .valueEncoder(JavaValueEncoder.INSTANCE) .valueDecoder(JavaValueDecoder.INSTANCE) .broadcastChannel("projectA") .redissonClient(redissonClient); remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder); GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig(); globalCacheConfig.setLocalCacheBuilders(localBuilders); globalCacheConfig.setRemoteCacheBuilders(remoteBuilders); globalCacheConfig.setStatIntervalMinutes(15); globalCacheConfig.setAreaInCacheName(false); return globalCacheConfig; } }要点说明:
@Import(JetCacheBaseBeans.class)在 2.7+ 是必需的:该配置类(见 JetCacheBaseBeans.java)自动注册SpringConfigProvider(含@Bean(destroyMethod = "shutdown"))和名为jcCacheManager的SimpleCacheManager。而在 2.6 及更早版本,需要手工声明SpringConfigProviderBean(示例中注释保留的代码);@EnableMethodCache:开启方法缓存切面(AOP),basePackages指定需要拦截注解的包路径;@EnableCreateCacheAnnotation:开启@CreateCache注解支持(注意:该注解在仓库中已标记@Deprecated,官方建议改用注入CacheManager后通过getOrCreateCache(QuickConfig)创建缓存,见 EnableCreateCacheAnnotation.java 的注释);GlobalCacheConfig:全局配置容器,setLocalCacheBuilders/setRemoteCacheBuilders按 area(区域)组织缓存构建器,CacheConsts.DEFAULT_AREA即"default";setStatIntervalMinutes(15)表示每 15 分钟输出一次统计信息。
这里本地缓存使用LinkedHashMapCacheBuilder,远程使用RedissonCacheBuilder,两者通过keyConvertor统一 Key 序列化方式,值编码统一使用JavaValueEncoder/JavaValueDecoder。这样配置出的就是"本地 + 远程"两级缓存,配合broadcastChannel还能实现多实例本地缓存同步(见下文广播机制)。
不使用注解:Builder API 手工构建 Cache
如果不希望使用@CreateCache、@Cached注解,可以直接用 Builder API 编程式地创建缓存。官方文档给出的最小示例:
Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0); Cache<String, String> cache = RedissonCacheBuilder.createBuilder() .redissonClient(Redisson.create(config)) .keyConvertor(Fastjson2KeyConvertor.INSTANCE) .valueEncoder(JavaValueEncoder.INSTANCE) .valueDecoder(JavaValueDecoder.INSTANCE) .expireAfterWrite(200, TimeUnit.SECONDS) .buildCache();Builder 的构建入口是 RedissonCacheBuilder.java:
createBuilder()返回RedissonDataCacheBuilderImpl,构造时通过buildFunc(config -> new RedissonCache((RedissonCacheConfig) config))绑定最终构建函数;redissonClient(RedissonClient)把客户端写入RedissonCacheConfig(该配置类仅比ExternalCacheConfig多一个redissonClient字段,见 RedissonCacheConfig.java);keyConvertor、valueEncoder、valueDecoder、expireAfterWrite等均继承自ExternalCacheBuilder(ExternalCacheBuilder.java)。
仓库中的可运行示例 RedissonExample.java 展示了完整的生命周期管理:
Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0); Redisson redisson = (Redisson) Redisson.create(config); Cache<String, String> cache = RedissonCacheBuilder.createBuilder() .redissonClient(redisson) .keyConvertor(Fastjson2KeyConvertor.INSTANCE) .keyPrefix("projectC") .buildCache(); cache.put("K1", "V1"); System.out.println(cache.get("K1")); redisson.shutdown();为什么手工构建必须设置 keyPrefix
这是 Builder API 与注解方式的一个重要差异:
- 注解方式(
@Cached/@CreateCache)会自动生成 keyPrefix; - 手工构建时,需要显式设置
keyPrefix,以免不同 Cache 实例的 key 在 Redis 中互相冲突。
从源码看这不仅是"建议",而是强制要求:AbstractExternalCache.java 的checkConfig()在构造时校验,若config.getKeyPrefix()为null会直接抛出CacheConfigException("keyPrefix is required")。因此官方 Builder 示例中省略keyPrefix的写法仅作示意,实际使用时建议像RedissonExample那样通过.keyPrefix("projectC")(或keyPrefixSupplier)显式指定。
key 的构造过程
RedissonCache的 key 构造链路为:RedissonCache.getCacheKey()→AbstractExternalCache.buildKey()→ExternalKeyUtil.buildKeyAfterConvert()(见 ExternalKeyUtil.java):
- 先用
keyConvertor把原始 key 转成统一形态(Fastjson2KeyConvertor会把非 String 对象序列化为 JSON 字符串); - 再按类型把 key 转成字节:
String直接 UTF-8 编码、byte[]原样、Number以"类名+值"形式(如Long1)、Date以"类名+yyyyMMddHHmmss,SSS"格式、Boolean直接 toString、实现了Serializable的对象走 Java 序列化,其他类型抛CacheException("can't convert key of class: ..."); - 最后把
keyPrefix字节与 key 字节拼接作为最终 Redis key。
RedissonCache.getCacheKey()把这个字节数组按 UTF-8 转回字符串,作为 RedissonRBucket的 bucket 名称。
RedissonCache 底层实现剖析
存储模型:RBucket + ByteArrayCodec
RedissonCache.java 继承AbstractExternalCache,内部固定使用 Redisson 的RBucket<byte[]>存储,且 Redis 编解码器固定为ByteArrayCodec.INSTANCE——也就是说 JetCache 自行负责值的序列化(通过valueEncoder/valueDecoder),Redisson 只负责字节的存取。值在写入前被包装为CacheValueHolder<V>(携带值、过期时间、访问时间等元数据),再经valueEncoder编码成byte[]。
操作方法映射
| JetCache 抽象操作 | Redisson 底层实现 |
|---|---|
do_GET | client.getBucket(key, codec).get()读取字节,解码后按holder.getExpireTime()判断是否过期,过期返回EXPIRED_WITHOUT_MSG(L109-L127) |
do_GET_ALL | client.getBuckets(codec).get(keys...)一次批量读取(L130-L166) |
do_PUT | bucket.set(encoded, expireAfterWrite, timeUnit),把 TTL 同步交给 Redis 管理(L168-L178) |
do_PUT_ALL | 通过client.createBatch()创建 RBatch,对每个 key 提交setAsync后统一batch.execute()(L180-L197) |
do_REMOVE | bucket.delete(),删除成功返回SUCCESS,否则FAIL(L199-L208) |
do_REMOVE_ALL | RBatch 批量deleteAsync()(L210-L223) |
do_PUT_IF_ABSENT | bucket.setIfAbsent(encoded, Duration),实现原子性的"不存在才写入",已存在返回EXISTS_WITHOUT_MSG(L225-L236) |
可以看出,批量读写(GET_ALL/PUT_ALL/REMOVE_ALL)都借助了 Redisson 的RBatch(Redis Pipeline)能力,一次网络往返完成多个命令。
旧值兼容:compatibleOldVal
RedissonCache还实现了一个值得注意的兼容逻辑(compatibleOldVal):当解码抛出CacheEncodeException时,会尝试读取client.getConfig().getCodec()(Redisson 客户端自身配置的编解码器)对原始字节再解码一次。若 Redisson 配置的 codec 不是ByteArrayCodec(即历史数据是用 Redisson 默认编解码器写入的),则用该 codec 的getValueDecoder().decode()还原出字节后再次走 JetCache 解码。这一机制使得 JetCache 可以读取 Redisson 客户端早期版本(未使用ByteArrayCodec)写入的数据,最多兼容一层嵌套(counter <= 1)。
其他行为
unwrap()直接抛出UnsupportedOperationException(L52-L55),即不支持解包为底层对象;- 所有操作的异常都被捕获并通过
logError记录,以CacheResult/CacheGetResult包装后返回,保证缓存故障不向上抛出导致业务中断(这符合 JetCache 一贯的"缓存失败降级"设计)。
多实例本地缓存同步:广播机制
当 JetCache 采用"本地缓存(如 LinkedHashMap)+ 远程 Redisson 缓存"的两级结构部署在多实例时,某实例更新/删除缓存后,其他实例的本地缓存仍可能残留旧数据。解决方案是启用broadcastChannel,让变更通过 Redis 发布订阅广播出去。
RedissonCacheBuilder对此有显式支持(RedissonCacheBuilder.java):
@Override public boolean supportBroadcast() { return true; } @Override public BroadcastManager createBroadcastManager(final CacheManager cacheManager) { final RedissonCacheConfig<?, ?> c = (RedissonCacheConfig<?, ?>) this.getConfig().clone(); return new RedissonBroadcastManager(cacheManager, c); }也就是说type: redisson的远程缓存天然支持广播;只要配置了broadcastChannel,JetCache 自动注册RedissonBroadcastManager参与消息收发。其实现(RedissonBroadcastManager.java)基于 Redisson 的RTopic:
startSubscribe()(L38-L49):通过client.getTopic(channel).addListener(byte[].class, ...)注册监听器,收到消息后用配置的valueDecoder解码为CacheMessage并交给processNotification处理;publish()(L70-L83):把CacheMessage用配置的valueEncoder编码后client.getTopic(channel).publish(msg)发布;close():removeListener反注册,并用ReentrantLock保证订阅/关闭操作的线程安全。
因此广播消息的编解码复用同一个RedissonCacheConfig的 valueEncoder/valueDecoder,保持与缓存值一致的序列化策略。
测试与可运行示例
仓库为 Redisson 支持提供了完整的测试与样例,可作为接入时的参考与验证:
- RedissonCacheTest.java:继承
AbstractExternalCacheTest的完整能力测试,覆盖基础 CRUD、fastjson2 keyConvertor、过期时间、LoadingCache、RefreshCache(刷新)、Kryo 序列化、keyConvertor(null)场景,以及 10 线程 3 秒并发读写测试;测试中每个 Cache 用随机数作为keyPrefix隔离数据; - RedissonBroadcastManagerTest.java:继承
AbstractBroadcastManagerTest,验证广播管理器的发布/订阅与消息处理; - RedissonStarterTest.java:通过
spring.profiles.active=redisson验证 Spring Boot starter 全链路装配,并用@CreateCache注入的 Cache 直接执行PUT断言; - RedissonExample.java:纯 Java(无 Spring)环境下 Builder API 的完整可运行示例,包含
redisson.shutdown()关闭客户端; - RedissonDemo.java:Spring Boot 示例入口,配合 application.yml 可直接运行。
小结
总结 JetCache 接入 Redisson 的三个关键认知:
- 客户端由 Spring 容器管理:Redisson 的
RedissonClient必须作为 Bean 存在,JetCache 不做连接参数解析;Spring Boot 下用redissonClient指定 Bean 名(多 Bean 时必填),连接参数交给redisson-spring-boot-starter处理; - 两种装配路径:Spring Boot 走
jetcache.starter-redisson+application.yml自动配置;普通项目走@EnableMethodCache+@Import(JetCacheBaseBeans.class)+ 手工组装GlobalCacheConfig; - 编程式创建不忘 keyPrefix:Builder API 创建
RedissonCache时,keyPrefix是必填项(源码级校验),同时推荐显式配置keyConvertor(默认 fastjson2 系)与序列化策略(默认 Java 序列化),如需多实例同步再配上broadcastChannel启用基于 RTopic 的广播机制。
- 后端
- 缓存抽象
【免费下载链接】jetcache
JetCache is a Java cache framework.
相关推荐
JetCache 集成 Redisson 实战指南:从 Spring Boot 配置到 Builder API 源码剖析
JetCache 集成 Redisson 实战指南:从 Spring Boot 配置到 Builder API 源码剖析 JetCache 从 2.7 版本开始
后端缓存抽象JetCache 入门实战指南:从创建缓存实例到 Spring Boot/非 Spring Boot 完整配置
JetCache 入门实战指南:从创建缓存实例到 Spring Boot/非 Spring Boot 完整配置 导读 本文以 JetCache 官方 Getti
后端缓存抽象Spring Boot 集成 Ehcache 缓存实战:基于 spring-boot-demo-cache-ehcache 的完整配置与源码解析
Spring Boot 集成 Ehcache 缓存实战:基于 spring boot demo cache ehcache 的完整配置与源码解析 本文以 spr
示例工程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考