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