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

devtools/2024/9/22 2:37:36/

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

    • 写在前面的话
    • 缓存序列化器
      • 技术简介
      • RedisSerializer 接口
      • 常用序列化器
      • 方案比较
      • 使用示例
      • 故障说明
      • 模拟异常
    • 总结陈词

CSDN.gif

写在前面的话

博主所在公司近期线上环境,某天遇到某服务大批量异常,查看异常堆栈指向 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 两个接口;

//**把对象序列化为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的,用代码表示比较清楚:

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作为值序列化器。
它的特点就是存的值如下所示,通过缓存客户端工具,难以阅读和直接修改。
image.png

使用 GenericFastJsonRedisSerializer
博主所在公司最新框架封装的RedisTemplate采用GenericFastJsonRedisSerializer作为值序列化器。
采用它的主要目的应该是解决可读性差的问题,如下所示,同时性能和兼容性方面都不错。
当存入的是一个对象,能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。
image.png
image.png

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

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,也带来了更多的影响。

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

模拟异常

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

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();
}

image.png


总结陈词

此篇文章介绍了@Value在项目中得常见用法,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif


http://www.ppmy.cn/devtools/84842.html

相关文章

ubuntu中git log中文乱码

要在 Ubuntu 上永久设置环境变量&#xff0c;你可以将其添加到你的 shell 配置文件中。具体步骤如下&#xff1a; 1. 打开终端 2. 编辑 shell 配置文件 根据你使用的 shell&#xff08;例如 Bash 或 Zsh&#xff09;&#xff0c;编辑相应的配置文件。 如果你使用的是 Bash&a…

MySQL的存储过程

MySQL的存储过程 存储过程的概念 完成特定功能的sql语句的集合。把定义好的sql集合在一个特定的sql函数中每次执行调用函数即可。还可以实现传参的调用。 存储过程的作用 执行速度比sql语句执行的速度更快&#xff0c;执行的效率也更高。 客户端可以随时调用发放&#xff…

Linux - 进程间通信

目录 1 进程间通信 2 管道 2.1匿名管道 2.2 命名管道 3 共享内存 shmget shmctl shmat shmdt 优点 缺点 4 消息队列和信号量 4.1 消息队列 msgget msgctl​编辑 msgsnd msgrcv 4.2 信号量 semget semctl semop system V 标准的通信方式的共性 1 进程间通信 为…

上海市测绘院第三分院引进Infortrend存储磁盘阵列用于无人机测绘

用户背景 上海市测绘院第三分院集控制测量、工程测量、航空摄影测量、卫星图像处理、数字正射影像图制作、三维城市建模、精密工程测量、数字化地形测量、规划红线测量、规划全过程监督检查测量、地下综合管线测量、日照分析测量、建筑物变形测量、GPS、GIS、RS 测量等多种专业…

科普文:后端性能优化的实战小结

一、背景与效果 ICBU的核心沟通场景有了10年的“积累”&#xff0c;核心场景的界面响应耗时被拉的越来越长&#xff0c;也让性能优化工作提上了日程&#xff0c;先说结论&#xff0c;经过这一波前后端齐心协力的优化努力&#xff0c;两个核心界面90分位的数据&#xff0c;FCP平…

1-如何挑选Android编译服务器

前几天&#xff0c;我在我的星球发了一条动态&#xff1a;入手洋垃圾、重操老本行。没错&#xff0c;利用业余时间&#xff0c;我又重新捣鼓捣鼓代码了。在接下来一段时间&#xff0c;我会分享我从服务器的搭建到完成Android产品开发的整个过程。这些东西之前都是折腾过的&…

华为云依赖引入错误

问题&#xff1a;记录一次项目加在华为云依赖错误&#xff0c;如下&#xff1a; 错误信息&#xff1a;Could not find artifact com.huawei.storage:esdk-obs-java:pom:3.1.2.1 in bintray-qcloud-maven-repo (https://dl.bintray.com/qcloud/maven-repo/) 找到本地仓库&#…

[Android] Surface SurfaceView SurfaceHolder 三者之间的关系

三者的定位和关系 Surface 是绘制对象&#xff0c;SurfaceView是一个组件&#xff0c;SurfaceHolder是SurfaceView操作Surface的接口。虽然SurfaceView里面包含Surface&#xff0c;但是SurfaceView无法直接操作Surface&#xff0c;需要借由SurfaceHolder这个接口类来操作。 Su…