
项目标题配着一堆热搜词不用想也知道又是Spring Boot里那点事。说实话Redis序列化配置这玩意儿几乎每个用Spring Boot接Redis的项目都会碰到基本逃不掉。你现在看的这篇就是把我实际踩坑和调优的过程捋了一遍核心就解决一件事为什么Redis里的key是一堆\xAC\xED\x00\x05t\x00开头的东西value存进去是JSON字符串读出来却是LinkedHashMap或者直接类型转换报错。这篇内容不挑基础哪怕你刚把Redis装好、正准备在项目里配置也能顺着步骤把问题理清楚。已经能正常用的建议重点看看后面排查部分那部分是常规文档里不太会写的。1. 为什么Spring Boot项目要专门配Redis序列化1.1 默认配置的“隐藏坑”先说结论Spring Boot的Redis自动配置里RedisTemplate默认用的是JdkSerializationRedisSerializer。这个序列化器是Java原生序列化方案的封装它能工作但问题也最明显。我用一个最简单的例子说明。你往Redis里塞一个对象User user new User(zhangsan, 25); redisTemplate.opsForValue().set(user:1, user);用Redis Desktop Manager连上去看你会发现这个user:1的key不是干净的字符串而是一长串带\xAC\xED\x00\x05前缀的二进制数据。value更是惨不忍睹一坨坨的转义字符看着像乱码但又不是完全乱码。这种\xAC\xED是JDK序列化协议的特殊标记。Java序列化会给每个对象附加类信息、序列化版本号等元数据再加上Spring容器对key和value的包装造成三个直接后果。第一可读性为零。运维同事查数据、排查缓存命中问题打开可视化工具一看全是天书根本没法人工确认缓存里到底是什么内容。第二空间浪费严重。Java序列化后的数据体积比原始JSON大好几倍。我做过一次实测一个只有100个字段的订单对象JSON大概是2KBJdk序列化之后能到6~8KB。缓存数据量大时这在Redis内存占用和带宽消耗上是非常明显的浪费。第三反序列化脆弱。Java序列化和反序列化依赖类的serialVersionUID。类结构一变比如增加一个字段、调整一个字段类型老缓存数据就可能反序列化失败整个缓存直接失效。线上环境里改个实体类导致大面积缓存报错十有八九就是这个原因。1.2 为什么要用JSON序列化替代JDK序列化把序列化方案从JDK换成JSON几乎是所有Spring Boot项目规范里的标配动作。JSON序列化本质是把对象转成字符串key和value都是可读的字符串格式。这个转变最直接的好处是开发者能直观看到缓存内容省去了“黑盒调试”的痛苦。另一个容易被忽略的好处是跨语言。你用Java写服务后来有个Go或Python的服务也需要读这份缓存JSON字符串双方都能解析JDK序列化产物对方根本读不了。在微服务架构里这个价值比在单体应用里更明显。不过JSON方案也有它自己的问题最大的坑就是类型信息丢失。JSON只有字符串、数字、数组、对象这几种结构没有Java的类信息。你从一个对象序列化成{name:zhangsan,age:25}反序列化时如果不知道目标类型Jackson只能把它还原成LinkedHashMap。这就是很多人在项目里遇到“取出来是LinkedHashMap强转成User直接ClassCastException”的根本原因。要解决这个问题业内常用两种思路。第一种是存储时把类型信息一起序列化进去。比如用GenericJackson2JsonRedisSerializer它在序列化时会加一个class字段记录全类名反序列化时根据这个字段还原真实类型。优点是完全自动缺点是数据里多一段类型描述体积会大一点。第二种是RedisTemplate统一只处理指定类型。key用Stringvalue用JSON字符串业务层自己负责对象和JSON的转换。这种方式数据干净、类型可控但需要你在读写时多写几行转换代码。两种方案没有绝对优劣取决于项目团队习惯。但无论选哪种都需要专门写配置类而不是依赖Spring Boot的默认行为。这就是“Redis序列化配置”这个操作存在的全部意义。1.3 什么时候需要动序列化配置不是所有用Redis的项目都必须改序列化配置但下面这三种场景建议一定要配。场景一把Java对象存进value。默认JDK序列化虽然能存但后续问题多前文已经分析过。场景二多个服务共享Redis缓存。比如两个Java服务、一个Python服务都要访问同一批key这种情况必须用JSON或其他跨语言格式。场景三需要排查缓存数据或者做数据迁移。二进制格式的数据没法直接看、没法直接导入导出统一成JSON之后这些操作都变得很方便。如果你只在Redis里存原子计数、简单字符串、临时token这类几类固定数据用Spring Boot默认配置也许够用但依然没法看数据、没法调格式长期看并不划算。所以我的建议是新项目一律从第一天就配好JSON序列化老项目也找时间把序列化统一掉。这事越早做成本越低等到缓存里堆了几百万个JDK序列化的key再想切迁移过程会遇到不小的麻烦。2. 序列化器选型四种方案的对与错2.1 常见的序列化器横向对比配置Redis序列化第一步是选序列化器。Spring Data Redis里常见的有四种各有利弊。JdkSerializationRedisSerializer使用Java原生序列化兼容性好但数据可读性差、体积大、耦合Java类结构上一节已经详细吐槽过不建议日常业务使用。StringRedisSerializer只处理String类型把数据按UTF-8编码存进去。它的典型用途就是如果你确定value是String用它保证可视化和轻量。很多团队甚至把key统一成StringRedisSerializervalue用JSON方案。Jackson2JsonRedisSerializer把对象序列化成JSON字符串反序列化时能通过指定目标类型来还原。Spring Boot官方文档推荐这个但它有一点让很多开发者踩坑构造时需要告诉它目标Object类型比较麻烦。而且泛型容器类如List 在反序列化时丢类型处理起来要动不少脑筋。GenericJackson2JsonRedisSerializer在普通Jackson序列化基础上加了一个class字段来保存原始类名从JSON串还原时可以自动找到类并完成反序列化。直接new就能用源代码里封装好了大部分常见的坑是当前社区实践最推荐方案的候选。这四个方案放一起做对比更直观序列化器数据格式可读性存储开销类型还原使用复杂度JdkSerializationRedisSerializer二进制JDK格式差最高自动最低StringRedisSerializer字符串好低不适用String最低Jackson2JsonRedisSerializerJSON字符串好中需指定目标类型中GenericJackson2JsonRedisSerializerJSON字符串含class好中高自动还原低2.2 什么项目选什么方案我的实践经验是9成项目可以直接选GenericJackson2JsonRedisSerializer。它是“能自动还原类型”和“使用门槛低”这两个点上的最优解不需要在业务代码里反复写类型转换。一个典型的Spring Boot配置类用这个方案40行代码以内就能搞定后文会给完整代码。对存储在Redis里的数据格式有洁癖的团队可以用StringRedisTemplate 手动JSON转换。这种模式下Redis里存的全是纯JSON字符串没有class这种额外字段数据最干净也能最大限度减小存储体积。代价是业务代码里每次读写缓存都要手动做序列化和反序列化代码量会多一些。如果项目里Redis使用量不大只存少量String类型和计数器直接用StringRedisTemplate就够。但注意StringRedisTemplate的value序列化器默认就是StringRedisSerializer平时存普通字符串、数字、JSON字符串都没问题只是不能直接存Java对象。2.3 关键原则key和value分开配置配置的时候很多人一开始会直接把RedisTemplate的key和value序列化器都设成同一个。这个习惯要改掉。规范做法是key用StringRedisSerializervalue根据实际需求选择JSON方案。原因有两个。Redis的key通常是user:1、order:20240613:10086这种业务前缀加标识组成的字符串用String保存又省空间又直观。而value往往是对象、列表、Map需要JSON这类能表达结构化的格式。把key也序列化成JSON反而多此一举。另外很多Redis命令如scan、keys、type对字符串key支持更好。如果你把key用JDK序列化成二进制排查问题时在命令行和可视化工具里都不方便操作。所以key统一Stringvalue按需选JSON这个原则值得坚持。3. 可直接落地的配置代码与完整实操过程3.1 自己写配置类的完整代码下面是一个可以在Spring Boot项目2.x或3.x均适用中直接使用的配置类。我以GenericJackson2JsonRedisSerializer为例因为它在类型还原和易用性之间平衡得最好。import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import com.fasterxml.jackson.databind.jsontype.DefaultTyping; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(connectionFactory); // key 统一用 StringRedisSerializer StringRedisSerializer stringSerializer new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); // value 使用 GenericJackson2JsonRedisSerializer核心是自动保存类型信息 ObjectMapper om new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(om); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); // 初始化模板使设置生效 template.afterPropertiesSet(); return template; } }这里出现了我见过最多人困惑的afterPropertiesSet()。这句的作用是让RedisTemplate内部的RedisSerializer相关属性完成初始化配置不调用它某些版本下序列化设置可能没真正生效运行时会走默认的JDK序列化。新版本的Spring Data Redis里RedisTemplate构造后很多操作会懒初始化稍微严谨一点把这句加上能避免一类隐性问题。3.2 配置过程中的参数选择说明上面代码里比较关键的是ObjectMapper和activateDefaultTyping这一段。很多人在此纠结我拆开讲清楚。om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)是让Jackson能访问所有属性包括private字段、getter、setter等。如果一个对象属性是private的没有这个设置Jackson可能只序列化public字段或者直接报错。activateDefaultTyping(LaissezFaireSubTypeValidator.instance, DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)是核心中的核心。它让Jackson在序列化对象时自动附加一个类型标识字段。默认情况下一个User对象加上这个配置后序列化结果会带class字段{ class: com.example.entity.User, name: zhangsan, age: 25 }反序列化时Jackson看到class就知道该把它还原成com.example.entity.User而不是LinkedHashMap。NON_FINAL表示只对非final类起作用因为final类没有多态性没必要加类型信息。PROPERTY表示把类型信息作为一个普通JSON属性存进去。有人看到class字段会觉得数据不干净如果非常介意可以用Jackson2JsonRedisSerializer并手动指定类型。比如专门做一个User的Redis操作组件给这个组件明确指定目标类型为User.class这样JSON里就不会有class字段。但对应的代价就是每加一种对象类型都要写一套代码业务一多就变成了负担。两害相权我建议直接用Generic方案。3.3 验证配置是否生效的完整步骤代码写完别急着上线先跑一个验证流程。这种验证流程我每次在新项目里都会做几十秒就能确认问题。重启Spring Boot应用用一个简单的测试接口或者单元测试执行下面这三步操作。第一步写入数据Autowired private RedisTemplateString, Object redisTemplate; User user new User(lisi, 30); redisTemplate.opsForValue().set(user:test:1, user);第二步读取数据并强转类型User cachedUser (User) redisTemplate.opsForValue().get(user:test:1); System.out.println(cachedUser.getName());如果配置成功这段代码会正常打印出lisi。如果配置失败或者没有生效这里大概率抛ClassCastException因为你拿到的是一个LinkedHashMap强转成User当然报错。很多新手一看到这个异常就去查业务代码其实根因就是序列化配置没到位。第三步打开Redis Desktop Manager或任何可视化工具检查Redis里的user:test:1。正常应该看到如下内容{ class: com.example.entity.User, name: lisi, age: 30 }key显示为user:test:1纯字符串不再有\xAC\xED前缀。到这里就可以确认整套配置已经生效。3.4 Hash类型和List类型的序列化验证很多业务会用到opsForHash()和opsForList()这两个操作类型也值得单独验证。我见过不少项目RedisTemplate的配置没问题但往Hash里塞对象时用的是默认JDK序列化。原因很简单Hash内部的HashValue序列化器如果没单独设置它会退回默认配置。一个完整的验证方式是分别执行下面两段代码// Hash操作 redisTemplate.opsForHash().put(hash:test, field1, new User(wangwu, 28)); Object hashValue redisTemplate.opsForHash().get(hash:test, field1); System.out.println(hashValue); // List操作 redisTemplate.opsForList().leftPush(list:test, new User(zhaoliu, 22)); Object listValue redisTemplate.opsForList().leftPop(list:test); System.out.println(listValue);两个操作读出来的对象字段都正常说明HashValueSerializer和List结构的内部序列化也都走的是JSON方案。如果HashValue读出来是乱码或类型转换失败先检查配置类里是否写了template.setHashValueSerializer(jsonSerializer)。4. 序列化细节JSON序列化器的几个隐藏坑4.1 LocalDateTime等Java时间类型的处理GenericJackson2JsonRedisSerializer默认对Java 8的时间类型支持不是那么完美。把LocalDateTime塞进Redis有可能读出来报错或者格式跟预期不符。这个问题在Spring Boot 2.x时代尤其明显Spring Boot 3.x自带jackson-datatype-jsr310模块后情况好一些但为了稳妥建议显式处理。配置类里加两行代码om.registerModule(new JavaTimeModule()); om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);JavaTimeModule是Jackson处理Java 8时间类型的官方模块。WRITE_DATES_AS_TIMESTAMPS这个配置控制的是时间输出格式关闭它之后LocalDateTime序列化结果是2024-06-13T10:15:30而不是一串数字数组。数字数组是Jackson的默认表现在Redis里看到它基本没法人工阅读也影响后续解析。在实际项目中我强烈建议把这一段直接加到ObjectMapper的初始化里防患于未然。4.2 泛型信息在反序列化时为什么会丢GenericJackson2JsonRedisSerializer解决了普通对象的类型还原但对复杂的泛型类型比如ResultListUser这种包装结构它依然有可能出问题。原因在于Jackson默认使用运行时类型信息做反序列化而Java泛型存在类型擦除运行时拿不到ListUser里面的User类型参数。比如定义了这样一个包装类public class ResultT { private int code; private T data; // getter/setter }存的时候是ResultListUser读出来的时候data字段就很可能被还原成ListLinkedHashMap而不是ListUser。很多人到这一步会觉得很邪门单独存User没问题一包上泛型就出问题。这不是配置错了是泛型信息的客观限制。解决方案有几种。最简单的避免把复杂泛型对象直接塞进Redis在业务层先把数据转换成一个具体的、不含泛型的DTO。其次是使用TypeReference手动指定反序列化类型ResultListUser result objectMapper.readValue(json, new TypeReferenceResultListUser() {});但这种做法意味着你要拿到原始JSON再手动处理自动化的RedisTemplate流程帮不上忙。所以我的建议是对于强类型的核心业务缓存尽量用具体的、不含泛型继承的模型类不给序列化器添乱对于确实需要泛型的场景走TypeReference手动反序列化。4.3 Long类型精度丢失这个坑比较隐蔽但一旦踩中就是线上事故级别。Jackson在处理Java的Long类型时会把它序列化成JSON数字。如果这个数字超过JavaScript的Number.MAX_SAFE_INTEGER9007199254740991前端JavaScript解析时精度会直接丢失。典型场景是雪花算法生成的ID。一个19位的大整数比如1752866536941240322在Redis里被正确存成了这个数字前端一读可能就从末尾悄悄变了数值。虽然Spring Boot后端之间互相读不会出问题但一旦有前端参与或者通过接口把Redis数据传到浏览器端就会出现ID对不上、详情查不到这类诡异问题。针对这个问题需要在ObjectMapper里把Long和Long的包装类型序列化成字符串SimpleModule longModule new SimpleModule(); longModule.addSerializer(Long.class, ToStringSerializer.instance); longModule.addSerializer(Long.TYPE, ToStringSerializer.instance); om.registerModule(longModule);序列化结果是1752866536941240322带引号JSON解析时按字符串处理精度保留。要注意这个处理影响面比较大项目里所有Long类型字段都会变成字符串。如果前端接口契约里对Long类型有强类型要求建议只在Redis专用的ObjectMapper上做这个配置不要污染全局的Jackson配置。4.4 反序列化时属性多余的兼容处理项目迭代过程中Redis里旧数据的字段结构往往和新版本实体类不一致。比如旧版本User有phone字段新版本删掉了反序列化时Jackson默认报UnrecognizedPropertyException导致整个缓存读取失败。很多线上缓存故障都源于这类前后端模型不一致的问题。解决办法是在ObjectMapper上开启FAIL_ON_UNKNOWN_PROPERTIES关闭om.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);加上这行之后JSON里有Class里没有的属性Jackson会静默忽略不会报错。这个配置对缓存场景特别重要因为缓存数据通常是长期存在的而代码是在持续演进的。新代码读旧数据、旧代码读新数据都靠这个配置兜底。但注意这是兜底手段不是数据结构混乱的借口。真正常态的规范是向前兼容新增字段要有默认值删除字段要经过评估。只是线上环境变化多加这行配置能让你的服务韧性更强一点不至于因为一个多余的字段就全缓存不可用。5. 高频报错与排查技巧实录5.1 常见报错速查表把这几年遇到的和序列化相关的高频报错整理成了一张表方便你按图索骥。报错信息可能原因解决方法key显示为\xAC\xED\x00\x05t\x00key序列化器未配置走了默认JDK设置keySerializer为StringRedisSerializer读出来是LinkedHashMap强转ClassCastExceptionvalue反序列化丢失类型信息改用GenericJackson2JsonRedisSerializer反序列化报InvalidDefinitionException或cannot construct instance对象缺少无参构造函数补齐无参构造反序列化报UnrecognizedPropertyExceptionJSON中有多余字段关闭FAIL_ON_UNKNOWN_PROPERTIESLocalDateTime反序列化报错缺少JavaTimeModule注册JavaTimeModule泛型List内元素全部变成LinkedHashMapJava泛型擦除导致类型丢失使用TypeReference手动指定类型Redis Desktop Manager里value是数字数组WRITE_DATES_AS_TIMESTAMPS未关闭关闭该序列化特性Spring Boot报java.lang.ClassNotFoundException反序列化的类不在classpath检查实体类依赖或清理旧缓存数据5.2 排查思路先分清楚是读还是写的问题遇到序列化相关报错我习惯先判断是写入阶段出错还是读取阶段出错这样能缩小排查范围。写入阶段报错通常在set或leftPush那一行。常见的如Jackson无法处理某些类型、循环引用检测触发JsonMappingException等。排查时先看报错堆栈里是不是有sun.reflect、jackson相关的字样如果有基本可以定位到某个实体类或者字段类型有问题。最有效的做法是把那个对象单独拿出来在单元测试里序列化一次看具体在哪个字段上失败。读取阶段报错通常在get那一行异常类型多半是ClassCastException或JsonProcessingException。ClassCastException照5.1的表排查类型丢失JsonProcessingException则要关注具体是哪个字段解析失败。有一个实用的小技巧遇到反序列化报错但看不到实际JSON内容时可以先用Redis Desktop Manager或者redis-cli把value取出来粘贴到任意JSON在线解析工具里人工观察结构基本一眼就能看出是字段类型对不上、嵌套层级不对还是存在多余引号。5.3 实体类的无参构造函数问题这个坑简直太常见了。Jackson进行反序列化时需要先实例化目标对象。如果你的实体类只有一个带参构造函数Jackson就无法实例化直接报异常。很多项目的实体类都用了Lombok的Data注解很遗憾Data生成的是全参构造函数不会生成无参构造。我见到过的一个线下经典问题是写入一切正常一读就报InvalidDefinitionException: Cannot construct instance of com.example.entity.User (no Creators, like default constructor, exist)异常信息看起来是Bean类有问题。解决方式不复杂在实体类上补一个无参构造即可。用Lombok的话直接加NoArgsConstructor注解又快又明确。强烈建议所有要存进Redis的对象实体类统一加上NoArgsConstructor甚至同时加AllArgsConstructor。这是成本最低、效果最好的防御性写法不管序列化方案怎么变都不会因为构造函数问题翻车。5.4 Redis Desktop Manager查看数据时的技巧排查Redis序列化问题时可视化工具的使用方式也有些讲究。很多同事直接双击一个key发现value栏是个大文本或者转义的字符串习惯性认为“数据坏了”。其实数据可能没坏只是工具的JSON渲染模式没开。在Redis Desktop Manager里读取value之后默认按字符串显示。如果你看到的是{\name\:\zhangsan\}这种带转义的文本不要慌把RDM的JSON格式转换功能打开。它本身内置了对JSON串的解析识别功能展开那份字符串内部结构的方式也很简单不太需要反复粘贴到外部工具解析。另外也不要忽略一个操作细节写入后立刻读取相同key若关闭应用再次能读到一致内容说明数据已经持久化落盘。序列化配置不同往往影响着读取的阶段你可以通过重启应用或者直接在redis-cli里get key对比不同阶段看到的数据快速判断问题出在应用启动加载还是序列化器本身。5.5 配置不生效的排查顺序有时候代码明明按照规范写了序列化却仍然不对特别是看起来配置类完全正确但没有作用。按经验先从快变量排查起。检查是否是多个RedisTemplate的Bean做了覆盖。Spring容器里如果存在多个同名或同优先级的RedisTemplate Bean某个不合适的配置可能会覆盖你的定制Bean导致白写。最稳妥的方式是用Primary标注你的自定义配置Bean或者干脆在注入的地方用Qualifier明确指定使用哪一个。还有一种容易被忽略的情况项目里如果还有RedisCacheManager它的默认序列化配置和RedisTemplate是独立的两者互不干扰。缓存注解如Cacheable走的是CacheManager的序列化单独配置RedisTemplate不会影响它。如果你的报错发生在那批加了缓存注解的方法上必须要去配置RedisCacheConfiguration否则单独配RedisTemplate解决不了问题。RedisCacheConfiguration的典型写法是Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(factory).cacheDefaults(config).build(); }两个口径都要配。一个是给RedisTemplate手写操作用的一个是给注解缓存用的漏掉哪一个都会造成“有的缓存正常有的缓存一直乱码”的非常迷惑的现象。6. 实际经验配置过一次之后后续还要注意什么6.1 项目里最好统一使用同一套序列化方案配置完成之后最怕什么最怕大家各写各的有的地方用RedisTemplate有的地方用StringRedisTemplate还有的直接裸写redis-cli脚本。一段时间后Redis里的数据就会出现一部分是JSON、一部分是二进制、一部分是纯字符串的情况排查时无从下手。我的习惯是在配置类旁边加一个简短的说明注释并且把与Redis序列化相关的约定写进团队开发规范文档。约定很简单key一律用StringRedisSerializer序列化的字符串value一律用配置好的JSON序列化器StringRedisTemplate只允许用来存简单字符串类型。这个约定成本为零但能保证Redis里的所有key格式一致、所有value的格式基线一致。6.2 数据迁移时如何平滑切换序列化方案还有一个问题值得单独提一句如果项目跑得很好Redis里已经堆积了大量旧JDK序列化的key怎么平滑切换成JSON序列化直接把配置改了的话老数据全部读不了只能眼睁睁让缓存穿透到数据库这在并发量大的系统里可能导致数据库压力瞬间飙升。稳妥的做法分三步走。先在低峰期把配置改好并发布保留读老数据的兼容逻辑。读取时先尝试按新JSON格式反序列化失败就按旧JDK格式尝试双读策略挡住旧数据。第二步是逐步重建缓存。给缓存key加版本号比如从user:1升级成user:v2:1或者靠TTL自然过期让旧数据逐步失效。第三步等老key基本过期后删掉兼容逻辑完成彻底切换。这个过程说起来简单但每一步都要配合监控数据去判断推进节奏实际上很考验团队的工程习惯。6.3 序列化配置与Redis内存优化的关系最后说一个容易被忽视但又实际影响成本的细节。同样的业务对象JDK序列化存出来的体积可能是JSON序列化的3倍以上。线上缓存数据量大时这一个序列化器的选择就会直接反映在Redis内存占用上。有个真实例子。一个电商项目订单对象缓存量约500万key每个订单对象JDK序列化后平均3.2KB切到JSON后平均1.4KB内存占用从16GB直接降到7GB左右。不只是省了内存还降低了内存淘汰带来的缓存命中率波动。所以把Redis序列化规范做对不只是代码层面的事它直接呼应了资源成本这一层。序列化配置这件事从写第一版配置到今天我最大的感受就是它本身不难但绊倒人的全是小细节。类型丢失、时间格式、泛型擦除、CacheManager独立配置绝大多数问题不是理解不了而是没提前想到。希望这篇内容能帮你把该避的坑都绕过去。如果你正在做Redis序列化配置照着第3节的代码跑通一遍再回来看第5节的排查表剩下的问题多半就能自己在几分钟内定位出来了。