问题描述
负责的应用在发布时,部分机器load飙高超出阈值报警(load大于8)。
伴随现象:cpu使用率飙高(超过80%)且瞬时RT超高,一般大于1000ms。
一般三分钟左右以后性能恢复到正常。如图:
找出瓶颈
发布时监控机器性能,并查看资源使用较多的线程。
- 应用刚启动,用top -H查看CPU使用率最高的线程,并用prinf "%X\n"打出对应的16进制的pid值,比如56148对应:DB54,56147:DB53,57026:DEC2,等下用于查找java进程中的线程堆栈信息,如图:
- 用jstack打出当前java进程的堆栈信息,并在其中搜索上面找出的3个cpu使用率高的线程,前两名的线程看到分别为C2 CompilerThread1和C2 CompilerThread0;第三名是tomcat的正常线程(http-/0.0.0.0:8080-n)用来监听请求并处理,这个Running的线程号是随机变化的,前两个线程(56148,56147)比较固定,并且cpu使用率一直很高,大概应用启动两分钟左右以后才降下去(整个过程与"问题描述"中的图表展示的情况一致)。
分析原因
瓶颈找到了,C2 CompilerThread的两个线程在应用启动中和刚启动成功的这段时间CPU使用率较高,而发布的时候应用启动后立即上线,此时如果大流量进来,就会load和rt飙高(这里澄清一个误区, load与cpu使用率并非一定是对应地升高或者降低,也取决于进程调度情况;在时间片分配以后,如果使用系统资源又完全取决于进程内部,因此完全有可能load高但是cpu使用率低的情况)。
查看资料C2 CompilerThread是JVM做编译和编译优化的线程:"Java程序在启动的时候所有代码的执行都处于解释执行模式,只有在运行了一段时间后,根据代码方法执行的次数,或代码里循环的执行次数等达到一定的阈值才会编译成机器码,编译成机器码后执行效率会得到大幅提升,而随着执行时间进一步拉长,JVM的各种更高级的编译优化手段就会逐渐加上,例如if条件的执行状况,逃逸分析等"。
结合到globalseo的实际问题,在应用刚刚启动的时候,代码里大量工具类等都没有编译或执行过,刚开始流量进入执行到代码的时候才开始初始化;另一方面程序刚启动的时候处于解释执行模式,这种模式下的执行效率较低,当代码执行次数增加到C2 CompilerThread线程完成编译优化以后,C2 CompilerThread线程的CPU使用率降低,并且此时由于执行机器码效率提升。
解决问题
所以解决问题的方法是应用启动以后先不开放外部流量,请求应用中经常访问的页面,编译并执行热点代码起一个预热的作用,等C2 CompilerThread线程完成编译和优化以后,这时CPU使用率已经正常,再上线应用并开放流量和打开监控。
结合解耦以后freedom发布"检查应用"这一步会检查preload.sh脚本的执行,考虑在preload.sh中请求应用中经常访问的页面。
验证
为了模拟发布中freedom执行preload.sh,在preload.sh脚本中封装app_preload()方法,并在jbossctl脚本中增加调用,重启应用情况如下:


结论:问题解决。这一周三次发布中没有出现load高的报警。
附test_preload.sh
#!/bin/bash
curl_url() {
#....
#....
#curl -s "http://localhost/xxxxxxx" > ~/preload.log
#....
#....
}
app_preload() {
echo "" >> ~/preload.log
TIMES=1
while true
do
if [ $TIMES -lt 10 ]; then
TIMES=$(($TIMES + 1))
echo $TIMES
curl_url
else
break;
fi
done
}
status=1