《知识点扫盲 · 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在项目中得常见用法,仅供学习参考。

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

相关推荐
寂寞旅行5 小时前
向量数据库Milvus的使用
数据库·milvus
闻哥5 小时前
Redis事务详解
java·数据库·spring boot·redis·缓存·面试
道亦无名6 小时前
aiPbMgrSendAck
java·网络·数据库
面向对象World9 小时前
正点原子Mini Linux 4.3寸800x480触摸屏gt115x驱动
linux·服务器·数据库
dinga198510269 小时前
mysql之联合索引
数据库·mysql
微风中的麦穗9 小时前
【SQL Server 2019】企业级数据库系统—数据库SQL Server 2019保姆级详细图文下载安装完全指南
大数据·数据库·sqlserver·云计算·个人开发·运维必备·sqlserver2019
zjttsh10 小时前
MySQL加减间隔时间函数DATE_ADD和DATE_SUB的详解
android·数据库·mysql
顾北1210 小时前
SpringCloud 系列 04:Gateway 断言 / 过滤器 / 限流 一站式落地指南
java·开发语言·数据库
禹凕10 小时前
MYSQL——基础知识(NULL 值处理)
数据库·mysql
码云数智-大飞10 小时前
SQL Server 无法启动?常见原因及详细解决方法指南
数据库