欢迎来到「我是真的狗杂谈世界」,关注不迷路 背景 最近一个项目上线前QA对压测结果不是很满意(并且表示之前的项目压测结果也都不理想),于是我在上线后(上线前是肯定来不及
欢迎来到「我是真的狗杂谈世界」,关注不迷路
背景
最近一个项目上线前QA对压测结果不是很满意(并且表示之前的项目压测结果也都不理想),于是我在上线后(上线前是肯定来不及了)开始了一轮性能测试、排查和优化,记录一下整个过程。
思考过程
基础信息
QA同学提供的压测环境与结论:
- 施压侧:
- 并发度100~300;
- 持续运行120~300秒;
- 结果表现:
- 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%响应耗时;
- 并发度越高,效果越明显(在可承受的并发度范围内)。
我也对带上不同程度业务逻辑的场景进行了测试;
简单逻辑下缓存配置较比不缓存能带来:
复杂逻辑下缓存配置较比不缓存能带来:
换运行模式
如果前面两者都不可用或者不能满足需要的时候,可以考虑更换当前这种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上下,但最后几秒会出现本地端口用尽的问题。