一次性能优化:吞吐量从1提升到2500

31次阅读

共计 1729 个字符,预计需要花费 5 分钟才能阅读完成。

性能优化,简而言之,就是在不影响系统运行正确性的前提下,使之运行地更快,完成特定功能所需的时间更短。压测也是检验一个架构设计是否合理的一个重要方法。
项目介绍
这个项目是一个线下支付的交易系统,使用线下设备发起支付。项目使用 SpringMVC+Dubbo 的微服务开发模式,其中 SpringMvc 作为 Web 端部署在 tomcat 上对外提供 rest 服务,其他模块使用 dubbo 开发,SpringMVC 调用 dubbo 服务完成业务功能。
优化目的
这一次的性能优化,目的是在不调整业务逻辑的情况下通过压力测试的方式测试下单时接口的性能能达到多少,如果有性能问题,就要在不修改业务逻辑的情况下通过优化各项参数的方式(包括 JVM 优化、数据库连接数和 Dubbo 连接数)来提升整体性能。
压测前的准备
压力测试策略
采用循序渐进的方式,逐渐增加并发量的方式,先以 1 个并发开始,慢慢增加,当达到瓶颈的时候就进行参数调整和优化。
建立压测分支
压测优化过程中,可能随时需要调整配置参数,可能会对一些配置参数进行修改或者对公共代码进行优化。为了不让其他研发人员的开发受到干扰,在 git 上基于 release 分支建立了 feature_stress 分支,专门用来压测过程中,参数配置调优使用,不影响其他研发人员的开发。
搭建压测环境
为了不不影响研发和测试人员的工作,单独申请了三台 4C32G 的虚拟机进行压力测试,一台部署 tomcat,一台部署 dubbo 服务,一台部署 mysql。为什么只申请单机部署的方式压测呢?因为本次测试的目的是看单台服务器的性能情况,优化的目的是将单台服务器的性能最大化。
准备测试脚本
本次测试选用 jmeter 作为测试工具进行压力测试,由测试人员模拟线下支付的流程写好测试脚本。
初步压测
很保守的,从 1 个并发开始压测,测试结果让人非常的惊喜,压测开始几分钟后,就出现大量的连接失败,无法继续测试。出现大量的内存溢出的异常。没出现异常时的吞吐量达到了 1req/s。同时,通过 jconsole 控制台监控 tomcat,发现其线程数最高到了 2 万多个。
问题分析
这个异常是创建本地线程失败抛出的异常,为什么有这么大量的线程呢?不合常理呀!于是查看对应的代码,原来这是一个自定义的一个人日志 Appender,在这个 Appender 里使用了线程池,这个 Appender 本来的目的是使用多线程提升日志性能,并且将所有的日志都收集到一个文件中。代码类似如下:log4j.properties 中直接将新的 Appender 添加到了 rootLogger 下:
初步优化
通过以上代码和日志配置文件就能看出来,每次日志输出都会创建线程,这就是线程为什么越来越多,最后导致无法创建新的本地线程的原因。
为了不影响现有的功能,将 LogAppender 类 append 方法中的线程池创建的部分变成了类的属性,并固定只用 8 个线程:

提交代码重新部署压测环境,再进行压测,吞吐量马上就上来了,达到了 1700req/s,
][7]
深入调优
经过上面的优化之后,整体的性能还不是很高,经过 jconsole 监控发现,线程数还是很高,虽然没有 2 万那么多了,但还是有 3000 多的线程,这么多的线程根本无法发挥机器的性能,时间都浪费在线程切换上了,通过查看线程栈发现大量的线程都是 dubbo 连接的线程,为什么如此之多呢?查看 dubbo 的配置文件,consumer 的链接数都设置成了 1000,于是将所有 dubbo 的 consumer 连接数均改为了 64。同时将 log4j.properties 中配置了控制台输出的都关闭了。
最终的优化后的测试结果,最高达到了 2600req/s。
总结
性能优化需要从几个方面考虑:

CPU 是否有瓶颈,本项目没有大量的计算,所以 CPU 没有瓶颈。
IO 是否是瓶颈,线程太多或者连接太多都是瓶颈,本项目中并发时创建了大量的线程,达到了服务器的最高可用的连接数,导致内存溢出了,创建太多的线程也会让 CPU 把时间都浪费在线程切换上了。
生产环境不要使用控制台输出,因为控制台输出是同步的,输出太多会对性能有很大的影响。
如果出现吞吐量小的情况可以输出线程栈,看看到底是 block 在哪里了,是调用服务时间长还是读写数据库时间长。

———— / END / ————

正文完
 0