fastjson序列化导致生产CPU飙满

1.背景

先交代下fastjson的版本,为fastjson1的最新版本1.2.83,因为某些原因,没有升级到fastjson2

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

运行了很久的系统,突然出现监控告警,响应时间慢、cpu占用高的问题,第一个想法是,不合理的竞争锁(cpu自旋)?序列化反序列化(一个个字符读取)?或者有什么异常的死循环递归?

2.原因定位

系统是springboot工程,在系统日志里面,请求进来的时候会打印进入日志(请求参数、ip、时间等等),结束的时候也会打印结束(返回信息),真是良好的习惯!所以通过两两比较下,就可以找出目前在tomcat容器中剩余的线程。

我们其中发现有一个线程当前正在做json序列化。那么原因基本上就可以定位了。我们在开发环境通过复现相同的参数请求,发现同样的问题,CPU飙满,其他请求开始有延迟。

3.原因分析

定位到了方向后,我们在开发环境中CPU飙高的时候,通过jstack命令打印下内存信息

jstack -F pid

然后堆栈信息最后一行是

com.alibaba.fastjson.serializer.SerializeWriter.writeStringWithDoubleQuote

马上打开源码看下,在源码831行里面

我们顺着源码往下解析,发现有一个fastjson的参数,我们在初始化序列器的时候有添加,那就是BrowserCompatible(浏览器兼容模式),这个参数作用主要是把中文转成Unicode、在特殊字符,双引号、斜线、反斜线前面添加一个斜线,大致如此吧。

 

我们看下他的实现方式,代码994行开始:

前面说过了,序列化和反序列化需要一个个字符去读取,然后进行解析。fastjson里面的处理方式遇到特殊字符,就调用System.arraycopy的方式进行移位操作,如果待序列化的文本里面有100w个特殊字符(前端模板之类的),那就需要位移100w次,简直不敢想象,做个小实验。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FastjsonTest {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < 1000000; i++) {
            stringBuffer.append("/");
        }
        log.info("start to calculate");
        long start = System.currentTimeMillis();
        JSON.toJSONString(stringBuffer.toString(), SerializerFeature.BrowserCompatible);
        log.info("spend {} millisecond!", System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        JSON.toJSONString(stringBuffer.toString());
        log.info("spend {} millisecond!", System.currentTimeMillis() - start);
    }
}

Connected to the target VM, address: '127.0.0.1:55195', transport: 'socket'
08:37:08.901 [main] INFO com.leakey.learn.test.FastjsonTest -- start to calculate
08:40:24.887 [main] INFO com.leakey.learn.test.FastjsonTest -- spend 195978 millisecond!
08:40:24.908 [main] INFO com.leakey.learn.test.FastjsonTest -- spend 14 millisecond!

结果显而易见,消耗了195S的时间。所以使用了BrowserCompatible参数的序列化,如果恰巧你的序列化字符串总包含大量的特殊字符(双引号、斜线、反斜线)将会消耗大量的时间来处理移位运算,System.arraycopy里面的调用的native方法,也就是用C写的,源码这边就暂时不分析了,因为也没有意义,但是肯定的是这个会跟你的操作系统、cpu指令有关系的,搞不好你将会遇到CPU卡死、系统没有响应的问题。

4.总结

BUG触发条件:

        1.版本:fastjson1

        2.序列化文本:里面包含大量的特殊符号(双引号、斜线、反斜线)

        3.fastjson开启了BrowserCompatible

fastjson1的版本估计阿里也是处于半放弃阶段了,这段源码简直看着揪心,是一个程序员写的代码,但明显没什么算法思维,属于硬编码级别。建议大家尽快升级到fastjson2,目前fastjson2已经解决这个BUG。

大家开发中有遇到什么BUG,也可以联系我,万一我能给点建议呢?O(∩_∩)O

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值