欢迎来到《我是真狗说世界》,注意不要迷路背景最近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);核心问题是性能,压力测试等相关概念可以参考《基础11.什么是性能》QPS性能低;CPU占用率过高,很快就会满载;响应时间太长(90TH以上);目前这三个是关联的,目前看来单个请求对CPU资源的需求比较高,导致小并发下CPU资源占满;随着并发度的增加,系统通过频繁调度分配CPU资源,调度磨损增加(用于处理业务逻辑的比例降低);因此,高CPU占用率也会在一定程度上拖累响应时间和QPS性能。硬件性能存疑方向:发现压测环境结果低于生产环境,且两边环境均为独立集群,因此怀疑两边集群节点的硬件性能侧面是完全不同的;执行过程:由于运行在PHPFPM模式的服务中,FastCGI处理、PHP代码解释执行等可能会造成CPU资源占用和耗时;IO阻塞度:业务逻辑中复杂(多)同步阻塞请求(三方HTTP接口、MySQL、Redis)比较简单(几次)会导致请求响应时间变长,QPS变低;短连接资源:整个服务对三方HTTP接口、MySQL、Redis使用短连接。连接只在请求周期内重用,请求结束后释放,频繁创建和销毁连接也会占用CPU资源,耗时;frameworkwear:新版开发框架投入使用后,对性能磨损的关注度不高。排查方法(尽可能)控制其他变量不变,针对待排查的链路和对象进行多组压测对比,记录分析得出结论。实践与结论定位基准由于QA给出的结论似乎有很多不稳定性,所以我决定在压测环境下基于项目进行一波测试,作为后续对比分析研究的基准。第一波测试资源情况程序情况接口复杂度并发QPSAVG50TH90TH95TH99THMAXCPU(峰值)2C2GPHP-FPM7.4+OPcache新框架+健康检查2980-----802C2GPHP-FPM7.4+OPcache新框架+健康检查2490------90记录第一波测试的数据(因为QPS性能一直太不稳定,其他数据暂时没有关注和记录)。很容易发现,除了我控制的变量之外,肯定还有一个变量。我没有发现的变量影响了压力测试的性能;并且两次测试只有两个变量:测试时间:由于压测服务的串行控制(同一时间点只能执行一个压测任务),以上两波测试是在不同的时间进行的时间点。测试副本:由于压测环境需要大量的配置成本,所以没有单独配置两套压测环境,在上述两波测试之间重新构建了应用副本(重建了服务)。为了进一步考察以上两个变量,我每次都rebuild了多次replica,并且在每次rebuild后每隔一段时间进行多组压测。现象如下:同一个replica(不rebuild)性能稳定(如果是500左右就一直是500左右,如果是1000左右就一直是1000左右),甚至第二天依然稳定;不同副本(rebuild副本)的性能会有波动(比如500变1000,1000变500),但波动切换不一定每次rebuild都会发生。根据以上现象,基本可以排除时间差异,怀疑是replica调度到的节点是硬件或者其他基础设施的差异造成的;我把这个问题反馈给服务器基础架构组,协助排查定位,最后在压测环境中找到了4个节点,其中一个节点的CPU基础频率在1.5~3.5GHz之间动态波动(正常情况下,四个节点都应该为3.5GHz);在他们解决问题的时候,我设法向他们要了一个独立的节点来继续我的测试。下面所有的资源都会这样描述:新节点:代表他们要来的独立节点,本身性能很差,2C性能还不如原来的节点1C;originalnode:表示除异常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号副本Framework+健康检查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;新节点;复制PHP-FPM7.4+OPcache新框架+健康检查号50472104.1210019519720230561001C2G;原始节点;2号副本PHP-FPM7.4+OPcache新框架+健康检查50446110.3100197199297182794新节点在并发1和5之间达到CPU100%,QPS也在这之间达到最高性能(理论上会略高于526),而且性能真的很破。.原节点在并发1和5之间达到100%CPU,最高QPS性能也在这之间达到(理论上会略高于660/624)。当CPU没有被充分利用时,随着并发量的增加,QPS和CPU占用率也会增加,响应时间保持稳定;CPU被充分利用后,随着并发量的增加,QPS先稳定后逐渐下降,响应时间逐渐下降增加。PHP8&Jit资源状态程序状态接口复杂度并发QPSAVG50TH90TH95TH99THMAXCPU(峰值)2C2G;新节点;0号复制PHP-FPM7.4+OPcache新框架+健康检查18311.1311171819158302C2G;新节点;新框架+健康检查18411.041117181988302C2G;新节点;copy0PHP-FPM8.0+OPcache+JIt新框架+健康检查18211.2111718191674382C2G;新节点;copy0PHP-FPM7.4+OPcache新框架+健康检查528715.67134241071682C2G;新节点;第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;OPcache+JIt新框架+健康检查2027271.349410310719218071002C2G;新节点;0号拷贝PHP-FPM7.4+OPcache新框架+健康检查50239200.0420030239849719981002C2G;新节点;No.0复制PHP-FPM8.0+OPCACHE新框架+健康检查5025119519930239548921051002C2G;;;新节节节节点点点点;;号号号号号号副号副号副副副号号号副号副副副副副副FPM7.4+OPcache新框架+健康检查100216454.79403797901120026641002C2G;新节点;复制号0PHP-FPM8.0+OPcache新框架+健康检查100225438.31404746896119325421002C2G;新节点;0号副本PHP-FPM8.0+OPcache+JIt新框架+健康检查100222444.76407744.989210992443100对比PHP8.07.4在业务代码不动时没有直接表现(基于7.4及之前的语法)改进;大部分CPU消耗不在opcache到机器代码的链接中(想想看);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;新节点;copy0PHP-FPM8.0+OPcache+JIt新框架+健康检查2045142.92893949618431002C2G;新节点;copy0PHP-FPM8.0+OPcache+JIt新框架+简单逻辑2027570.91494190;新节点;0号拷贝PHP-FPM8.0+OPcache+JIt新框架+简单逻辑*52026972.799210110518818091002C2G;0号复制PHP-FPM8.0+OPcache+JIt新框架+健康检查50386126.5210319920229619891002C2G;新节点;0号拷贝PHP-FPM8.0+OPcache+J新框架+简单逻辑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新框架逻辑上不同程度的IO100222444.76407744.989210992443100会增加单次请求的响应时间,但对CPU占用率影响不大;高阻塞IO的响应时间一般要高于低阻塞IO;并发度不增加CPU满时,高阻塞IO的QPS性能低于低阻塞IO;当并发增加,CPU满了的时候,高阻塞IO的QPS性能和低阻塞IO几乎是一样的。复杂度并发数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;原始节点;No.2copyPHP-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新框架+简单逻辑+长连接.12644991C2G;原始节点;No.2copyPHP-FPM7.4+OPcache新框架+简单逻辑+长连接100325303.062994965017012205100网络连接资源复用相比非复用可以带来:当前业务特性下35-55%的QPS提升;响应时间减少25-37%框架截断资源情况程序情况接口复杂度并发QPSAVG50TH90TH95TH99THMAXCPU(峰值)1C2G;原始节点;3号副本PHP-FPM7.4+OPcache入口文件直接返回2065052.721126817741001C2G;原始节点;入口文件解析后请求为2059562.721116918321001C2G;原始节点;3号副本PHP-FPM7.4+OPcache自动加载后;;3号加载底层配置后复制PHP-FPM7.4+OPcache2084923.04294949518671001C2G;原始节点;3号副本PHP-FPM7.4+OPcache注册所有请求接口2063030.97295969718991001C2G;原始节点;第3副本PHP-FPM7.4+OPcache健康检查完成处理2051637.623969798187877磨损主要环节:引入自动加载(composerautoload);加载项目配置;加载底层配置。优化方向由短连接改为长连接。将请求MySQL和Redis的连接方式改为长连接,将连接周期从请求层面扩展到FPM进程层面,减少连接创建和销毁操作,降低CPU占用率,减少响应时间,提升整体性能.表现。但是考虑到:MySQL和Redis的连接数会和FPM开启的Work进程数相差无几;多个项目和多个组共享MySQL和Redis;在短连接模式下,QPS过高会导致本地端口耗尽的问题(具体见文末)。决定暂时不使用这个选项。缓存工程和底层配置会在每次请求时读取多个ini配置文件并解析结构在初始化时读取解析然后写回一个php数组文件:避免configmap对性能的影响(原来ini配置都是通过configmap挂载到项目中);省去了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号copyPHP-FPM7.4+OPcache新框架+健康检查+无Cache配置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;原始节点;No.4copyPHP-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等待状态快速恢复复用:无权限,但根据现象观察,应该是60s当我增加CPU资源到2C压测一个简单的逻辑(每次请求都会创建一个MySQL连接,请求结束会主动关闭连接),QPS达到500左右,但是本地端口在最后会耗尽几秒钟。
