踩坑一则复杂对象(领域实体)序列化失败问题。

归因:测试环境Redis连接挂掉,同时对DDD领域实体序列化,JSON.toJSON() 封装了异常,导致关键异常栈丢失,后经过正向逆向分析找到了问题原因与解法。

背景

11.14号,QA给我反馈了一则问题:

测试环境,添加指纹密码时,会报tojson error

分析

根据trace日志,可以直接定位到报错的源码位置:

1
com/company/domain/lock/polymorphism/encoder/impl/SampleEncoder.java:294

其中报错代码示例,password是这个服务的一个核心领域实体:

1
log.info(JSON.toJSON(password));

但是这里的异常栈并没有把最原始的异常打印出来,原因是JSON.toJSON中对异常进行了包装:

现在可以确定的一点是,在 SampleEncoder 中,对领域实体 Password 使用fastjson库序列化为字符串时,发生了报错。

我们顺着 Password entity 的结构分析一下:

Password领域实体内持有Lock领域实体,而Lock领域实体内持有HardwareInfo领域实体。

getHardwareInfo() 方法中,存在查库查缓存行为。

当序列化框架实现序列化方法 toJSON 时,会通过反射调用JavaBean中的属性方法,即各个get方法。

凑巧的是,当时我们的测试环境redis连接挂了,导致调用 getHardwareInfo() 直接抛错。

而在 JSON.toJSON 中,对异常进行了包装,丢失了一部分异常栈,因此在日志中看不到这块信息。

我是怎么发现redis连接挂掉了呢?

是因为QA同学反馈了其他的问题,其他的操作也无法操作redis,在这些地方异常栈被打印了出来。 两类问题结合分析,得出了上面的正向、反向的通路。

而在线上环境我们可以直接通过监控报警观测到这种异常,不用兜这么大圈子。

小结

问题根因:测试环境的redis连接挂了,导致领域对象的get方法抛错,导致toJSON直接失败。

解决:联系DBA修复redis

教训:在使用了DDD架构的代码上,其实不建议对领域对象进行序列化操作,这种操作,无异于MVC架构中,对service进行序列化操作。做类似操作前,应该仔细评估+做好提前设计。