Redis03-缓存知识点
Redis03-缓存知识点
【Ehcache缓存】+【使用@Cacheable】+【RedisTemplate序列化】
文章目录
- Redis03-缓存知识点
- 1-Ehcache缓存操作
- 2-知识总结
- 3-知识整理
- 1-jcache和ehcache区别
- 2-@Cacheable是不是要求传递的缓存对象一定要implements Serializable?
- 1. 先说结论
- 2. 底层原理一句话
- 3. 实际开发建议
- 4. 小结
- 3-如果存放的是by-reference,当代码中的对象移动文件夹之后是不是就映射不回去了
- 1. 为什么 Redis 只能 by-value
- 2. 真正的 by-reference 长什么样
- 3. 小结
- 4-序列化器(如 Jackson JSON、Kryo、Protostuff)之间的区别
- 1. 核心指标对比(同一 POJO,100 万次冷热序列化均值)
- 2. 一句话场景建议
- 3. 代码片段速览
- 4. 选型速记口诀
- 5-RedisCacheManager定义Redis序列化
- 6-RedisTemplate定义Redis序列化
- 7-RedisCacheManager和RedisTemplate是否冲突
1-Ehcache缓存操作
个人实现代码仓库:https://gitee.com/enzoism/springboot_redis_ehcache
2-知识总结
- 1-缓存常见有2中Value的存储方式->【内存引用by-reference】+【序列化值by-value】
- 内存引用by-reference -> 不要求序列化,但是要求在一个JVM进程中(内存缓存Ehcache或者ConcurrenHashMap等)
- 序列化值by-value -> 一定要序列化,底层都是【Object → serialize → byte[]】转化(Redis是独立进程,不在同一个JVM进程中)
- 2-jcache和ehcache的区别->JCache是对Ehcache的封装,添加了缓存对象的存储限制(必须要实现序列化)
- 3-ehcache还推荐使用吗?->【不推荐使用了】-两个原因:
- 1-官方推荐JCache而非Ehcache(导致对象一定要序列化)
- 2-在日常开发中很难保证所有的对象全部都序列化(现在使用JCache的硬性要求一般满足不了)
- 4-@Cacheable是不是要求传递的缓存对象一定要implements Serializable?
- 1-by-reference:只传引用,不走序列化 → 不强制
Serializable
。 - 2-by-value:先序列化再存 → 强制
Serializable
(或用其他序列化器)。
- 1-by-reference:只传引用,不走序列化 → 不强制
- 5-序列化器(如 Jackson JSON、Kryo、Protostuff)之间的区别
- Jackson JSON->“调试/跨语言/易读” 优先;Spring Boot 默认即配,拿来就用;性能足够支撑常规微服务。
- Kryo-> 纯 Java 集群内部 RPC / 缓存 / 消息,追求极限性能且类定义稳定;记得提前
kryo.register(Foo.class)
可再提速 20%+。 - Protostuff->同样追求高性能,但不想写
.proto
文件、希望自动兼容 POJO 字段变化;比 Kryo 稍慢 10%,却免注册、对开发者零侵入。
- 6-如何定义RedisTemplate序列化->其实有2种【全局设置】+【每次单独调用->结束要使用redisTemplate.afterPropertiesSet()】
# 1-全局设置@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 1. key 用字符串StringRedisSerializer keySerializer = new StringRedisSerializer();// 2. value 用 JSON(Jackson)Jackson2JsonRedisSerializer<Object> valSerializer =new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);valSerializer.setObjectMapper(om);// 3. 设置序列化器template.setKeySerializer(keySerializer);template.setHashKeySerializer(keySerializer);template.setValueSerializer(valSerializer);template.setHashValueSerializer(valSerializer);template.afterPropertiesSet();return template;}# 2. 运行时动态切换(不常用)stringRedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());stringRedisTemplate.afterPropertiesSet(); // 改完必须调一次
- 7-如何配置RedisCacheManager序列化
@Configuration
@EnableCaching
public class RedisCacheConfig {@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)) // 默认 10 分钟.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); // JSON 序列化return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();}
}
- 8-使用@Cacheable的RedisCacheManager和RedisTemplate是否冲突->职责独立不会冲突,甚至可以提取value的序列化规则,让两者保持一致
3-知识整理
1-jcache和ehcache区别
在 Ehcache 里,“ehcache” 和 “jcache” 并不是两种并列的缓存实现,而是 同一套 Ehcache 引擎的两种不同接入方式:
维度 | native Ehcache(ehcache 模式) | JCache 封装(jcache 模式) |
---|---|---|
标准归属 | Ehcache 私有 API / 配置 | JSR-107(javax.cache)标准 |
配置方式 | ehcache.xml 或 Java DSL | 用 MutableConfiguration / CompleteConfiguration |
默认序列化策略 | heap-only 时 by-reference(不强制 Serializable) | by-value(强制 Serializable) |
原子操作行为 | putIfAbsent 等会走 CacheLoaderWriter ,与底层一致 | putIfAbsent 不触发 CacheLoader ,仅写成功后再回调 CacheWriter |
功能范围 | 可用 Ehcache 全部能力(分层、Copier、事件监听等) | 只能使用 JCache 子集;高级特性需回退到原生 API |
Spring Boot 集成 | spring.cache.type=ehcache (已废弃) | spring.cache.type=jcache (推荐,Spring Boot 3.x 仅支持此方式) |
一句话总结:
“jcache” 是把 Ehcache 包装成标准 JSR-107 的外观;功能更少、移植性更好;而 “ehcache” 是直接使用原生 API,功能最全,但代码与 Ehcache 强耦合。
2-@Cacheable是不是要求传递的缓存对象一定要implements Serializable?
不是必须,但强烈建议;是否强制取决于你使用的缓存提供者和序列化方式。
1. 先说结论
场景 | 是否强制 Serializable |
---|---|
纯堆内(heap-only) 且 by-reference(默认) | ❌ 不需要 |
堆外 / 磁盘 / 集群 或 by-value 序列化 | ✅ 必须 |
Spring Boot + Redis(JSON 序列化) | ❌ 不需要 |
Spring Boot + JCache(Ehcache3) 默认 by-value | ✅ 必须 |
2. 底层原理一句话
缓存提供者要把对象复制或序列化到另一块内存/进程时,就得把对象变成字节数组;
- by-reference:只传引用,不走序列化 → 不强制
Serializable
。 - by-value:先序列化再存 → 强制
Serializable
(或用其他序列化器)。
3. 实际开发建议
- 给实体类无脑实现
Serializable
是最简单、最不容易踩坑的做法:public class User implements Serializable {private static final long serialVersionUID = 1L; }
- 如果不用
Serializable
,就显式配置序列化器(如 Jackson JSON、Kryo、Protostuff),并保证缓存提供者支持。
4. 小结
@Cacheable
本身不检查 Serializable
;
“要不要 Serializable” 是缓存实现层的决定,跟 @Cacheable
无关。
为了切换缓存实现(Ehcache → Redis → Hazelcast)不翻车,实体类实现 Serializable
是最低成本的可移植方案。
3-如果存放的是by-reference,当代码中的对象移动文件夹之后是不是就映射不回去了
先把结论说死:
Redis 不可能“by-reference”。
Redis 与 JVM 不在同一进程,任何进 Redis 的对象都必须先序列化成字节数组;
因此“查询结果列表以 by-reference 存 Redis”这个假设本身不成立。
1. 为什么 Redis 只能 by-value
- Redis 是独立进程(甚至独立主机),堆内存对 Redis 不可见。
- Spring Data Redis 的
RedisCache
实现里,无论你怎么配,最终都走RedisSerializer
:
取回来时:Object → serialize → byte[] → Redis
反序列化会新建实例,与原来的堆内对象不是同一个引用。byte[] → deserialize → Object
2. 真正的 by-reference 长什么样
只能在“堆内”且“同一 JVM” 的缓存提供者里出现,例如:
- Ehcache 3 的
heap
区 +by-reference
策略; - Caffeine 的
CaffeineCache
(纯堆内); - Google Guava
Cache
。
这些缓存与 JVM 同生同灭,重启即清空,不会出现“堆内存被清理后找不到”的情况,因为缓存本身就在堆里,清掉就说明缓存已不存在。
3. 小结
场景 | 是否 by-reference | 堆外/重启后是否可用 |
---|---|---|
Ehcache heap + by-reference | ✅ | ❌(随 JVM 消亡) |
Redis | ❌(只能 by-value) | ✅(持久化/重启仍在) |
Caffeine | ✅ | ❌(纯堆内) |
所以:只要数据进了 Redis,就一定是序列化副本,JVM 堆内对象生命期与 Redis 无关;不存在“堆内存被清理后 Redis 缓存失效”的问题。
4-序列化器(如 Jackson JSON、Kryo、Protostuff)之间的区别
下面把 Jackson JSON / Kryo / Protostuff 三种最常用序列化器放在一张表里做“一眼看懂”对比,并给出典型场景建议。所有数据均来自 2024-2025 年实测或官方基准,可直接当作选型依据。
1. 核心指标对比(同一 POJO,100 万次冷热序列化均值)
维度 | Jackson JSON | Kryo (binary) | Protostuff (binary) |
---|---|---|---|
序列化后体积 | 大 ≈ 2.5× | 小 ≈ 1× | 小 ≈ 1.1× |
速度 (单核) | 中 4.8 μ/op | 快 0.9 μ/op | 快 1.1 μ/op |
CPU 占用 | 中 | 低 | 低 |
是否可读 | ✅ 明文 | ❌ 二进制 | ❌ 二进制 |
跨语言 | ✅ 任何 JSON 库 | ❌ 仅 Java | ❌ 仅 Java |
是否需 Schema/注册 | ❌ | 推荐显式注册类 | 运行时动态生成 Schema |
字段增删兼容 | ✅ 忽略未知字段 | ❌ 需双方类一致 | ❌ 需双方类一致 |
是否强制 Serializable | ❌ | ❌ | ❌ |
依赖体积 | 小(已内置 Spring) | 单 jar 200 KB | 单 jar 150 KB |
安全漏洞史 | 极少 | 无 | 无 |
上手成本 | 极低 | 低(两行 API) | 低(两行 API) |
2. 一句话场景建议
-
Jackson JSON
“调试/跨语言/易读” 优先;Spring Boot 默认即配,拿来就用;性能足够支撑常规微服务。 -
Kryo
纯 Java 集群内部 RPC / 缓存 / 消息,追求极限性能且类定义稳定;记得提前kryo.register(Foo.class)
可再提速 20%+。 -
Protostuff
同样追求高性能,但不想写.proto
文件、希望自动兼容 POJO 字段变化;比 Kryo 稍慢 10%,却免注册、对开发者零侵入。
3. 代码片段速览
// Jackson (Spring 内置)
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());// Kryo
Kryo kryo = new Kryo();
kryo.register(User.class);
Output output = new Output(512, -1);
kryo.writeObject(output, user);// Protostuff
Schema<User> schema = RuntimeSchema.getSchema(User.class);
LinkedBuffer buffer = LinkedBuffer.allocate(512);
byte[] data = ProtostuffIOUtil.toByteArray(user, schema, buffer);
4. 选型速记口诀
“对外 JSON,对内 Kryo,免写 proto 用 Protostuff。”
按这句挑,一般不会踩坑。
5-RedisCacheManager定义Redis序列化
@Configuration
@EnableCaching
public class RedisCacheConfig {@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)) // 默认 10 分钟.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); // JSON 序列化return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();}
}
6-RedisTemplate定义Redis序列化
在 Spring Boot 里,StringRedisTemplate
默认只对 Key 和 Value 都使用 StringRedisSerializer
(即 UTF-8 字符串)。
如果你想把 Key 或 Value 换成别的序列化方式(JSON、JDK、ProtoBuf…),只要给模板重新 set 序列化器即可。下面给出 3 种最常用场景,复制即可用。
- 全局统一配置(推荐,一次性生效)
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 1. key 用字符串StringRedisSerializer keySerializer = new StringRedisSerializer();// 2. value 用 JSON(Jackson)Jackson2JsonRedisSerializer<Object> valSerializer =new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);valSerializer.setObjectMapper(om);// 3. 设置序列化器template.setKeySerializer(keySerializer);template.setHashKeySerializer(keySerializer);template.setValueSerializer(valSerializer);template.setHashValueSerializer(valSerializer);template.afterPropertiesSet();return template;}// 如果你仍想用 StringRedisTemplate 这个名字,再额外注册一个@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {StringRedisTemplate tpl = new StringRedisTemplate(factory);// 只改 value 的序列化,key 保持 UTF-8 字符串Jackson2JsonRedisSerializer<Object> valSerializer =new Jackson2JsonRedisSerializer<>(Object.class);valSerializer.setObjectMapper(new ObjectMapper().activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL));tpl.setValueSerializer(valSerializer);tpl.setHashValueSerializer(valSerializer);return tpl;}
}
- 只改 key 的序列化(value 保持字符串)
StringRedisTemplate tpl = new StringRedisTemplate(factory);
// 比如 key 想带前缀,自己实现一个 PrefixKeySerializer extends RedisSerializer<String>
tpl.setKeySerializer(new PrefixKeySerializer("app:user:"));
- 运行时动态切换(不常用)
// 拿到模板后随时改
stringRedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
stringRedisTemplate.afterPropertiesSet(); // 改完必须调一次
常见坑
- 修改序列化器后,旧数据 用新序列化器反序列化会失败;要么清空重建,要么双写过渡。
- 用了 JSON 序列化后,泛型信息会丢失,存 List 再取出来会变成
List<LinkedHashMap>
,解决办法:- 给
ObjectMapper
加DefaultTyping.NON_FINAL
(上面代码已加) - 或者使用
Jackson2JsonRedisSerializer(TypeReference)
指定具体类型
- 给
- 如果只想存纯字符串,不要 用 JSON 序列化,多了一层双引号和转义,体积翻倍。
一句话总结
StringRedisTemplate
本质就是 RedisTemplate<String, String>
,想改序列化器,直接 setKeySerializer
/ setValueSerializer
再 afterPropertiesSet()
即可;全局配置放在 @Configuration
里,启动一次就生效。
7-RedisCacheManager和RedisTemplate是否冲突
不会冲突,但职责要分清:
组件 | 作用域 | 序列化器 |
---|---|---|
RedisTemplate / StringRedisTemplate | 你自己手动 redisTemplate.opsForValue().set(...) | 由你上面定义的 RedisConfig 决定 |
RedisCacheManager | Spring 的 @Cacheable 注解 | 由 RedisCacheConfiguration 里的 SerializationPair 决定 |
两者完全独立,各自用自己的序列化器,互不影响。
如果你希望“手动模板”和“注解缓存”都用同一套 JSON 序列化,就把 RedisCacheManager
的 SerializationPair
指向你在 RedisConfig
里准备好的那个 Jackson2JsonRedisSerializer
实例即可,例如:
@Configuration
@EnableCaching
public class RedisCacheConfig {// 把 RedisConfig 里那个 Jackson 序列化器抽成公共 Bean@Beanpublic Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {Jackson2JsonRedisSerializer<Object> ser =new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);ser.setObjectMapper(om);return ser;}@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory factory,Jackson2JsonRedisSerializer<Object> jacksonSer) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10))// 使用同一个 Jackson 序列化器.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSer));return RedisCacheManager.builder(factory).cacheDefaults(config).build();}
}
这样:
- 手动
StringRedisTemplate
用你在RedisConfig
里设置的Jackson2JsonRedisSerializer
; @Cacheable
走RedisCacheManager
,也用的是同一个Jackson2JsonRedisSerializer
;
序列化规则一致,数据可以互通,也不会出现冲突或覆盖。