百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

记一次细思极恐的FastJson差点引发的大面积故障

suiw9 2025-02-07 19:06 27 浏览 0 评论


在短短不到两年的开发生涯里,加上这次,印象中已经碰到过至少3次FastJson的问题了。而且FastJson不同版本之间的差异很大,各位同学在使用时一定注意不要踩坑。?

下面讲一下我碰到的这个细思极恐的问题。

一、首先讲讲工程背景

我们的工程是Kotlin与Java混编的,再附加偶尔写写Groovy,在团队中不断熟悉后发现各语言在编程中各有利弊。

  • Java就不说了,阿里在国内Java应用上堪称鼻祖,集团各种工具对Java的支持都比较完备;
  • Kotlin有很多语法上的优势,同样的代码Java 10行,Kotlin可能只需要5行,此外支持协程(coroutines),编写非阻塞异步代码看起来像是同步的,能很好地处理IO密集型任务。但是Kotlin毕竟非正统,集团很多工具对Kotlin的支持性比较一般;
  • groovy的语法规则更加特殊,工程里用的不是特别多,所以基本都是需要开发时用大模型去解决,没有特意去研究;


二、问题现象

突然有一天组里同学告诉我预发有大量报错,是不是我改了架构层面的代码引起的。我心想肯定不是啊,我这是增量编程,并没有改动太多原来的代码。

但是处于谨慎起见,还是把分支踢掉吧,重新部署了一下,发现同样的问题在工程重启一小段时间后又发生了。

下面是问题报错,这会导致工程运行时大部分涉及反序列化的链路中断,而工程到处用了FastJson,影响面可想而知。


三、排查过程

1. 怀疑FastJson版本

既然是FastJson的抛错,那是不是有人改动了工程依赖,于是开始查pom文件的改动,一时也想不到别的原因可能导致该报错。

查了一圈并没有发现可疑的地方,于是上AppInsights中看了一下FastJson相关类的加载情况,发现这里有个1.2.68_noneautotype版本的包,而工程其实统一用的是1.2.83_noneautotype,会不会是这里引起的呢?


后来登到线上机器对比了一下发现没有这个版本,那有可能就是别的包引入的,于是看了一下出问题module的pom文件,发现果然有这个版本的FastJson,再定位了下发现是rass-sdk-core引入了这个包,scope为compile,打包时会将这个包编译进工程中。




??很果断的将该包中的FastJson排掉,那问题铁定解决了,观察了日志发现没有再抛错,我就没再管。到了下午同学又来反馈有问题,我一看还真是,难道是搞错了。

再次对比了一下预发和线上,发现还真是我搞错了,线上也有1.2.68_noneautotype。?

我有点郁闷,为了定位这个bug,翻遍了预发每个人的分支,为了排包前后部署过很多次。此时这个问题已经困扰了我1天多了,想撂挑子放弃,到时候谁发线上时注意下好了。

但又一想这是我们辛苦维护的庞大工程,不能因为这种莫名其妙的问题引发大面积故障,到时候辛苦半年多白干(上价值,给自己增加点动力)。理智告诉我哪怕手头的需求先放一放,还得继续查,虽然只是预发。?

2. 怀疑kotlin相关依赖

跟同学确认了JDK近期也没有变动后,接下来开始到处翻资料。

大概能确认是反序列化Kotlin的data class实体时出现的问题。类似下面这种结构?


GitHub上关于该问题的讨论也比较多,总结一下解法有:

1.FastJson和kotlin版本不兼容

2.工程中需要引入kotlin-reflect

3.需要对data class的非空字段默认初始化

4.……?



第三个字段初始化问题不太可能,因为受影响的class从来没改动过,不可能好端端出问题。估计问题应该还是出在工程环境上。

于是去工程里看了一下kotlin-reflect依赖,仍然是正常引入的。

又去看了几遍近期所有分支的所有改动,也没有任何相关变动,慢慢地开始头麻,环境问题一向是最难查的。。。。


??

3. 翻阅到相似的例子

接下来就还是一直在网上翻阅其他资料,后来在掘金找到一篇:

?https://juejin.cn/post/6929740384734019597



文章提到的问题现象跟我们的还挺像的,都是一开始正常,过了一会儿就又出现问题。

文章对FastJson剖析挺深,感觉我们的工程不至于这么奇葩吧。还提到了什么
getKoltinConstructorParameters、Unit变量、kotlin_error标记位问题。
核心原因是有个静态标记位被置为异常状态。?


虽然抽象,但死马当活马医,试试看吧。

先看工程里有没有打印这个NPE吧,SLS日志搜了半天都没搜到,去源码看了一下,原来这里只打印了异常堆栈,那不会被采集到SLS中,不过服务器日志里可能会有。?


妈耶还真给我搜到了,再仔细读了一下打堆栈的地方,会将kotlin_error置为true,而且关键的地方在于这玩意儿是个static volatile变量!!具有静态共享特征(static)和多线程可见性(volatile),意味着整个工程使用的都是被修改后的值。?

??


4. ☆ 锁定关键报错位置

继续进QuestionCardProxyApi 228行 找到报错源,发现确实有个toJsonString。对于invokeResumeReq这个对象,我一眼看到有个古怪的赋值,this.resumeBody被赋值为{}。但这个赋值有何神奇之处,哪能这么个小玩意儿导致全局异常啊??

这段日志代码是12-17添加的,时间看了下还挺符合。再看resumeBody其实是个Java类的一个object类型字段,kotlin中这样对其赋值,编译器倒也没报错?


其实到这个地方已经感觉要水落石出了,再一看master,妈耶这个代码上线了,但线上却没有任何报错,运行一切正常。

但基本能确定是这里无疑了,就赶紧拉写这段代码的同学看(悄悄说,仁兄一开始提给我的bug),确认线上还没开始灰度,无任何流量后放心了。他在家里加班修,我继续看原理。?

首先肯定本意是想将resumeBody置空,误用了{},{}在kotlin中被编译器解释为一个lambda表达式。


??


再看debug信息,这里resumeBody实际上被解释为 ()->kotlin.Unit 这样一个lambda函数,arity表示函数的参数数量为0.?



也就是说:{}是一个没有任何入参,并且返回值默认为Unit类型的一个函数(没有明确返回值时默认Unit类型)。

为了更清晰地了解这个语法,可以看下面这个例子:这里定义了一个函数式变量x,入参为s,出参为s+“000”。在使用时可以直接以函数的方式调用x,最终得到y=111000?


FastJson自然无法正确解析这样的一个Object字段,而最令人细思极恐的问题是 解析这个对象报了错,却把kotlin_error置为true,而后有没有地方将其复原为false,导致所有的反序列化都进到这里,返回不正确的结果,错误结果被外层拦截抛出default constructor not found异常。这个影响面是巨大的,会导致整个工程崩溃。

具体代码解释:

1.一开始 kotlin_error !=true ,会进到
kotlin_kclass_getConstructors.invoke
获取类构造器,这里抛错了,把kotlin_error改为true;

2.往后所有相关的FastJson序列化、反序列化重新进到这里都会return null,导致类构造器获取失败;

3.外层没拿到paramNames,直接抛了异常。




5. 有问题的代码(自测)

有kotlin运行环境的同学可以尝试运行下面的代码自测。

注意:经测试这段代码在FastJson 2.0.53版本可以正常运行,其他版本同学们可以再自测下。

class KotlinErrorTest {
    @Test
    fun testFastJson() {
        val jsonObj = JSONObject().apply {
            this["accessorUuid"] = "accessorUuid"
            this["orgId"] = 4343L
            this["resumeTaskId"] = "resumeUrl"
            this["resumeBody"] = {}
        }
        // 这里 toJSONString 破坏了 kotlin_error 这个变量
        JSON.toJSONString(jsonObj)


        // 定义一个对象A,并序列化
        val testA = A("1")
        val strA = JSON.toJSONString(testA)
        // 反序列化拿data class A的字段时会直接返回null,导致 default constructor not found
        val objA = JSON.parseObject(strA, A::class.java)


        println(objA)
    }


    data class A(
        val a: String
    )


    @Test
    fun testLambda() {
        val x = { s: String -> s + "000" }
        val y = x("111")
        println(y)
    }
}

四、总结&反思

至此,问题定位清楚并彻底解决了。这次bug是工作以来碰到的最抽象的一个,耗时两天。虽然有点低效,但找到原因后俺非常激动,逮住组里同学细细讲了一番,估计这么抽象的问题工作多年的大佬也不一定能遇到。

再次膜拜一下掘金大佬:https://juejin.cn/post/6929740384734019597

另外也反思了以下几个问题:

1.工程中Java、kotlin、groovy等多语言混编,对开发同学的语法掌握程度有较高要求,有时候各语言间会混淆,特别是判空、变量定义规则等。这些语言的线上空指针我都尝过,有点酸爽。这次同学出现语法问题,其实也是有点语言混淆了,如果纯Java,铁定不会甩个{}上去;

2.这次线上无异常,得益于灰度开关(哥们可得感谢我,还没放量,否则一旦流量进来,涉及kotlin的链路全部中断就寄了);

3.FastJson是有很多漏洞的,使用时仍然要高度注意。任何框架都是不能完全信任的,毕竟代码都是人写出来的,Bug要大家一起合力发现hhh;

4.写Bug是每个开发同学都会经历的,朋友经常嘲笑我写Bug。但实际上你写过的Bug越多,说明越有经验,吃一堑长一智,下次不要再犯就好了。

以上内容分享给大家,欢迎大家指导和建议~~


CentOS到Alinux操作系统迁移

2020年12月08日,CentOS官方宣布了停止维护CentOS Linux的计划,操作系统迁移解决方案为企业提供ECS实例运行的操作系统EOL(生命周期结束)后的替换或升级服务。

点击「链接」查看详情。

相关推荐

俄罗斯的 HTTPS 也要被废了?(俄罗斯网站关闭)

发布该推文的ScottHelme是一名黑客,SecurityHeaders和ReportUri的创始人、Pluralsight作者、BBC常驻黑客。他表示,CAs现在似乎正在停止为俄罗斯域名颁发...

如何强制所有流量使用 HTTPS一网上用户

如何强制所有流量使用HTTPS一网上用户使用.htaccess强制流量到https的最常见方法可能是使用.htaccess重定向请求。.htaccess是一个简单的文本文件,简称为“.h...

https和http的区别(https和http有何区别)

“HTTPS和HTTP都是数据传输的应用层协议,区别在于HTTPS比HTTP安全”。区别在哪里,我们接着往下看:...

快码住!带你十分钟搞懂HTTP与HTTPS协议及请求的区别

什么是协议?网络协议是计算机之间为了实现网络通信从而达成的一种“约定”或“规则”,正是因为这个“规则”的存在,不同厂商的生产设备、及不同操作系统组成的计算机之间,才可以实现通信。简单来说,计算机与网络...

简述HTTPS工作原理(简述https原理,以及与http的区别)

https是在http协议的基础上加了一层SSL(由网景公司开发),加密由ssl实现,它的目的是为用户提供对网站服务器的身份认证(需要CA),以至于保护交换数据的隐私和完整性,原理如图示。1、客户端发...

21、HTTPS 有几次握手和挥手?HTTPS 的原理什么是(高薪 常问)

HTTPS是3次握手和4次挥手,和HTTP是一样的。HTTPS的原理...

一次安全可靠的通信——HTTPS原理

为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。Alice与Bob的通信...

为什么有的网站没有使用https(为什么有的网站点不开)

有的网站没有使用HTTPS的原因可能涉及多个方面,以下是.com、.top域名的一些见解:服务器性能限制:HTTPS使用公钥加密和私钥解密技术,这要求服务器具备足够的计算能力来处理加解密操作。如果服务...

HTTPS是什么?加密原理和证书。SSL/TLS握手过程

秘钥的产生过程非对称加密...

图解HTTPS「转」(图解http 完整版 彩色版 pdf)

我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。...

HTTP 和 HTTPS 有何不同?一文带你全面了解

随着互联网时代的高速发展,Web服务器和客户端之间的安全通信需求也越来越高。HTTP和HTTPS是两种广泛使用的Web通信协议。本文将介绍HTTP和HTTPS的区别,并探讨为什么HTTPS已成为We...

HTTP与HTTPS的区别,详细介绍(http与https有什么区别)

HTTP与HTTPS介绍超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的...

一文让你轻松掌握 HTTPS(https详解)

一文让你轻松掌握HTTPS原文作者:UC国际研发泽原写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。...

如何在Spring Boot应用程序上启用HTTPS?

HTTPS是HTTP的安全版本,旨在提供传输层安全性(TLS)[安全套接字层(SSL)的后继产品],这是地址栏中的挂锁图标,用于在Web服务器和浏览器之间建立加密连接。HTTPS加密每个数据包以安全方...

一文彻底搞明白Http以及Https(http0)

早期以信息发布为主的Web1.0时代,HTTP已可以满足绝大部分需要。证书费用、服务器的计算资源都比较昂贵,作为HTTP安全扩展的HTTPS,通常只应用在登录、交易等少数环境中。但随着越来越多的重要...

取消回复欢迎 发表评论: