Java应用调优实录
本文记录下近期的Java应用调优(线上环境:JDK8 + mysql5.7 + CPU4核 + 8G内存下的调优)
起因
缘自上帝(客户)要求,上帝的要求就是坚持下去的动力,要求如下:
- 各功能TPS要在300以上 (TPS和响应时间、)
- 压测时FGC要满足6小时最多一次,8小时内最多两次
- 压测时FGC单次耗时要尽量在50ms内,最多100ms
- 极限压测状态下TPS要平稳,无报错、异常(压力测试tps性能下降问题解决方案)
- CPU使用在90%以下,负载在10以内。。。
- 数据库无慢sql、异常报错
经过
首先,基于这样的要求首先优化方向就确定了,基本有如下几方面:
- 数据库调优
- 索引优化
- 数据库缓存
- 报错、异常、慢sql
- 配置调整
- JVM调优
- 内存比例调整
- GC收集器调整
- 内存回收、占用分析
- 代码调优
- 增加缓存
- 代码分析
- 硬件调优
- 加CPU
- 加内存
- 加带宽
- 加机器
下面就基于以上各模块进行分析:
数据库优化
效果最明显、最快的优化就是数据库的优化了,应当优先考虑此方面。
索引优化
一般情况下,QPS上不去很有可能跟数据库有关系,开启下慢sql监控看下。根据慢sql结合数据库explain好好分析下扫到的rows,看看是不是很高,通常存在索引优化情况(索引应尽量控制数量,不要加太多,过多可以考虑使用复合索引或调整表结构),QPS都能有大比例的提升,但前提是待有足够的数据量供测试,最好有百万级以上的数据,效果比较明显。
数据库缓存
如果有过多的查询场景,可以适当开启数据库缓存,增加查询性能。
报错、异常
检查sql错误日志是否有异常日志。
配置调整
数据库连接数、超时时间等等数据库配置,可自行百度。
JVM调优
JVM调优对应用的流畅体验还是很明显的,置于调整的合适点还是要观察分析出来的,JVM可调的也就内存比例、垃圾收集器选取,再有就是根据内存回收分析潜在问题,比如内存溢出。
内存比例调整
内存比例调整基本要调整+观察,没有确定的值,没有明确的答案,这里只说不好的情况:
元数据空间:
最好调整一个比实际占用大一点的大小,默认是比较小的,如果大小不够会触发FGC
新老比例:
新生代设置过小会导致YGC耗时短且频率高、老年代很容易满,当然老年代足够大就另说了;
新生代过大YGC耗时会增加;
Eden:Sur比例过大会导致容易进入老年代,过小Eden容易满时YGC会频繁,Sur一般为复制算法应考虑来回复制的开销;
老年代过小FullGC耗时会短且频繁,且有可能溢出;
老年代过大FullGC耗时会过长;
总的来说比例调整要结合调优目标、收集器、服务器配置进行调整,找到一个合适的度即可。
GC收集器调整
选择垃圾收集器要根据实际要求、场景来:
- 想要少量的内存和并行开销选择串行收集器Serial GC;
- 对吞吐量有要求可以使用Parallel GC;
- 需要尽量少的停顿时间使用CMS
- 大的内存处理要求用G1;
- 高版本的JDK的大数据收集器ZGC,致力于停顿时间小于10ms(可以关注);
内存分析
内存分析主要在老年代的分析上,一般对象在创建后生命周期不会过长,正常情况FGC后内存应该基本稳定在一个稳定的比例,偶有浮动,但不能出现持续性的上升情况,如果存在持续性的上升,回收不掉,且回收率越来越大,就应该考虑是不是对象持续性的创建未释放的情况,一般导出FGC前后的堆进行对比,FGC后的堆中的前几名应该就是重要线索,很稳,不稳请留言。
硬件优化
上面优化做完如果离目标还有距离就可以考虑在机器硬件上优化了,当然如果时间充裕的话也可以先考虑代码优化。
加CPU
计算型应用可考虑适当加CPU,CPU负载尽量控制在10以内,线程数调整会有明显的变化(Dubbo性能调优参数及原理)
加内存
内存型应用加内存就成了,加多少根据实际场景权衡
加带宽
网络跟不上带宽调整下
加机器
没有什么问题是加机器不能解决的,如果不行就再加一批
代码优化
如果代码没有bug,依然达不到理想状态,就要有话较多时间的觉悟了,是不是代码设计有问题?如果没有那就不用忘下看了(拜拜啦),如果必须做优化,那以下代码仅供参考,也可参照另一篇优化思路的文章寻找突破口:
缓存
是不是系统中查询都是直接请求数据库的?针对变动不频繁的考虑下加缓存,对于重要的数据,缓存的失效、更新等策略也最好考虑下。
reids基本可作为缓存首选:
- 菜鸟教程redis
- spring cache 学习 —— @Cacheable 使用详解
- redis缓存、失效、更新
- 使用@Cacheable 踩过的坑
- redis中hash和string的使用场景
- springboot 2.x配置redisTemplate及cacheManager、配置RedisTemplate和RedisCacheManager
- Cacheable注解Redis方法时,如果Redis服务器挂了 继续往下走的解决方案
- redis连接池JedisPool的使用
- jedis 与 RedisTemplate 操作比较
- SpringBoot使用redis缓存List < Object >
设计分析
通常没有bug,也没有缓存什么可以优化,就需要考虑是不是功能设计、开发风格的问题了。
对于功能设计可以考虑是不是需要进行拆分、解耦、分布、异步等处理措施
- 是不是业务路径处理过长?
- 是不是与数据源交互次数过多、量过大?
- 是不是CPU计算耗时过度?
- 是不是内存申请过于频繁?
- 是不是数据内容可以压缩?
- 是不是需要加一个缓冲器,如MQ?
- 是不是需要加一些池?
- 是不是有可以避免的重复处理?
- 是不是可以拆分?分模块、分步骤?
- 是不是可以异步?异步一时爽,一直异步一直爽?(异步尽量可控)
- 是不是可以避免锁?避免线程安全问题?
结果
优化之后CPU、内存、GC频率基本都达到了要求,目前单次FGC耗时未满足50\100ms以下,目前最佳状态再300ms,再整就是降老年代内存或者增加CPU核数啦,再往深了挖就是在分析下老年代有没有再可以归置归置的内容了。