因在缓存对象中增加字段,导致Redis出现反序列化失败的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了因在缓存对象中增加字段,导致Redis出现反序列化失败的问题相关的知识,希望对你有一定的参考价值。

参考技术A

因为业务需求的需要,我们需要在原来项目中的一个DTO类中新增两个字段(我们项目使用的是dubbo架构,这个DTO在A项目/服务的domain包中,会被其他的项目如B、C、D引用到)。但是这个DTO对象已经在Redis缓存中存在了, 如果我们直接向类中增加字段而不做任何处理的话,那么查询操作查出来的缓存对象就会报反序列化失败的错误,从而影响正常的业务流程 ,那么来看一下我的解决方案吧。

我们的正式环境和预发布环境是共用Redis和mysql。如果修改了DTO且没有加@JsonIgnoreProperties(ignoreUnknown = true)这个注解。

那么DTO所在的A项目发到预发布之后,会启动一个后台定时任务把最新的DTO对象刷新到缓存中去,但是除了这个工程以外的其他依赖服务如果没有发的话,那么他们jar包里面的domain还是旧的DTO。那么这个时候取出来的缓存(最新的DTO的缓存)就会有反序列化的错误,发包的延迟和预发布验证的时间都会导致线上反序列化失败,从而阻塞业务。

解决方案就是升级缓存的版本号(修改原来缓存DTO的Redis的Key值)

缓存key升级版本号,在其他未更新的应用中的缓存key已经在跑的jar包里面,他们的key是旧的,比如v1,那么v1对应的DTO就是旧的DTO。升级后新的DTO版本为v2那么发起来的自身服务刷新最新的DTO缓存是放到v2的key里面的,即v2->新的DTO,v1->旧的DTO。这样可以保证不会有反序列化的问题。

改版本号一定要在第一次发的时候改上去才好,不然你按v1发的版,发现问题再改成v2已经就晚了,因为已经把新的DTO刷到v1里面了,线上的依赖服务里面的domain包就是v1捞出来肯定异常。如果发生这种情况只能再发v2版本到预发布,同时删掉线上v1的缓存。

我是 「翎野君」 ,感谢各位朋友的: 点赞 收藏 评论 ,我们下期见。

Redis 序列化和反序列化

【中文标题】Redis 序列化和反序列化【英文标题】:Redis Serialization and Deserialization 【发布时间】:2015-08-28 00:04:04 【问题描述】:

我注意到我存储在 Redis 中的一些序列化对象存在反序列化问题。

这通常发生在我对存储在 Redis 中的对象类进行更改时。

我想了解问题,以便为解决方案制定清晰的设计。

我的问题是,是什么导致了反序列化问题? 移除公共/私有财产会导致问题吗? 添加新属性,也许? 向类中添加新功能会产生问题吗?更多的构造函数怎么样?

在我的序列化对象中,我有一个属性 Map,如果我更改(更新一些属性、添加功能等)myObject,会导致反序列化问题吗?

【问题讨论】:

【参考方案1】:

导致反序列化问题的原因是什么?

在回答你的问题之前,我想给你一些背景知识,

序列化运行时将一个版本号与每个可序列化类相关联,称为 serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已为该对象加载了与序列化兼容的类。如果接收方为对象加载了一个类,该对象的 serialVersionUID 与相应发送方的类不同,则反序列化将导致 InvalidClassException。

如果一个可序列化的类没有显式声明一个serialVersionUID,那么序列化运行时将根据该类的各个方面为该类计算一个默认的serialVersionUID值,它使用该类的以下信息来计算SerialVersionUID,

    类名。 写成 32 位整数的类修饰符。 按名称排序的每个接口的名称。 对于按字段名称排序的类的每个字段(私有静态和私有瞬态字段除外: 字段的名称。 以 32 位整数形式写入的字段修饰符。 字段的描述符。

    如果存在类初始化器,请写出以下内容:

    方法的名称,.

    方法的修饰符,java.lang.reflect.Modifier.STATIC,写成32位整数。

    方法的描述符,()V。

    对于按方法名称和签名排序的每个非私有构造函数:

    方法的名称,.

    方法的修饰符写成 32 位整数。

    方法的描述符。

    对于按方法名称和签名排序的每个非私有方法:

    方法的名称。

    方法的修饰符写成 32 位整数。

    方法的描述符。

所以,回答你的问题,

移除公共/私有财产会导致问题吗?添加新属性,也许?向类中添加新功能会产生问题吗?更多的构造函数怎么样?

是的,所有这些默认添加/删除都会导致问题。

但解决此问题的一种方法是显式定义 SerialVersionUID,这将告诉序列化系统我知道该类会随着时间的推移而演变(或演变)并且不会引发错误。所以反序列化系统只读取两边都存在的那些字段并赋值。反序列化端新添加的字段将获得默认值。如果在反序列化端删除了某些字段,算法只是读取并跳过。

以下是声明 SerialVersionUID 的方式,

private static final long serialVersionUID = 3487495895819393L;

【讨论】:

非常感谢您提供的信息丰富的回答。我还有另一个问题需要澄清。当您说“显式定义 SerialVersionUID”时,可以将此 UID 定义为默认值“private static final long serialVersionUID = 1L;”吗? 我的意思是,如果 CLASS 没有声明 UID,那么它将使用 DEFAULT。但!如果我声明了一个 UID,但它的值仍然是 DEFAULT 怎么办?它还会计算 UID 吗? 您好,您可以定义任何值,只要两边的值相同即可。顺便说一下 1L 不是默认值,你可以认为它是一个版本号。 那么 serialVersionUID 本身是否应该是瞬态的?我猜不是,但是它可能会在 redis 中一遍又一遍地使用大量内存来存储相同的值?

以上是关于因在缓存对象中增加字段,导致Redis出现反序列化失败的问题的主要内容,如果未能解决你的问题,请参考以下文章

redis缓存怎么存储对象

解决Springboot使用Redis反序列化遇到的类型转换异常

解决Springboot使用Redis反序列化遇到的类型转换异常

Redis 序列化和反序列化

关闭内存缓存对效能有影响吗

Redis缓存技术学习系列之邂逅Redis