Redis03-缓存知识点

Redis03-缓存知识点

【Ehcache缓存】+【使用@Cacheable】+【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(或用其他序列化器)。
  • 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()】
java 复制代码
	  # 1-全局设置
    @Bean
    public 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序列化
java 复制代码
@Configuration
@EnableCaching
public class RedisCacheConfig {

    @Bean
    public 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 是最简单、最不容易踩坑的做法:

    java 复制代码
    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. 代码片段速览

java 复制代码
// 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 {

    @Bean
    public 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 默认只对 KeyValue 都使用 StringRedisSerializer(即 UTF-8 字符串)。

如果你想把 Key 或 Value 换成别的序列化方式(JSON、JDK、ProtoBuf...),只要给模板重新 set 序列化器即可。下面给出 3 种最常用场景,复制即可用。


  1. 全局统一配置(推荐,一次性生效)
java 复制代码
@Configuration
public class RedisConfig {

    @Bean
    public 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 这个名字,再额外注册一个
    @Bean
    public 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;
    }
}

  1. 只改 key 的序列化(value 保持字符串)
java 复制代码
StringRedisTemplate tpl = new StringRedisTemplate(factory);
// 比如 key 想带前缀,自己实现一个 PrefixKeySerializer extends RedisSerializer<String>
tpl.setKeySerializer(new PrefixKeySerializer("app:user:"));

  1. 运行时动态切换(不常用)
java 复制代码
// 拿到模板后随时改
stringRedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
stringRedisTemplate.afterPropertiesSet();   // 改完必须调一次

常见坑

  1. 修改序列化器后,旧数据 用新序列化器反序列化会失败;要么清空重建,要么双写过渡。
  2. 用了 JSON 序列化后,泛型信息会丢失,存 List 再取出来会变成 List<LinkedHashMap>,解决办法:
    • ObjectMapperDefaultTyping.NON_FINAL(上面代码已加)
    • 或者使用 Jackson2JsonRedisSerializer(TypeReference) 指定具体类型
  3. 如果只想存纯字符串,不要 用 JSON 序列化,多了一层双引号和转义,体积翻倍。

一句话总结
StringRedisTemplate 本质就是 RedisTemplate<String, String>,想改序列化器,直接 setKeySerializer / setValueSerializerafterPropertiesSet() 即可;全局配置放在 @Configuration 里,启动一次就生效。


7-RedisCacheManager和RedisTemplate是否冲突

不会冲突,但职责要分清

组件 作用域 序列化器
RedisTemplate / StringRedisTemplate 你自己手动 redisTemplate.opsForValue().set(...) 由你上面定义的 RedisConfig 决定
RedisCacheManager Spring 的 @Cacheable 注解 RedisCacheConfiguration 里的 SerializationPair 决定

两者完全独立,各自用自己的序列化器,互不影响


如果你希望"手动模板"和"注解缓存"都用同一套 JSON 序列化 ,就把 RedisCacheManagerSerializationPair 指向你在 RedisConfig 里准备好的那个 Jackson2JsonRedisSerializer 实例即可,例如:

java 复制代码
@Configuration
@EnableCaching
public class RedisCacheConfig {

    // 把 RedisConfig 里那个 Jackson 序列化器抽成公共 Bean
    @Bean
    public 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;
    }

    @Bean
    public 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
  • @CacheableRedisCacheManager,也用的是同一个 Jackson2JsonRedisSerializer

序列化规则一致,数据可以互通,也不会出现冲突或覆盖。


相关推荐
自信的小螺丝钉8 小时前
Leetcode 146. LRU 缓存 哈希表 + 双向链表
leetcode·缓存·散列表
ANYOLY10 小时前
Redis 面试宝典
数据库·redis·面试
洲覆12 小时前
Redis 核心数据类型:从命令、结构到实战应用
服务器·数据库·redis·缓存
山海不说话13 小时前
Java后端面经(八股——Redis)
java·开发语言·redis
muxin-始终如一14 小时前
MySQL与Redis面试问题详解
数据库·redis·mysql
沧澜sincerely16 小时前
Redis 键空间 & 五大类型
java·redis·docker
不剪发的Tony老师19 小时前
RedisFront:一款免费开源的跨平台Redis客户端工具
数据库·redis·redisfront
麦兜*19 小时前
Redis数据迁移实战:从自建到云托管(阿里云/腾讯云)的平滑过渡
java·spring boot·redis·spring·spring cloud·阿里云·腾讯云
RoboWizard19 小时前
传输无界 金士顿双接口U盘上新抽电脑
运维·人工智能·缓存·电脑·金士顿