web服务挂了:排查问题,访问服务web端出现502,前端vue项目能正常访问,说明nginx服务没挂,后端服务可能挂了,打开日志打印,没有打印了,确定是服务挂了。
一、查看日志文件,这个接口多次出现gc
Url:/collect/import/area
Failed to complete processing of a request java.lang.OutOfMemoryError: GC overhead limit exceeded
- 检查代码,调用频繁的接口,是否存在对象未释放,导致内存泄漏;(调用量大的是excel导入接口,下载excel数据接口)
- 线上抓包,利用Mat内存解析工具,排查是否存在大对象;
查看代码
- 导入excel接口/collect/import/area,excel文件解析,使用了poi包workbook对象,未进行释放;
public TcoCollectExcelMsgDto analysisExcelFileToList(File file) {
checkExcelFile(multipartFile);
InputStream in = null;
Workbook workbook = null;
try {
in = new FileInputStream(file);
workbook = WorkbookFactory.create(in);
} catch (Exception e) {
AssertU.isTrue(false, "文件读取失败", e);
} finally {
Tools.release(in);
}
return analysis(workbook);
}
修改为
public TcoCollectExcelMsgDto analysisMultipartFileToList(MultipartFile multipartFile) {
checkExcelFile(multipartFile);
TcoCollectExcelMsgDto result = null;
InputStream in = null;
Workbook workbook = null;
try {
in = multipartFile.getInputStream();
workbook = WorkbookFactory.create(in);
result = analysis(workbook);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
Tools.release(workbook, in);
}
return result;
}
也可以是这种写法,只需要把要关闭资源的代码放入try语句中即可,JDK8开始已经不需要我们再手动关闭资源。
/**
* 解析前端传入的excel表格中数据
* @param multipartFile
*/
public TcoCollectExcelMsgDto analysisMultipartFileToList(MultipartFile multipartFile) {
checkExcelFile(multipartFile);
TcoCollectExcelMsgDto result = null;
try (InputStream in = multipartFile.getInputStream();
Workbook workbook = WorkbookFactory.create(in)) {
result = analysis(workbook);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return result;
}
- 下载文件,new文件流,没有trycatch,也可能会存在未进行释放风险;
public String realOssPathByTemplate(String fileName, String templateName, List<?> dataList){
//创建流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//从oss读取模板文件
InputStream inputStream = ossClientUtil.downloadFile(templateName);
ExcelWriter excelWriter = EasyExcel
.write(bos)
.withTemplate(inputStream)
.build();
WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
excelWriter.write(dataList, writeSheet);
excelWriter.finish();
excelWriter.close();
release(inputStream);
return getOssPath(bos,fileName);
}
优化后
public String realOssPathByTemplate(String fileName, String templateName, List<?> dataList){
ByteArrayOutputStream bos = null;
InputStream inputStream = null;
ExcelWriter excelWriter = null;
String ossPath = null;
try {
//创建流
bos = new ByteArrayOutputStream();
//从oss读取模板文件
inputStream = ossClientUtil.downloadFile(templateName);
excelWriter = EasyExcel
.write(bos)
.withTemplate(inputStream)
.build();
WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
excelWriter.write(dataList, writeSheet).finish();
ossPath = getOssPath(bos, fileName);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
excelWriter.close();
release(inputStream);
}
return ossPath;
}
- 下载文件,存在个别new文件流,未进行释放;
@ApiOperation("错误信息导出”)
@GetMapping("/export/errorinfo”)
public R errorInfo(String guid) {
if (StringUtils.isBlank(guid)) {
AssertU.isTrue(false, "guid不能为空!”);
}
//从redis中取出错误信息
List<String> msgList = redisUtil.get(StaticValue.DECLARE_ERROR_PREFIX + ":" + guid, List.class);
if (CollectionUtils.isEmpty(msgList)) {
AssertU.isTrue(true, "暂无错误信息!”);
}
List<DeclareTmpMsgData> msgDataList = new ArrayList<>();
for (String msg : msgList) {
DeclareTmpMsgData msgData = new DeclareTmpMsgData();
msgData.setMsg(msg);
msgDataList.add(msgData);
}
String fileName = "基地档案导入报错提示_" + DateTime.getCurrentDate_YYYYMMDD() + ".xlsx”;
//创建流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
EasyExcel.write(new BufferedOutputStream(bos), DeclareTmpMsgData.class).sheet("errorinfo").doWrite(msgDataList);
return R.data(getOssPath(bos, fileName));
}
优化后
@ApiOperation("错误信息导出”)
@GetMapping("/export/errorinfo”)
public R errorInfo(String guid) {
if (StringUtils.isBlank(guid)) {
AssertU.isTrue(false, "guid不能为空!”);
}
//从redis中取出错误信息
List<String> msgList = redisUtil.get(StaticValue.DECLARE_ERROR_PREFIX + ":" + guid, List.class);
if (CollectionUtils.isEmpty(msgList)) {
AssertU.isTrue(true, "暂无错误信息!”);
}
List<DeclareTmpMsgData> msgDataList = new ArrayList<>();
for (String msg : msgList) {
DeclareTmpMsgData msgData = new DeclareTmpMsgData();
msgData.setMsg(msg);
msgDataList.add(msgData);
}
String fileName = "基地档案导入报错提示_" + DateTime.getCurrentDate_YYYYMMDD() + ".xlsx”;
//创建流
ByteArrayOutputStream bos = null;
BufferedOutputStream bufferedOutputStream = null;
String ossPath = null;
try {
bos = new ByteArrayOutputStream();
bufferedOutputStream = new BufferedOutputStream(bos);
EasyExcel.write(bufferedOutputStream, DeclareTmpMsgData.class).sheet("errorinfo").doWrite(msgDataList);
ossPath = easyExcelFileCreator.getOssPath(bos, fileName);
} finally {
Tools.release(bufferedOutputStream);
}
return R.data(ossPath);
}
利用内存解析工具
图片上传有问题...
内存分析工具,并未发现有大对象。考虑未在服务挂掉前dump文件,可能导致内存分析不是有效的。
优化代码后,重新打包发布,服务恢复正常。
总结:
- 对于文件流,或文件相关的初始化(代码层次,implements Closeable,实现接口Closeable),使用结束后,都要进行释放流操作,并且要严格遵守try-catch-finally。
- 对于代码不严谨,使得项目存在内存溢出的隐患,在测试环节不容易能测试出来,发布到线上,并发量大了,就会出现服务挂掉问题,怎样能够避免此类严重风险,成为考虑的重点。
我认为可以做到2点,第一,后端人员每个人的代码水平都不一样,需要定期对项目所用到的知识点讲解说明;第二,需要有经验的后端人员,对每一轮迭代的代码进行code review,排除代码风险隐患;
- 考虑Java进程出现问题的时候可能已经被系统kill掉。因此我们需要配置JVM,让他能够在特定时间点自动生成heap dump。
conf文件中配置
在OOM的时候生成heap dump:
-XX:+HeapDumpOnOutOfMemoryError
文件生成的路径需要配置
-XX:HeapDumpPath=/yuanben/dumps/heapdump.hprof
操作linux用到的命令:
jps
jstat -gc pid 5000
free -m
jmap -dump:live,format=b,file=/yuanben/farm-web123.bin pid
或者
jcmd pid GC.heap_dump /yuanben/dumps/heapdump.hprof
tail -f /yuanben/xx.log
kill -9 pid
配置:服务器,2核4G,该web服务分配了512M内存-Xmx512m
工作中的每一次困难,都是一次成长
每个人工作都不一定是一帆风顺的,当在工作中遇到困难时,我们应该积极地去面对它,战胜它。因为每次战胜困难的过程都是自我学习的过程,它能给予我们知识力量,能够让我们积累经验,能够促使我们更加strong。
网友评论