java序列化2-再谈序列化

 

序列化引起的血案

最近项目组中碰到好几起缓存序列化引起的问题,其中一个类型问题让一个人三次踏入同一条河流,在此再对序列化做个探讨与总结。

顺便回顾一下那句著名的口号:对代码要有敬畏之心

序列化初探

在之前介绍过java的序列化,传送门:java序列化

所以这里是再谈

复盘过程

故障1

在前面java8-lambda之toMap引发的线上故障里面,介绍上线了一个疫情模块,因为lambda的toMap引发了一个故障。

还是这个模块,又引发了另一个故障,而其中就涉及到序列化

流程图

原来的设计是按天按区域展示当前疫情情况。

  1. 用户在APP上面访问,点击疫情模块;首先去查询缓存,有缓存直接返回
  2. 缓存里面没有,去第3方系统实时请求
  3. 第3方系统请求成功,返回实时数据,先放入缓存,再返回APP;如果未查到,直接返回APP空数据,模块不展示

怎么样,看起来没啥毛病吧。

刚上线前几天,的确也很正常,相关项目人员都开了庆功会了。直到一个午睡后的中午,开始收到报警,模块展示成功率突然再次迭0,并且一直持续。

这次是第3方系统挂了,获取不到数据了。根据上面的设计,就算第3方挂了,系统也不应该迭0才对,至少在缓存过期之前。

难道是缓存雪崩?分析一下也不可能,qps很平稳,理论上就算所有流量直接走到了实时请求,第3方也能撑住或者降级,然后才会成功率缓慢下迭,而不会突然迭0。

后来紧系排查发现,原来缓存里面根本就没有数据,不是过期了,也不是当时没有,而是从来没有过!原因就是在put缓存时,没有实现Serializable,所以序列化一直是失败的,但是代码把异常吃掉了,所以就出现了上述尴尬的问题!

思考

  1. 根据墨菲定律,设计之初考虑到可能出现的问题,一定会出现,更不要说一些不可预知的问题。所以对代码要有敬畏之心,多测试。
  2. 做好监控很重要。先做到有监控,然后做好监控。
  3. 缓存有很多坑,一知半解最可怕,多实践,多学习,做好打工人。

故障2

再来说另一个故障,也是导致一个功能模块不展示,也是由于序列化引起的,但是具体原因却不一样。

  1. 用户在APP上面访问,首先去查询缓存,有缓存直接返回
  2. 缓存查询失败,抛出了系统异常,导致模块不展示

这个问题原因就是,代码在重构的时候,对相关类的包路径进行了调整,导致反序列化失败,这是序列化经常出问题的地方之一。比如下面:

老代码的包路径

        package com.overseacommon.component.dynamic2.domain.config

新代码的包路径

        package com.overseacommon.dynamic2.spi.domain.config

因为对象序列化的内容,包括三部分:meta信息(对象类型所在包路径)、属性类型、属性值。这三部分的信息,是为了反序列化时,能正确转化成序列化之前的对象。

而且这个现象在生产还不是稳定复现,因为线上机器是集群,存在新老代码发布过程的一个不兼容行:

  • 如果是部署老代码的服务器写入缓存,并且是部署老代码的服务器读取缓存,此时不会报错。同样,如果是部署新代码的服务器写入缓存,并且是部署新代码的服务器读取缓存,也不会报错
  • 反之,如果是部署老代码的服务器写入缓存,并且是部署新代码的服务器读取缓存,此时就会报错。同样,如果部署新代码的服务器写入缓存,并且是部署老代码的服务器读取缓存,此时也会报错。这就是问题不可稳定复现的原因。

思考

  • 对于序列化的类,要千万注意包路径的变动都可能会引起反序列化失败
  • 在设计及测试阶段一定要考虑兼容性,尤其是线上是集群或者分布式系统
  • 对象先转成jsonString再进行序列化和反序列化操作,防止包路径改变导致的不兼容

写在最后

对于序列化,看起来很简单,但是也有一些复杂的地方,后续会出一个《java序列化-序列化设计防坑点》

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页