问题现象
后台服务三个部署节点偶发cpu 200%占用,服务进程还在,但服务不可用,等待3-10分钟,服务恢复正常。
分析
根据以往项目经验分析,初步怀疑是本身代码问题,大量创建对象,频繁触发full gc。
排查
1、查看gc情况
通过gcutil可以看到服务gc情况正常。
2、查看tcp监控,TCP状态正常,可以排除是http请求第三方超时带来的问题。
3.查看线程栈
1)jps 获取服务进程PID
2)查看进程下的线程情况
3)找到cpu占用率高的线程PID(写文档时服务已恢复正常,截图非出现问题时截图),当时现象为两个线程cpu占用率99%。查看这两个线程栈信息。将线程ID按16进制展示,执行指令“printf "%x\n" PID”。
4)执行指令“jstack 进程PID | grep "16进制线程号" -C5 --color”:
修复
通过查看线程栈第4步,可以看到两个cpu占用搞的线程分别对应的是gc线程和一个业务线程号。
方向1:既然有gc线程,说明gc还是有问题,此时在查看服务的gc状况多了一次full gc,调整内存,加大内存到32G,避免full gc, 修改java8默认垃圾回收器,CMS,ParNew 换成G1。结论:问题未解决。
方向2:另一个业务线程号, 根据业务线程号 grep 历史日志(后台线程号做了重命名,每个线程号都是唯一),此时可以根据线程号定位到对应的接口:url转base64,日志中提取用户的请求报文,再次调用url转base64接口,果然,问题复现,再次犯症,服务不可用,cpu飚到200%。重启服务 开启arthas工具,定位到 set值以后该线程就一直卡顿不在返回,至此确定是业务代码问题。查看日志打印步骤,初步怀疑是统一返回fastJson 序列化问题。
旧代码如下(关键逻辑代码):
@GetMapping("/urlToBase64")
public UrltoBease64Vo imgUrlToBase64(@RequestParam("imageUrl") String imageUrl){
UrltoBease64Vo vo = new UrltoBease64Vo();
String base64 = urlToBase64(imageUrl);
vo.setBase64(base64);
return vo;
}
整改方向:该服务配置了统一返回时序列化为fastJson,不再走统一返回绕过统一返回时的序列化,即绕过fastjson序列化。
新代码如下(只列出修改部分):
public void imgUrlToBase64(HttpServletResponse response, @RequestParam("imageUrl") String imageUrl){
UrltoBease64Vo vo = new UrltoBease64Vo();
String base64 = urlToBase64(imageUrl);
vo.setBase64(base64);
StringBuffer stringBuffer = new StringBuffer( 20480);
stringBuffer.append("{\"OptCode\":0,\"Data\":{\"Base64\":\"").append(base64).append("\"}}");
try {
response.getWriter().write(stringBuffer.toString());
response.getWriter().flush();
response.getWriter().close();
}catch (IOException e) {
//....
}
}
再次尝试有问题的报文请求接口,接口正常返回,至此问题解决。
总结
fastJson对大对象序列化存在问题。