Redis 序列化问题分析与解决方案

目录

引言

一、JDK 序列化

JDK 序列化存在的四大问题

序列化后数据体积大

解决方案:自定义序列化配置

方案一:GenericJackson2JsonRedisSerializer

潜在问题

方案二:Jackson2JsonRedisSerializer

总结

1. 默认 JDK 序列化的问题

2. 解决方案:自定义序列化配置

方案一:GenericJackson2JsonRedisSerializer

方案二:Jackson2JsonRedisSerializer

3. 关键对比与选型建议


引言

一、JDK 序列化

我们在使用spring-data-redis时可以直接注入RedisTemplate对redis进行操作:

@Autowired
private RedisTemplate redisTemplate;
    
@Test
void testRedisTemplate() {
    redisTemplate.opsForValue().set("test:1","hello");
}

但是数据库中的结果是这样的:

之所以产生以下结果是因为:在java中,使用RedisTemplate但是没有配置序列化器的话,会默认使用JDKSerializationRedisSerializer对当前的key和value进行序列化,而序列化的结果就是我们所看到的形式。JDKSerializationRedisSerializer进行序列化会把java对象转成为字节流,字节流以16进制形式存储在redis中,从而出现了\xac\xed\x00\x05 这样的内容。\xac\xed是用来标识这是java序列化后的字节流。

JDK 序列化存在的四大问题
  1. 可读性差
    数据以二进制形式存储,无法直观阅读键值内容。

  2. 序列化后数据体积大

    Java 原生序列化会包含大量元数据,像类的完整路径、版本号等,这会使序列化后的数据体积大幅增加。在存储大量数据时,会占用更多 Redis 内存空间。

  3.  兼容性问题

    JDK序列化 依赖 Java 类的结构,若类的结构发生变化(如添加、删除字段,修改字段类型等),可能导致反序列化失败。此外,不同 Java 版本的序列化机制可能存在差异,跨版本使用时也容易出现兼容性问题。

  4. 跨语言支持差

    JDK序列化 是基于 Java 语言的,序列化后的数据只能在 Java 环境中进行反序列化。如果系统涉及多种编程语言,使用该序列化器会导致数据无法在不同语言间共享和交互。


解决方案:自定义序列化配置

为避免上述问题,我们需要配置序列化器,而配置序列化器主要有两种方法

方案一:GenericJackson2JsonRedisSerializer

1.创建并配置一个RedisTemplate 的bean 在该bean中自定义序列化器

而在自定义序列化器的过程中我们会用到两种序列化方式

1.RedisSerializer.string() 

由spring data redis提供,用于返回StringRedisSerializer实例StringRedisSerializer 基于 UTF-8 编码实现字符串和字节数组之间的转换。在序列化过程中,将java字符串按UTF-8编码格式序列化成字节数组形式。这是因为redis存储数据是以字节形式存在,除此之外很多存储系统(如数据库,文件系统)都是以字节形式存储数据,所以在使用数据的持久化储存时需要把java字符串类型序列化成字节数组形式。

2.GenericJackson2JsonRedisSerializer()

由spring data redis提供,基于Jackson库实现,用语将java对象序列化成json形式的字节数组,以及将json 格式的字节数组反序列化为 Java 对象。

所以我们分析一下,我们要将string类型的key和hashkey使用StringRedisSerializer进行序列化,而可能是java对象的value使用GenericJackson2JsonRedisSerializer进行序列化。

配置方法

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
​
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
​
        template.setKeySerializer(RedisSerializer.string());
        template.setValueSerializer(jsonSerializer);
​
        template.setHashKeySerializer(RedisSerializer.string());
        template.setHashValueSerializer(jsonSerializer);
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

存储形式

{
  "@class": "com.ymh.pojo.entity.User",
  "id": 1,
  "name": "zhangsan",
  "age": 20
}

现在实体类和键名都是字节数组形式,不再是十六进制字节流。解决了JDKSerializationRedisSerializer序列化后可读性差,跨语言支持差的问题。

但是我们在redis中看到了"@class": "com.ymh.pojo.entity.User"这样的内容,这是由于

GenericJackson2JsonRedisSerializer 在序列化后产生的用于辅助GenericJackson2JsonRedisSerializer 进行反序列化操作的字段。在反序列化时jackson库会读取@class字段的值也就是类的全限定类名,并找到对应路径下的类,将json数据反序列化成类对应的java对象。

潜在问题
  • 数据冗余@class 字段会增加存储数据的体积,特别是在存储大量数据时,会占用更多的 Redis 内存空间。

  • 类路径变更问题:如果 Java 类的包名、类名发生变化,或者类被移动到其他目录中,反序列化时由于 @class 记录的类路径失效,会导致反序列化失败。

    基于此我们来讲讲java对象进行序列化时,可以用到的另一种序列化器的配置。


    方案二:Jackson2JsonRedisSerializer

    Jackson2JsonRedisSerializer由spring data redis提供,用于将java对象序列化成为json格式的字节数组,并在反序列化时将json字节数组反序列化成java对象。但与GenericJackson2JsonRedisSerializer不同,Jackson2JsonRedisSerializer不会在redis中添加@class字段。

    具体配置:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
​
        Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
​
        template.setKeySerializer(RedisSerializer.string());
        template.setValueSerializer(jsonSerializer);
        template.setHashKeySerializer(RedisSerializer.string());
        template.setHashValueSerializer(jsonSerializer);
​
        template.afterPropertiesSet();
        return template;
    }
}

储存形式:

{
  "id": 1,
  "name": "lisi",
  "age": 20
}

可以看到redis中不仅不存在JDKSerializationRedisSerializer序列化产生的可读性差,跨语言支持差的问题,同时也没有GenericJackson2JsonRedisSerializer 序列化后在redis增加@class字段造成储存冗余的问题。

总结

1. 默认 JDK 序列化的问题

  • 问题表现
    使用未配置的 RedisTemplate 会默认采用 JDKSerializationRedisSerializer,导致存储数据为二进制流(如 \xac\xed\x00\x05t\x00\nhello),存在以下问题:

    1. 可读性差:二进制数据无法直观阅读。

    2. 体积膨胀:包含类元数据,内存占用高。

    3. 兼容性差:类结构变更或跨 Java 版本易失败。

    4. 跨语言限制:仅适用于 Java 生态。

2. 解决方案:自定义序列化配置

通过配置 RedisTemplate 的序列化器,优化存储格式。常用两种 Jackson 序列化方案:


方案一:GenericJackson2JsonRedisSerializer

机制

  • 序列化时添加 @class 字段记录类型信息(如 "@class": "com.example.User")。

  • 反序列化时通过 @class 动态解析对象类型。

优缺点

  • 优点:支持多态类型,反序列化自动识别对象类型。

  • 缺点

    • 数据冗余@class 字段增加存储体积。

    • 类路径耦合:类名或包名变更会导致历史数据反序列化失败。

    • 安全风险:反序列化时依赖类路径,可能引发反序列化攻击。


方案二:Jackson2JsonRedisSerializer

机制

  • 纯 JSON 序列化,不添加额外类型信息。

  • 反序列化时需显式指定目标类型。

优缺点

  • 优点

    • 数据精简:无额外元数据,存储体积小。

    • 跨语言友好:标准 JSON 格式,通用性强。

  • 缺点

    • 类型强耦合:需在序列化时指定类型,反序列化时需明确知道类型。

    • 多态支持弱:无法自动处理继承或复杂类型集合。


3. 关键对比与选型建议

特性JDKSerializationRedisSerializerGenericJackson2JsonRedisSerializerJackson2JsonRedisSerializer
存储格式二进制流JSON(含 @class 类型信息)纯 JSON
可读性❌ 差✅ 优✅ 优
存储体积❌ 大⚠️ 中(含元数据)✅ 小
跨语言支持❌ 仅 Java✅ 优(需其他语言解析 @class✅ 优
多态类型支持✅ 优✅ 优❌ 差
反序列化类型安全⚠️ 依赖类路径⚠️ 依赖类路径✅ 显式指定类型,更安全

选型建议

  1. 简单类型 & 明确类型:优先选择 Jackson2JsonRedisSerializer,需显式指定类型。

  2. 复杂对象 & 多态需求:使用 GenericJackson2JsonRedisSerializer,但需接受 @class 冗余。

  3. 避免使用 JDK 序列化:除非需兼容旧系统或处理特殊二进制数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赛博猿神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值