《知识点扫盲 · Redis 序列化器》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

写在前面的话

博主所在公司近期线上环境,某天遇到某服务大批量异常,查看异常堆栈指向 StringRedisSerializer#serialize,具体错误如下:java.lang.ClassCastException: class com.alibaba.fastjson.JSONArray cannot be cast to class java.lang.String

经排查发现,问题起因是该服务的开发人员,在某段业务代码修改了 redisTemplate 的序列化器导致该服务全局操作缓存异常,在解决问题并故障复盘后,这边也分享一下缓存序列化器的一些知识。

Tips:近期在更新程序猿入职必会系列(还在进行中),先更换一个知识点,调剂一下,每天都有新东西。


缓存序列化器

技术简介

Redis 数据序列化器,用于将数据在存储到 Redis 中时进行序列化(编码)和反序列化(解码)。

通俗来说,存数据的时候,需要先进行序列化,例如 Java 对象转为 JSON 字符串,再存到 Redis。

反过来,取数据的时候,需要先进行反序列化,例如 JSON 字符串转为 Java 对象,得到结果。

Tips:不一定是 Java 对象和 JSON 字符串的互相转换,也可以是和字节码、XML等多种格式。


RedisSerializer 接口

Spring-Redis 把上述逻辑封闭在redis序列化器中,接口为 RedisSerializer,完整类路径:

org.springframework.data.redis.serializer.RedisSerializer

该接口内容主要如下,主要关注序列化 serialize 和反序列化 deserialize 两个接口;

java 复制代码
//**把对象序列化为byte数组
byte[] serialize(@Nullable T t) throws SerializationException;

//**把byte数组反序列化为对象
T deserialize(@Nullable byte[] bytes) throws SerializationException;

//**静态方法,java格式序列化器
static RedisSerializer<Object> java() {
	return java(null);
}

//**静态方法,java格式序列化器
static RedisSerializer<Object> java(@Nullable ClassLoader classLoader) {
	return new JdkSerializationRedisSerializer(classLoader);
}

//**静态方法,json格式序列化器
static RedisSerializer<Object> json() {
	return new GenericJackson2JsonRedisSerializer();
}

//**静态方法,string数据类型序列化器
static RedisSerializer<String> string() {
	return StringRedisSerializer.UTF_8;
}

//**静态方法,byte数据类型序列化器
static RedisSerializer<byte[]> byteArray() {
	return ByteArrayRedisSerializer.INSTANCE;
}

//**检查是此序列化器否能够序列化指定数据类型
default boolean canSerialize(Class<?> type) {
	return ClassUtils.isAssignable(getTargetType(), type);
}

//**此序列化器针对的数据类型,默认object
default Class<?> getTargetType() {
	return Object.class;
}

常用序列化器

常用的序列化器,包含但不限于如下:

  • JdkSerializationRedisSerializer
  • StringRedisSerializer
  • JacksonJsonRedisSerializer
  • GenericFastJsonRedisSerializer

另外按用途序列化器又分两种,Key 和 Value的,用代码表示比较清楚:

java 复制代码
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jdkSerializationRedisSerializer);

Tips:一般 Key 都采用 StringRedisSerializer,Value 才需要讨论使用哪种策略。


方案比较

这四种 Redis 序列化器各有其优势和劣势,适用于不同的场景。

1、JdkSerializationRedisSerializer

优势:默认方案,不需要特别配置;可以序列化 Java 的任何对象,只要这些对象实现了 Serializable 接口。

劣势:性能相对较慢,序列化后的数据是二进制格式,可读性差。

过程:序列化是利用 ByteArrayOutputStream 将 Java 对象转换为 byte,反序列化就不赘述了。

2、StringRedisSerializer

优势:简单高效,适合处理字符串数据,序列化和反序列化速度快,序列化后的数据是字符串格式,易于调试和查看。

劣势:仅支持字符串类型,不适合复杂对象,在读取时需要手动转换为正确的类型,容易出错。

过程:序列化是使用指定的字符集直接把 String 类型转换为 byte。

3、JacksonJsonRedisSerializer

优势:序列化后的数据是 JSON 格式,易于调试和查看,兼容性强:支持多种 Java 对象,能够处理复杂对象和集合类型,也支持不同的配置选项,可以自定义序列化和反序列化过程。

劣势:需要引入 Jackson 库,增加了项目的依赖,速度低于 GenericFastJsonRedisSerializer。

过程:序列化时使用 jackson#ObjectMapper 把对象转换为 JSON 字符串,再转换为 byte。

4、GenericFastJsonRedisSerializer

优势:基于 FastJSON 实现,序列化和反序列化速度较快,适合处理大数据量,同时序列化后的数据为 JSON 格式,易于查看和调试,也支持泛型,能够处理不同类型的对象。

劣势:需要引入 FastJSON 库,增加了项目的依赖。

过程:序列化时使用 fastjson的JSON.toJSONBytes 把对象转换为 JSON 字符串,再转换为 byte。


使用示例

使用 JdkSerializationRedisSerializer

博主所在公司的旧框架封装的RedisTemplate采用JdkSerializationRedisSerializer作为值序列化器。

它的特点就是存的值如下所示,通过缓存客户端工具,难以阅读和直接修改。

使用 GenericFastJsonRedisSerializer

博主所在公司最新框架封装的RedisTemplate采用GenericFastJsonRedisSerializer作为值序列化器。

采用它的主要目的应该是解决可读性差的问题,如下所示,同时性能和兼容性方面都不错。

当存入的是一个对象,能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。

手动序列化

这种方案仅供参考,就是自己用代码实现序列化和反序列化,就挺麻烦的。

java 复制代码
private static final ObjectMapper mapper = new ObjectMapper();
 
@Resource
private StringRedisTemplate stringRedisTemplate;

@Test
void testSaveUser() throws JsonProcessingException {

    // 创建对象
    Student student = new Student("李白",28);

    // 手动序列化
    String json = mapper.writeValueAsString(student);

    // 写入数据
    stringRedisTemplate.opsForValue().set("student", json);

    // 获取数据
    String jsonUser = stringRedisTemplate.opsForValue().get("user:200");

    // 手动反序列化
    Student user1 = mapper.readValue(jsonUser, Student.class);

    System.out.println("user1 = " + user1);
}

故障说明

开发人员在新框架公用服务,编写了如下代码,凭一己之力修改了全局的 redisTemplate 的缓存策略。

最终可能实现了功能,但却本末倒置、因小失大了,替换回了不太合适的StringRedisSerializer,也带来了更多的影响。

java 复制代码
@Transactional
public List<Map<String,String>> getSCHData(BasicDict dict) throws Exception {
    redisTemplate.setHashValueSerializer(new StringRedisSerializer());
    。。。省略业务代码
}

模拟异常

参考下方代码,可以看到,通过 setValueSerializer 修改序列化器之后,再次操作设置缓存的语句,就出现了异常。

java 复制代码
try {
    ResultVO a = new ResultVO();
    a.setMessage("message");
    a.setCode("123");
    redisTemplate.opsForValue().set("PARAMS:PORTAL:0", a);
    Object o = redisTemplate.opsForValue().get("PARAMS:PORTAL:0");
    System.out.println(o);
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    Object o2 = redisTemplate.opsForValue().get("PARAMS:PORTAL:0");

    //这句出现异常
    redisTemplate.opsForValue().set("PARAMS:PORTAL:0", a);
} catch (Exception e) {
    //异常信息如下:
    //java.lang.ClassCastException: class com.zoe.onelink.common.entity.ResultVO cannot be cast to class java.lang.String (com.zoe.onelink.common.entity.ResultVO is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
	//at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:36)
    e.printStackTrace();
}

总结陈词

此篇文章介绍了@Value在项目中得常见用法,仅供学习参考。

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

相关推荐
一 乐2 小时前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
山猪打不过家猪3 小时前
(三)总结(缓存/ETag请求头)
缓存·微服务
美林数据Tempodata3 小时前
大模型驱动数据分析革新:美林数据智能问数解决方案破局传统 BI 痛点
数据库·人工智能·数据分析·大模型·智能问数
野槐4 小时前
node.js连接mysql写接口(一)
数据库·mysql
Zzzone6834 小时前
PostgreSQL日常维护
数据库·postgresql
chxii4 小时前
1.13使用 Node.js 操作 SQLite
数据库·sqlite·node.js
冰刀画的圈4 小时前
修改Oracle编码
数据库·oracle
这个胖子不太裤4 小时前
Django(自用)
数据库·django·sqlite
麻辣清汤4 小时前
MySQL 索引类型及其必要性与优点
数据库·mysql
天然首长6 小时前
Redis相关
redis