写点什么

【优化技术专题】「系统性能调优实战」终极关注应用系统性能调优及原理剖析(下册)

作者:浩宇天尚
  • 2022 年 1 月 31 日
  • 本文字数:4177 字

    阅读完需:约 14 分钟

【优化技术专题】「系统性能调优实战」终极关注应用系统性能调优及原理剖析(下册)

前提介绍

承接上文:【优化技术专题】「系统性能调优实战」终极关注应用系统性能调优及原理剖析(上册)之后我们接下来进行相关的。

流程相关分析优化

  1. 通过 access_log.txt 日志分析,在特定时间段内,将请求至系统的 url 分组计数,最后会出一个根据 url 调用次数的排序;

  2. 针对请求次数数一数二的 url 接口,在分析完业务场景后,决定将高频率的接口优化成同步转异步;

  3. 想查看 Server 端每个 Http 请求的耗时时间,却发现没有相关功能点统计,于是乎又新增切面 Aspect 来统计 Client 请求至 Server 的耗时统计日志,然后通过 less,grep,awk 命令来分析耗时最高的 url 请求,后续可做成监控及时发现长耗时请求;

  4. 同样发现请求下游系统也没有相关耗时统计,因此依葫芦画瓢再次新增请求下游系统切面来统计耗时情况;

  5. 在压测过程中,效果显著,单台服务器的 TPS 提升了 3 倍,在压测过程不断打出线程 dump, 堆 dump 日志。

  6. 通过和前端沟通,将一些请求进行了进行了聚合处理返回给前端,在某种程度上友好的提升了交互体验;

  7. 针对支付场景的支付状态同步新增了补偿机制,后台主动查询下游系统订单的支付状态,最大力度的保证用户看到的支付状态是准实时的;

  8. 针对 Http 的工具类,根据业务的实际情况定制一套超时参数,尽量减少 Http 连接长时间被 Hold 住不释放,必要时 Http 请求工具类做相应的尝试次数控制;

  9. 最后的压测也比较简单,通过写程序操控状态扭转,将整个呼叫流程走完,程序只需要传入人数来压测系统最高负荷;

  10. 最后一次统计的数据得知 TPS 相比最初提升了至少 5 倍以上,由此可见流程的优化和日志的优化对于系统的性能提升有很大的帮助;

数据库相关分析优化

收集产线出问题时刻的数据库指标

  • max_used_connections

  • max_user_connections

  • max_connections


还收集了数据库连接超时时间,出问题时刻数据库实例前后几个小时的时序分析图;

后台应用的连接池的参数配置为:

尝试步骤一

   (max_connections=160, max_user_connections=80, 两台1核2G的服务器)   tomcat.initialSize=25   tomcat.validationInterval=25000   tomcat.maxActive=85   tomcat.minIdle=25   tomcat.maxIdle=25
复制代码


结论:系统部署重启后直接启动不起来,然后继续调整连接池参数;

尝试步骤二:

(max_connections=160, max_user_connections=80, 两台1核2G的服务器)   tomcat.initialSize=12   tomcat.validationInterval=25000   tomcat.maxActive=85   tomcat.minIdle=12   tomcat.maxIdle=12
复制代码


结论:系统能正常启动,相对于步骤一来说,连接池的合理配置非常重要,但是在压测情况下发生了一些异常;


Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: User hmily already has more than 'max_user_connections' active connections,于是乎继续后续步骤;
复制代码

尝试步骤三

   (max_connections=160, max_user_connections=80, 两台1核2G的服务器)   tomcat.initialSize=12   tomcat.validationInterval=25000   tomcat.maxActive=80   tomcat.minIdle=12   tomcat.maxIdle=12
复制代码


结论:相对于步骤二来说,异常还是有一些,数据库的 160 刚刚等于 tomcat.maxActive * 2 = 160, 但是又引发了另外一个异常:


   Cause: org.apache.tomcat.jdbc.pool.PoolExhaustedException: [http-nio-0.0.0.0-40009-exec-91] Timeout: Pool  empty. Unable to fetch a connection in 5 seconds, none available[size:40; busy:3; idle:37; lastwait:5000];
复制代码

尝试步骤四

   (max_connections=160, max_user_connections=80, 两台1核2G的服务器)   tomcat.initialSize=12   tomcat.validationInterval=25000   tomcat.maxActive=80   tomcat.minIdle=12   tomcat.maxIdle=12
复制代码


结论:异常还是有一些,数据库的 160 刚刚等于 tomcat.maxActive * 2 = 160,但是还是会引发了另外一个异常:


   Cause: org.apache.tomcat.jdbc.pool.PoolExhaustedException: [http-nio-0.0.0.0-40009-exec-91] Timeout: Pool   empty. Unable to fetch a connection in 5 seconds, none available[size:40; busy:3; idle:37; lastwait:5000];
复制代码


无法计算每个 SQL 的耗时情况,于是集成了 SqlMapClientTemplate 类,在 SqlMapClientTemplate 的扩展类,中针对 queryForObject、queryForList、update、delete 主流的方法进行了 before/after/finally 增强处理打印 SQL 耗时;

尝试步骤五

   (max_connections=160, max_user_connections=80, 两台1核2G的服务器)   tomcat.initialSize=12   tomcat.validationInterval=25000   tomcat.maxActive=40   tomcat.minIdle=12   tomcat.maxIdle=12
复制代码


结论:通过将 maxActive 改回 40, 且对耗时的 SQL 进行优化处理,同时对一些不常变化的 SQL 配置进行了缓存处理,这样一来在压测高并发的情况下,所有情况全部有所好转,一切正常自如;


但是我远不满足于此,然后我又开始调整连接池参数配置;

尝试步骤六

   (max_connections=320, max_user_connections=160, 两台1核2G的服务器)   tomcat.initialSize=12   tomcat.validationInterval=25000   tomcat.maxActive=80   tomcat.minIdle=12   tomcat.maxIdle=12
复制代码


结论:之前出现的异常也没有了,于是我综合以上我的各种尝试,实际测试总结了一些关于个人理解的参数配置;

小结:

对于连接池这块,那就是对应用最适合的配置。而且大家在配置的时候,还得综合考虑服务器水平扩展后对数据库的一个压力情况,调整的时候一定要参照数据库实例的参数配置来综合考虑衡量应用的参数配置。


  • 初始化连接: 连接池启动时创建的初始化连接数量:tomcat.initialSize=12


原因:线程快照图最小连接数为 22,目前由于业务量大,仅仅只是 2 台服务器,考虑到后续的业务还会继续扩大,因为该参数不适宜调整的太高,根据实际情况最后调整为 12。


  • 避免过度验证,保证验证不超过这个频率——以毫秒为单位:tomcat.validationInterval=25000


原因:数据库的连接超时时间是 29 秒,我们尽可能小于数据库的连接超时时间


  • 最大活动连接: 连接池在同一时间能够分配的最大活动连接的数量,如果设置为非正数则表示不限制:tomcat.maxActive=50


原因:线程快照图的最大连接数为 82,目前产线只有 2 台服务器,最大也就是 50 * 2 = 100,还远远小于 max_user_connections 数值,最初是滴测试情况调整的为 40,而产线的服务器性能绝对比测试环境好,综合评估定位 50。


  • 最小空闲连接: 连接池中容许保持空闲状态的最小连接数量, 低于这个数量将创建新的连接, 如果设置为 0 则不创建.默认与 initialSize 相同:tomcat.minIdle=12


原因:考虑和 initialSize 调整为一样的


  • 最大空闲连接: 连接池中容许保持空闲状态的最大连接数量, 超过的空闲连接将被释放, 如果设置为负数表示不限制:tomcat.maxIdle=12


原因:考虑和 minIdle 一样

内存使用情况分析优化

  1. 通过 awk 命令分析 gc 日志文件,发现老年代还有较多的空间可以利用,于是就是看看哪些是可以不经常变化的配置,尽量减少数据库 IO 的操作,最大程度先去读取缓存,缓存没有再去从数据库中加载数据。

  2. 在压测的过程中,尽量多一些 headDump 文件,然后通过 eclipse 工具来分析看看都有哪些大对象,也没有发现任何有疑点的地方,于是反查代码,在一些定时任务需要加载大量数据的地方,当加载出来的数据用完之后直接手动释放,尽快让垃圾回收期回收内存。

  3. 还有一点就是,在一些后台定时轮询的任务中,有些任务需要通过 for 循环来处理一些任务,这个时候我们可以每循环一次或者循环数次之后通过调用 Thead.sleep(xxx)休眠一下,一是可以缓冲一下 IO 高密度的操作,还有就是让出 CPU 时间片,让有些紧急的任务可以优先获取 CPU 执行权。

  4. 做完这些后,压测后通过 GC 日志分析,老年代内存相比未优化前多了一些,但是性能吞吐率却很大的得到了提高,磁盘 IO 的 TPS 也在一定程度上降低了。

JVM 参数分析调优

  1. 为了应对将来的高业务量,目前需要扩容服务器,将 2 台服务器扩容至 4 台服务器,然后将服务器由 2 核 4G 升级成为 4 核 8G。因此在升级过程中对于参数的调整也存在了一定的迷惑期。

  2. JVM 参数的调整测试过程一两句话也说不清,这里我就讲解一下大致的思路:

  3. YGC 的平均耗时,YGC 的平均间隔,可以通过 GC 日志完整分析出来,根据实际情况是否需要调整堆大小,年轻代占比,存活区占比;

  4. FGC 的平均耗时,FGC 的平均间隔,同样可以通过 GC 日志完整分析出来,重点关注 FGC 耗时,想办法调整堆大小,年轻代占比,存活区占比,垃圾回收器方式;

  5. S2 区的使用占比,如果 S2 占比为 0,且 YGC 平均耗时也在 40ms 以内的话,也没有 FGC,这也算是相对比较理想的情况;

  6. S2 区满的话,FGC 频繁或者 GC 效果很差时建议调整堆大小,还得不到改善就要开发分析实际 Heap 消耗的对象占比了;

4 核 8G 的 JVM 参数情况如下:

-Xms2048M -Xmx2048M -Xss256k-XX:NewSize=512m -XX:MaxNewSize=512m -XX:SurvivorRatio=22-XX:PermSize=256m -XX:MaxPermSize=512m-XX:+UseParNewGC -XX:ParallelGCThreads=4 -XX:+ScavengeBeforeFullGC-XX:+CMSScavengeBeforeRemark -XX:+UseCMSCompactAtFullCollection-XX:CMSInitiatingOccupancyFraction=60 -XX:CMSInitiatingPermOccupancyFraction=70-XX:+PrintGCApplicationConcurrentTime -XX:+PrintHeapAtGC -XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=logs/oom.log -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps-verbose:gc -Xloggc:logs/gc.log -Djava.net.preferIPv4Stack=true

GC 优化的一点建议:

  • Minor GC 执行非常迅速 ( 建议 50ms 以内 )

  • Minor GC 没有频繁执行 ( 建议大约 10s 执行一次 )

  • Full GC 执行非常迅速 ( 建议 1000ms 以内 )• Full GC 没有频繁执行 ( 建议大约 10min 执行一次 )

TCP/Tomcat 参数分析调优

  1. 由于很大提升系统应用的请求并发,因此需要调整 Tomcat 中相关的参数,maxThreads、acceptCount(最大线程数、最大排队数),也就是说需要查看 tomcat 的 Connector、Executor 所有属性值;

  2. 在压测过程中,发现大量的 TIME-WAIT 的情况,于是根据实际调整系统的 TCP 参数,在高并发的场景中,TIME-WAIT 虽然会峰值爬的很高,但是降下来的时间也是非常快的,主要是需要快速回收或者重用 TCP 连接。

  3. 最后附上我的 4 核 8G 的 TCP 参数情况如下:


   vim /etc/sysctl.conf   #编辑文件,加入以下内容   net.ipv4.tcp_syncookies = 1   net.ipv4.tcp_tw_reuse = 1   net.ipv4.tcp_tw_recycle = 1   net.ipv4.tcp_fin_timeout = 30   #然后执行 /sbin/sysctl -p 让参数生效。
复制代码


发布于: 刚刚阅读数: 2
用户头像

浩宇天尚

关注

🏆InfoQ写作平台-签约作者🏆 2020.03.25 加入

【个人简介】酷爱计算机科学、醉心编程技术、喜爱健身运动、热衷悬疑推理的“极客达人” 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、微服务/分布式体系和算法设计等

评论

发布
暂无评论
【优化技术专题】「系统性能调优实战」终极关注应用系统性能调优及原理剖析(下册)