事故过程
昨天我做的一个需求上预发布,刚上,准备让测试同学验证一下,就发现列表页刷不出来,很快,CPU飙升,报警邮件发个不停。
我当时没有怀疑是我做的功能的问题,因为我做的这个功能在测试环境已经验证过了。
但是后来leader导出堆栈日志,把导致CPU飙升的方法发出来,发现就是我写的一个功能,当时的感觉是惊呆了。
事故分析
我拿到堆栈日志,是如下一个bin文件
导入到MAT中(eclipse 的一个插件 memory analyzer).
下图是堆栈报告的一个概览。
Biggest Objects by Retained Size: 这项显示的是,堆栈日志中对象的大小分部。
Actions下面有四项:
Histogram:这项显示的是每个类对象的数量列表
Dominator Tree: 列出最大的对象和它们保存的东西
Top Consumers:按照类和包分类打印出“最昂贵”的对象。
Duplicate classes:检测被多个类load的类
Reports下面有两项:
Leak Suspects:包括一个泄露怀疑和系统概览。
Top Components: 列出了大小超过1%的大对象报告
这里我直接点击了Leak Suspects, 查看MAT给我们生成的一个泄露报告。
可以清晰的看到MAT 认为的错误点。
at com.laidian.erp.crm.handler.shopListHandler.ShopApprovalService.listByNewStatus(Lcom/laidian/erp/crm/handler/shopListHandler/ShopContext;)Ljava/util/Optional; (ShopApprovalService.java:86)
根据这个提示 我们找到代码
可以看到错误的代码出现在方法的最后一行。如果两个if条件都不满足,程序就到达最后一行,相当于下面代码。
@Test
public void test2() {
LambdaQueryWrapper<DeviceOperApply> queryWrapper = Wrappers.lambdaQuery();
deviceOperApplyService.list(queryWrapper);
}
我测试了一下,上诉代码对应的sql语句是:
select * from T;
相当于把整张表查出来了。在预发布环境,这张表有65万条数据。测试环境 只有1800条数据。所以在测试环境不会发现问题,在预发布环境,一下子把整张表查询出来,CPU自然会飙升了。
解决方法
将这个方法改成下面这样
事故总结
1. 首先是对MyBatis plus 的 list() 方法要有足够的认识
2. 单元测试要做好,每写一个方法最好都有对应的单元测试。如果这个方法有对应的单元测试用例,打印出对应的sql,这个问题就能在开发阶段就解决
3. 压力测试要做好。很多问题在测试环境不会出错,但是一到预发布,正式就出错了。就是因为压力测试没有做好。测试环境数据量小,很多写法不会出现问题,但是预发布、正式环境,数据量大,用户量也大,就会出现各种问题。
4. 代码QC也可以更加仔细一点。