Chapter 13.一次性能测试和优化过程
我是真的狗我是真的狗 -欢迎来到「我是真的狗杂谈世界」,关注不迷路
背景最近一个项目上线前QA对压测结果不是很满意(并且表示之前的项目压测结果也都不理想),于是我在上线后(上线前是肯定来不及了)开始了一轮性能测试、排查和优化,记录一下整个过程。
思考过程基础信息QA同学提供的压测环境与结论:
施压侧:
并发度100~300;持续运行120~300秒;被压侧:
单副本资源1C2G;副本数1~4;结果表现:
CPU:单副本100%(但有时出现某副本100%,其他副本60~80%左右);内存:单副本15~20%(但有时出现某副本高至50%)QPS:单副本100~120;响应耗时:Avg(487~596ms);50TH(19~27ms);90TH(1867~3099ms);95TH(2409~3902ms);99TH(4460~7002ms);MAX(8589~13298ms);核心问题关于性能、压测等相关概念可参考「Foundation 11.性能是什么」
QPS表现较低;CPU占用过高,很快被打满;响应耗时存在少量(90TH以上)过高;当前这三者是有关联的,当前看来单个请求对于CPU资源需求较高,导致在小并发下CPU资源已占满;随着并发度增高,系统通过频繁调度来分配CPU资源,调度磨损增加(用于处理业务逻辑的比例降低);
因此CPU占用过高也会一定程度拖累响应耗时、QPS表现。怀疑方向硬件性能:之前有发现压测环境结果较比生产环境低,且两侧环境为独立的集群,因此怀疑两侧集群节点本身硬件性能差异较大;执行过程:由于是PHP FPM模式下运行的服务,FastCGI处理过程、PHP代码解释执行过程等都可能造成CPU资源的占用、耗时;IO阻塞度:业务逻辑中复杂(多次)同步阻塞请求(三方HTTP接口、MySQL、Redis)较比简单(少次)会造成请求响应需要时间变长,降低QPS;短连资源:整个服务对三方HTTP接口、MySQL、Redis都采用的短连,连接仅在请求周期内复用,请求结束后释放,对于连接的频繁创建销毁也会占用CPU资源、耗时;框架磨损:新版开发框架投入使用后没有太关注性能磨损问题,理论上这块一定会存在磨损,只是程度和关键磨损点问题。排查方法
(尽量)控制其他变量保持相同的情况下,针对要排查的环节、对象进行多组压测对比,记录并分析得出结论。
实践与结论定位基准由于QA提供的结论看起来存在很多不稳定性,因此我决定在压测环境上基于该项目先进行一波测试,作为后续对比、分析、研究等排查工作的基准。
第一波测试资源情况程序情况接口复杂度并发度QPSAVG50TH90TH95TH99THMAXCPU(峰值)2C2GPHP-FPM7.4+OPcache新框架+健康检查2980------802C2GPHP-FPM7.4+OPcache新框架+健康检查2490------90从第一波测试记录数据(因为QPS表现已发生过于不稳定现象了,其他数据就暂时没有关注和记录)很容易发现,除了我控制住的变量外,一定还存在一个我没发现的变量影响了压测表现;
而这两次测试存在的变量只有两个:
为了进一步排查上述两个变量,我每次多次重建副本,每次重建副本后进行相隔一段时间的多组压测,现象如下:
相同副本(不重建副本)中的表现是稳定的(如果是500上下则一直是500上下,如果是1000上下则一直是1000上下),甚至到第二天仍旧是稳定的;不同副本(重建副本)时的表现会发生波动(比如500变成1000,1000变500),但也不是每次重建都必然发生波动切换。基于上述现象,基本可以排除时间差异,而怀疑副本调度到的节点的硬件或其他基础设施的差异导致;
我将此问题报告给服务器基础设施团队后协助排查定位,最终发现压测环境4个节点其中1个节点的CPU基频在1.5~3.5GHz之间动态波动(正常4个节点都应该是3.5GHz);
在他们解决问题的同时,我管他们要了一个独立节点来继续我的测试,后面所有资源将按照下述描述:
新节点:代表管他们要来的独立节点,性能本身很差,2C表现还不如原节点1C好;原节点:代表除CPU异常节点之外的压测环境节点(理论上与生产环境节点性能仍有差距);X号副本:代表副本重建,相同的X代表副本未经过重建;原节点有意义(由于压测环境节点仍旧会有一些细微差异),新节点无意义(就只有一个节点);健康检查:代表业务上一个接口直接返回,没有业务逻辑和三方请求;简单逻辑:代表业务上一个接口包含一个MySQL查询;简单逻辑*x:代表业务上一个接口包含x个相同的MySQL查询;复杂逻辑:代表该项目业务真实逻辑(一个接口包含2~8次MySQL、Redis、HTTP接口请求等);继续测试资源情况程序情况接口复杂度并发度QPSAVG50TH90TH95TH99THMAXCPU(峰值)2C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查11625.63666820311C2G;原节点;1号副本PHP-FPM7.4+OPcache新框架+健康检查14961.632222217801C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+健康检查14841.672223138732C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查55268.6667404716861001C2G;原节点;1号副本PHP-FPM7.4+OPcache新框架+健康检查56606.4322727723001001C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+健康检查56246.92373789001002C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查2046241.74793939518581001C2G;原节点;1号副本PHP-FPM7.4+OPcache新框架+健康检查2053036.32396969724021001C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+健康检查2050938.11396979817151002C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查50371129.510220020329820231001C2G;原节点;1号副本PHP-FPM7.4+OPcache新框架+健康检查50472104.1210019519720230561001C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+健康检查50446110.3100197199297182794新节点在并发1~5之间达到CPU100%,QPS最高表现也在这之间达到(理论上会略高于526),表现确实很拉垮。。。原节点在并发1~5之间达到CPU100%,QPS最高表现也在这之间达到(理论上会略高于660/624)。CPU未打满时,随着并发度提高,QPS、CPU占用率也随之增长,响应耗时保持稳定;CPU打满后,随着并发度提高,QPS先稳定后逐步降低,响应耗时逐步增大。PHP8&Jit资源情况程序情况接口复杂度并发度QPSAVG50TH90TH95TH99THMAXCPU(峰值)2C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查18311.1311171819158302C2G;新节点;0号副本PHP-FPM8.0+OPcache新框架+健康检查18411.041117181988302C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查18211.2111718191674382C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查528715.671342485716821002C2G;新节点;0号副本PHP-FPM8.0+OPcache新框架+健康检查529015.66124248581711002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查528815.871242485917301002C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查2027270.679410310719217401002C2G;新节点;0号副本PHP-FPM8.0+OPcache新框架+健康检查2027370.59410310719320811002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查2027271.349410310719218071002C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查50239200.0420030239849719981002C2G;新节点;0号副本PHP-FPM8.0+OPcache新框架+健康检查50251195.9119930239548921051002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查50249197.1820030239649221831002C2G;新节点;0号副本PHP-FPM7.4+OPcache新框架+健康检查100216454.79403797901120026641002C2G;新节点;0号副本PHP-FPM8.0+OPcache新框架+健康检查100225438.31404746896119325421002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查100222444.76407744.989210992443100PHP8.0较比7.4在业务代码不动的情况下(基于7.4及之前语法)没有直接的性能提升;CPU消耗大头不在opcache转machine code环节(想想也是);PHP8.0+Jit目前无法带来并发和性能表现的提升。IO堵塞次数资源情况程序情况接口复杂度并发度QPSAVG50TH90TH95TH99THMAXCPU(峰值)2C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查11562.716668198302C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑1979.721010111327352C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑*517512.58121314161607272C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+复杂逻辑18211.2111718191674382C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查55089.096841481653992C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑529715.681044516020461002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑*5528216.51133443531809982C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+复杂逻辑528815.871242485917301002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查2045142.92893949618431002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑2027570.919410110419117401002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑*52026972.799210110518818091002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+复杂逻辑2027271.349410310719218071002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查50386126.5210319920229619891002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑50247197.519929930340420791002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑*550246200.2619929830140020671002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+复杂逻辑50249197.1820030239649221831002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查100357273.75292399473.6551020891002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑100233424.7240260269789723891002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+简单逻辑*5100226438.53403604699852.9824171002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+复杂逻辑100222444.76407744.989210992443100逻辑中不同程度的IO会导致单次请求的响应耗时增加,但对CPU的占用率影响较小;请求响应耗时高阻塞IO较比低阻塞IO普遍增加;并发度未将CPU打满时,高阻塞IO较比低阻塞IO的QPS表现要低;当并发度提升将CPU打满后,高阻塞IO较比低阻塞IO的QPS表现几乎一致PDO长连接资源情况程序情况接口复杂度并发度QPSAVG50TH90TH95TH99THMAXCPU(峰值)1C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+短连11396.56566152112401C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+长连12074.2334491337451C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+短连22417.335612331077831C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+长连23564.62346221060911C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+短连527416.37566748617221001C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+长连542710.3339677312931001C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+短连2026573.19931031963021778881C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+长连2039349.3112971001031742951C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+短连50253195.05198300398599.791914881C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+长连50345142.961022002023002136921C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+短连100241401.153976017051203.12644991C2G;原节点;2号副本PHP-FPM7.4+OPcache新框架+简单逻辑+长连100325303.062994965017012205100网络连接资源复用较比不复用在当前业务特点下能带来:
提升35~55%QPS;降低25~37%响应耗时。框架截断资源情况程序情况接口复杂度并发度QPSAVG50TH90TH95TH99THMAXCPU(峰值)1C2G;原节点;3号副本PHP-FPM7.4+OPcache入口文件直接返回2065052.721126817741001C2G;原节点;3号副本PHP-FPM7.4+OPcache入口文件解析完请求2059562.721116918321001C2G;原节点;3号副本PHP-FPM7.4+OPcache引入自动加载之后2043413.611127721081001C2G;原节点;3号副本PHP-FPM7.4+OPcache加载项目配置之后2022788.1612868911011001C2G;原节点;3号副本PHP-FPM7.4+OPcache加载底层配置之后2084923.04294949518671001C2G;原节点;3号副本PHP-FPM7.4+OPcache注册全部请求接口之后2063030.97295969718991001C2G;原节点;3号副本PHP-FPM7.4+OPcache健康检查完整处理2051637.623969798187877磨损主要发生环节:
引入自动加载(composer autoload);加载项目配置;加载底层配置。优化方向短连改长连将请求MySQL、Redis的连接模式改成长连接,将连接周期由请求级别延伸至FPM进程级别,以此减少连接创建销毁操作降低CPU占用、减少响应耗时提升性能整体表现。
但考虑到:
MySQL、Redis连接数将几乎与FPM开启的Work进程数量一致;多项目、多组共用MySQL、Redis;短连模式下,QPS过高反而会带来本地端口用尽的问题(具体见本文最后)。决定暂时不采用此方案。
缓存项目与底层配置将原先每次请求都读取多个ini配置文件并解析结构改造成初始化时读取并解析然后回写为php数组文件:
避免config map的性能影响(原先这些ini配置都是通过config map挂载进项目);节省ini配置解析成本(php文件直接借用php本身opcache、jit优化就足够)。我将这个优化做到了新框架的后续版本中,并且压测验证:
资源情况程序情况接口复杂度并发度QPSAVG50TH90TH95TH99THMAXCPU(峰值)1C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+不缓存配置14801.62222220771C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+缓存配置17051.0511221775791C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+不缓存配置26192.552223525261001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+缓存配置210831.541122622751001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+不缓存配置56596.822727724091001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+缓存配置510604.361227223351001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+不缓存配置2053037.09396979823981001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+缓存配置2097020.18293949526011001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+不缓存配置50469105.58100196198231.9927991001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+缓存配置5094652.2290989910329011001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+不缓存配置100415239.4320239440059830001001C2G;原节点;4号副本PHP-FPM7.4+OPcache新框架+健康检查+缓存配置100920107.891001951992892609100缓存配置较比不缓存对没有连接和阻塞IO的逻辑能带来:
提升46~121%QPS;降低34~54%响应耗时;并发度越高,效果越明显(在可承受的并发度范围内)。我也对带上不同程度业务逻辑的场景进行了测试;
简单逻辑下缓存配置较比不缓存能带来:
提升9~14%QPS;降低9~12%响应耗时;复杂逻辑下缓存配置较比不缓存能带来:
提升4~17%QPS;降低5~14%响应耗时;换运行模式如果前面两者都不可用或者不能满足需要的时候,可以考虑更换当前这种FPM的运行模式,改用Cli+异步模型:
天生可以使用MySQL、Redis、HTTP等连接池技术,又不需要当心连接数过多的问题;进一步减少代码解释执行过程的消耗,Opcache和Jit技术可以进一步发挥作用;异步搭配yield/Fiber,或是swoole封装的异步库充分利用CPU,提升单接口响应效率。但这样无疑是存在较高的成本和风险的:
现有的框架、库包都是基于同步阻塞封装的,异步可能带来并发争竞隐患;FPM同步阻塞模型牺牲一定性能带来的是开发效率和门槛的优势,如果换成异步模型,对开发人员和业务无疑会带来更高的门槛和风险;类似swoole+异步协程其实几乎都相当于换了大半个语言了。整体结论目前1C2G在压测环境一般的逻辑都可以达到250甚至更高了,暂时满足我们的业务需求(更高的性能表现可以通过扩展副本来实现);还有很多的优化方案和空间,但考虑到团队目前的情况和方案的成本与风险,暂时不开展这类方案。短连单副本QPS过高的问题当前的系统设置为:
本地可用客户端端口范围:32768~60999time wait状态快速回收和重用:没有权限,但根据现象观察应该是60s当我调大CPU资源为2C压测一个简单逻辑(每个请求会创建一个MySQL连接,请求结束就主动close连接)时,QPS达到500上下,但最后几秒会出现本地端口用尽的问题。
php介绍
PHP即“超文本预处理器”,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。