Skip to content

Latest commit

 

History

History

chapter13

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

欢迎来到「我是真的狗杂谈世界」,关注不迷路

背景

最近一个项目上线前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提供的结论看起来存在很多不稳定性,因此我决定在压测环境上基于该项目先进行一波测试,作为后续对比、分析、研究等排查工作的基准。

第一波测试

资源情况 程序情况 接口复杂度 并发度 QPS AVG 50TH 90TH 95TH 99TH MAX CPU(峰值)
2C2G PHP-FPM7.4+OPcache 新框架+健康检查 2 980 - - - - - - 80
2C2G PHP-FPM7.4+OPcache 新框架+健康检查 2 490 - - - - - - 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接口请求等);

继续测试

资源情况 程序情况 接口复杂度 并发度 QPS AVG 50TH 90TH 95TH 99TH MAX CPU(峰值)
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 1 162 5.63 6 6 6 8 20 31
1C2G;原节点;1号副本 PHP-FPM7.4+OPcache 新框架+健康检查 1 496 1.63 2 2 2 2 217 80
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+健康检查 1 484 1.67 2 2 2 3 138 73
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 5 526 8.66 6 7 40 47 1686 100
1C2G;原节点;1号副本 PHP-FPM7.4+OPcache 新框架+健康检查 5 660 6.43 2 2 72 77 2300 100
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+健康检查 5 624 6.9 2 3 73 78 900 100
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 20 462 41.74 7 93 93 95 1858 100
1C2G;原节点;1号副本 PHP-FPM7.4+OPcache 新框架+健康检查 20 530 36.32 3 96 96 97 2402 100
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+健康检查 20 509 38.11 3 96 97 98 1715 100
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 50 371 129.5 102 200 203 298 2023 100
1C2G;原节点;1号副本 PHP-FPM7.4+OPcache 新框架+健康检查 50 472 104.12 100 195 197 202 3056 100
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+健康检查 50 446 110.3 100 197 199 297 1827 94

  • 新节点在并发1~5之间达到CPU100%,QPS最高表现也在这之间达到(理论上会略高于526),表现确实很拉垮。。。
  • 原节点在并发1~5之间达到CPU100%,QPS最高表现也在这之间达到(理论上会略高于660/624)。
  • CPU未打满时,随着并发度提高,QPS、CPU占用率也随之增长,响应耗时保持稳定;
  • CPU打满后,随着并发度提高,QPS先稳定后逐步降低,响应耗时逐步增大。

PHP8&Jit

资源情况 程序情况 接口复杂度 并发度 QPS AVG 50TH 90TH 95TH 99TH MAX CPU(峰值)
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 1 83 11.13 11 17 18 19 158 30
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache 新框架+健康检查 1 84 11.04 11 17 18 19 88 30
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 1 82 11.2 11 17 18 19 1674 38
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 5 287 15.67 13 42 48 57 1682 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache 新框架+健康检查 5 290 15.66 12 42 48 58 171 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 5 288 15.87 12 42 48 59 1730 100
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 20 272 70.67 94 103 107 192 1740 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache 新框架+健康检查 20 273 70.5 94 103 107 193 2081 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 20 272 71.34 94 103 107 192 1807 100
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 50 239 200.04 200 302 398 497 1998 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache 新框架+健康检查 50 251 195.91 199 302 395 489 2105 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 50 249 197.18 200 302 396 492 2183 100
2C2G;新节点;0号副本 PHP-FPM7.4+OPcache 新框架+健康检查 100 216 454.79 403 797 901 1200 2664 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache 新框架+健康检查 100 225 438.31 404 746 896 1193 2542 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 100 222 444.76 407 744.9 892 1099 2443 100

  • PHP8.0较比7.4在业务代码不动的情况下(基于7.4及之前语法)没有直接的性能提升;
  • CPU消耗大头不在opcache转machine code环节(想想也是);
  • PHP8.0+Jit目前无法带来并发和性能表现的提升。

IO堵塞次数

资源情况 程序情况 接口复杂度 并发度 QPS AVG 50TH 90TH 95TH 99TH MAX CPU(峰值)
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 1 156 2.71 6 6 6 8 198 30
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑 1 97 9.72 10 10 11 13 27 35
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑*5 1 75 12.58 12 13 14 16 1607 27
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+复杂逻辑 1 82 11.2 11 17 18 19 1674 38
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 5 508 9.09 6 8 41 48 1653 99
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑 5 297 15.68 10 44 51 60 2046 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑*5 5 282 16.51 13 34 43 53 1809 98
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+复杂逻辑 5 288 15.87 12 42 48 59 1730 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 20 451 42.92 8 93 94 96 1843 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑 20 275 70.91 94 101 104 191 1740 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑*5 20 269 72.79 92 101 105 188 1809 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+复杂逻辑 20 272 71.34 94 103 107 192 1807 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 50 386 126.52 103 199 202 296 1989 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑 50 247 197.5 199 299 303 404 2079 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑*5 50 246 200.26 199 298 301 400 2067 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+复杂逻辑 50 249 197.18 200 302 396 492 2183 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+健康检查 100 357 273.75 292 399 473.65 510 2089 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑 100 233 424.72 402 602 697 897 2389 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+简单逻辑*5 100 226 438.53 403 604 699 852.98 2417 100
2C2G;新节点;0号副本 PHP-FPM8.0+OPcache+JIt 新框架+复杂逻辑 100 222 444.76 407 744.9 892 1099 2443 100

  • 逻辑中不同程度的IO会导致单次请求的响应耗时增加,但对CPU的占用率影响较小;
  • 请求响应耗时高阻塞IO较比低阻塞IO普遍增加;
  • 并发度未将CPU打满时,高阻塞IO较比低阻塞IO的QPS表现要低;
  • 当并发度提升将CPU打满后,高阻塞IO较比低阻塞IO的QPS表现几乎一致

PDO长连接

资源情况 程序情况 接口复杂度 并发度 QPS AVG 50TH 90TH 95TH 99TH MAX CPU(峰值)
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+短连 1 139 6.56 5 6 6 15 2112 40
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+长连 1 207 4.23 3 4 4 9 1337 45
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+短连 2 241 7.33 5 6 12 33 1077 83
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+长连 2 356 4.62 3 4 6 22 1060 91
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+短连 5 274 16.37 5 66 74 86 1722 100
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+长连 5 427 10.33 3 9 67 73 1293 100
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+短连 20 265 73.19 93 103 196 302 1778 88
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+长连 20 393 49.31 12 97 100 103 1742 95
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+短连 50 253 195.05 198 300 398 599.79 1914 88
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+长连 50 345 142.96 102 200 202 300 2136 92
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+短连 100 241 401.15 397 601 705 1203.1 2644 99
1C2G;原节点;2号副本 PHP-FPM7.4+OPcache 新框架+简单逻辑+长连 100 325 303.06 299 496 501 701 2205 100

网络连接资源复用较比不复用在当前业务特点下能带来:

  • 提升35~55%QPS;
  • 降低25~37%响应耗时。

框架截断

资源情况 程序情况 接口复杂度 并发度 QPS AVG 50TH 90TH 95TH 99TH MAX CPU(峰值)
1C2G;原节点;3号副本 PHP-FPM7.4+OPcache 入口文件直接返回 20 6505 2.72 1 1 2 68 1774 100
1C2G;原节点;3号副本 PHP-FPM7.4+OPcache 入口文件解析完请求 20 5956 2.72 1 1 1 69 1832 100
1C2G;原节点;3号副本 PHP-FPM7.4+OPcache 引入自动加载之后 20 4341 3.61 1 1 2 77 2108 100
1C2G;原节点;3号副本 PHP-FPM7.4+OPcache 加载项目配置之后 20 2278 8.16 1 2 86 89 1101 100
1C2G;原节点;3号副本 PHP-FPM7.4+OPcache 加载底层配置之后 20 849 23.04 2 94 94 95 1867 100
1C2G;原节点;3号副本 PHP-FPM7.4+OPcache 注册全部请求接口之后 20 630 30.97 2 95 96 97 1899 100
1C2G;原节点;3号副本 PHP-FPM7.4+OPcache 健康检查完整处理 20 516 37.62 3 96 97 98 1878 77

磨损主要发生环节:

  • 引入自动加载(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优化就足够)。

我将这个优化做到了新框架的后续版本中,并且压测验证:

资源情况 程序情况 接口复杂度 并发度 QPS AVG 50TH 90TH 95TH 99TH MAX CPU(峰值)
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+不缓存配置 1 480 1.6 2 2 2 2 220 77
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+缓存配置 1 705 1.05 1 1 2 2 1775 79
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+不缓存配置 2 619 2.55 2 2 2 35 2526 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+缓存配置 2 1083 1.54 1 1 2 26 2275 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+不缓存配置 5 659 6.8 2 2 72 77 2409 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+缓存配置 5 1060 4.36 1 2 2 72 2335 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+不缓存配置 20 530 37.09 3 96 97 98 2398 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+缓存配置 20 970 20.18 2 93 94 95 2601 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+不缓存配置 50 469 105.58 100 196 198 231.99 2799 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+缓存配置 50 946 52.22 90 98 99 103 2901 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+不缓存配置 100 415 239.43 202 394 400 598 3000 100
1C2G;原节点;4号副本 PHP-FPM7.4+OPcache 新框架+健康检查+缓存配置 100 920 107.89 100 195 199 289 2609 100

缓存配置较比不缓存对没有连接和阻塞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~60999
  • time wait状态快速回收和重用:没有权限,但根据现象观察应该是60s

当我调大CPU资源为2C压测一个简单逻辑(每个请求会创建一个MySQL连接,请求结束就主动close连接)时,QPS达到500上下,但最后几秒会出现本地端口用尽的问题。