线上六个性能问题案例分享
1、动态条件查询未做非空判断引起CPU占用100%并引发事故
代码示例:
//message为解析外部存管行返回的消息内容并封装成的对象
TppCgbankReq cgbankReq = new TppCgbankReq();
cgbankReq.setBusiness(getTrxCode());
cgbankReq.setOrderNo(message.getPreOrderNo());
List<TppCgbankReq> list = tppCgbankReqService.getAll(cgbankReq);
原因:
一、在正常(业务成功)的情况下,message.getPreOrderNo()有值,在业务失败的情况下,message.getPreOrderNo()为null;
二、tppCgbankReqService.getAll是一个动态条件查询方法,由于未对orderNo做判空处理,结果一次将整个表trxCode符合条件的记录(几十万)都查询出来了并load到堆中;
三、由于堆空间长期紧张,频繁触发full gc引起CPU占用100%。
解决方案:
一、orderNo应该取message.getHeaderOrderNo(),此时不论业务成功或失败message.getHeaderOrderNo()都有值;
二、防御性编码原则,由于orderNo是由外部系统报文解析而成,仍然存在为空的可能,加上非空判断。
2、Rabbitmq由于消费者处理慢引起CPU及内存占用高企
原因:
Rabbitmq采用推模式,当消费者处理速度跟不上生产者速度时,由于未限制qos,消息在消费者端堆积,导致消费者堆空间占用上升,full gc也跟着上升,cpu及内存双紧张。
解决方案:
由于短时间内没办法将消费者速度提高到与生产者速度匹配,故设置qos,限制每次取得的消息数量为10,参考:channel.basicQos(10);。
3、过多的线程导致cpu占用过高
代码示例:
//请求开始
//主业务操作
threadPool.execute(new Runnable() {
@Override
public void run() {
//生成合同
}
});
//请求响应
原因:
一、多线程模型不合理,虽有使用线程池,一些非核心业务起了过多的线程处理,导致cpu占用过高;
二、主业务处理完成后,使用了线程池异步生成合同,由于生成合同是IO密集型操作,高峰期时threadPool很快达到最大线程数;
三、由于threadPool设置了过大值,起过多的线程导致CPU占用过高。
解决方案:
一、短期调整threadPool值为合适大小;
二、长期将非核心异步任务使用MQ异步架构,使用异步消费线程处理;
4、慢SQL引起数据库CPU上升,其他SQL被挂起,系统短时不可用。
原因:
报表慢sql引起阿里云RDS数据库CPU占用100%
解决方案:
1、及时KILL掉该任务;
2、优化慢sql;
3、读写分离,报表走读库。
5、Netty写的TCP服务稳定跑了一个多月后,突然CPU占用100%
原因:
一、ByteBuf未手工释放引起堆外内存缓慢泄露,积累到一定程度后,由于空间不足,引起full gc并引起CPU占用100%;
二、Netty某些旧版本确实也存在其他原因内存泄露的风险。
解决方案:
1、升级Netty到最新稳定版;
2、主动释放手工建立的ByteBuf;
3、日志加上堆外内存报告并打印。
6、连接池设置过小,导致系统响应超时****原因:
一、初时CPU及内存正常,系统响应超时,日志中大量获取数据库连接超时报错;
二、由于核心业务操作接口是同步架构,资产端大量请求积压且频繁超时,造成资金端用户无法正常使用系统。
解决方案:
一、调整连接池,调大最大连接数等;
二、【架构调整】资产端与资金端互相分离;
三、【架构调整】将核心业务操作从同步操作修改为异步架构,引入MQ进行削峰处理。
总结
一般来说,引起CPU占用高的情况有:
1、FULL GC;
2、过多的Thread,线程频繁切换会耗费大量的CPU资源;
3、不合理的循环,包括死循环,循环次数过多等;
4、某些第三方lib等。
一般来说,引起内存占用高的情况有:
1、一次从数据库中查询过多数据,这个可以配合检查慢sql发起;
2、资源使用后忘记释放或未考虑到抛出异常后的释放;
3、大循环中创建过多的对象;
4、使用全局的集合类缓存过多的数据等。
线上排查的过程就是结合top、jstat、jmap、jstack命令验证以上猜想的过程如果线上无法很快定位问题,及时Dump文件到本地,再用MemoryAnalyzer、Jvisualvm等分析。