Jason Pan

一个HTTP接口的压力测试

潘忠显 / 2020-11-25


测试目的:

在不同网络情况下(HTTP服务器本机压测、局域网压测、广域网压测)确定接口支持的最大并发用户数,分析其性能瓶颈。

环境说明

Django版本 1.11.29

Python版本 2.7.16

wrk版本:Copyright (C) 2012 Will Glozer

服务器信息

Intel NUC10i7FNH 6核12线程 DDR4 2666 16GB

压测机信息

Macbook Quad-Core Intel Core i7 4核 8线程 16GB

网络延迟

连接情况 round-trip min/avg/max/stddev
服务器 ping 服务器 0.042/0.045/0.058/0.007 ms
压测机局域网 ping 服务器(Wi-Fi) 1.789/5.264/11.558/3.654 ms
压测机广域网 ping 服务器(Wi-Fi) 6.126/8.914/12.447/2.022 ms

系统内核参数

net.core.somaxconn = 128

net.ipv4.ip_local_port_range = 32768 60999

net.ipv4.tcp_tw_reuse = 1

测试工具

wrk

选择wrk是因为wrk有一个很好的特性就是能用很少的线程压出很大的并发量,通过异步网络 io 提升并发量,所以网络通信不会阻塞线程执行.

前置分析

  1. 将测试机与服务器分离,有效模拟真实用户情景。且将服务器部署于较干净的独立主机,有助于获得更为精准的数据。
  2. 本接口测试为短链接压测,考虑到过多的短连接释放会导致系统中存在大量处于“TIME_WAIT”状态的tcp端口,而默认状态下,不允许将TIME-WAIT sockets重新用于新的TCP连接,这会导致动态端口分配时出现抢占现象,进而影响性能。故在压测前,将其打开,即设置net.ipv4.tcp_tw_reuse = 1。
  3. 压测过程不启用过多线程,一方面系统中可允许线程数受限于ulimit -n,过多回引发“Too many open files”错误;另一方面,过多的线程数,反而会由于线程切换而影响性能。

服务器本地压测

持续时间5m

wrk线程数/连接数 1/1 1/4 4/4 12/12 10/100 1/200 1/400 1/600 1/1000 1/3000 1/1万 1/2.5万
Django CPU 150% 150% 150% 150% 150% 150% 150% 150% 150% 150% 150% 150%
QPS 1423 1366 1365 1363 1361 1361 1360 1359 1357 1357 1357 1356
流量Mbps 2.90 2.78 2.78 2.78 2.78 2.78 2.78 2.78 2.78 2.77 2.77 2.76
最大耗时ms 9.11 12.63 11.93 215.52 1.67s 1.67s 1.68s 1.86s 1.93s 1.87s 1.86s 1.74s
90%分位耗时ms 2.82 3.50 3.49 9.06 9.44 9.43 9.43 9.51 9.55 9.53 9.49 9.54
99%分位耗时ms 3.84 4.13 4.23 9.99 16.97 11.26 10.78 217.07 423.66 217.20 48.74 217.45
平均耗时ms 0.7 2.88 2.88 8.35 11.70 11.49 10.38 15.40 18.35 15.37 12.85 15.77
错误率 0 0 0 0 0 0 0(开始少量出现错误) 0 0 1.2% 3.6% 10%(压垮)

结论:

  1. 本机压测网络延迟极小<==>wrk统计1连接和多连接压测的QPS基本上相同
  2. 服务极限的处理能力在QPS 1400左右
  3. 由于Python全局解释器锁的原因,导致通常拉起的Django服务进程无法充分的利用CPU,CPU达到150%的之后不再继续上升。
  4. wrk CPU使用率在5%左右,没有必要使用多线程
  5. 比较wrk 1线程1连接情况与4线程4连接的情况,都是每个线程处理一个连接,可以认为wrk增加连接数对其本身产生的影响不大,但并发增加之后QPS下降,说明Django随着并发数增加,处理请求的能力会下降;但影响不大
  6. 比较wrk 4线程4连接情况与12线程12连接的情况,可以看出并发影响Django的处理能力有限,处理能力会稳定在一个水平,但最大耗时会显著增长
  7. 比较wrk多线程多连接情况,可以看出处理能力能稳定在同等水平,耗时相对增加,合理
  8. 随着连接数不断增加,当到达1线程/400连接时,开始少量出现错误;当连接数达到3000时,错误率上升至1.2%;当连接数持续增加至25000时,错误率上升至10%,系统被压垮

局域网压测

wrk线程数/连接数 1/1 1/4 1/10 1/20 1/50 1/100
Django CPU 125% 125% 125% 125% 125% 125%
QPS 198 73 912.13 1392 1389 1387
流量Mbps 0.4 0.15 1.8 2.8 2.83 2.82
最大耗时ms 9.47 12.63 26.40 199.96 1.83s 1.80s
90%分位耗时ms 2.94 3.77 5.04 10.12 11.27 11.65
99%分位耗时ms 3.72 5.34 6.80 16.68 195.53 200.82
平均耗时ms 2.38 2.80 3.72 9.12 17.29 18.57
错误率% 0 0 0 0 0 0

结论:

  1. 延迟增加,不能使用低并发去压测,因为大部分耗时都将耗费在网络延迟上,无法真正压出服务器的处理能力
  2. 需提高并发压测
  3. 这里局域网压测出来的QPS相对于服务器本机压测反而略高一些,这是因为服务器本机压测时,服务与发压力相互影响。

广域网压测

wrk线程数/连接数 1/1 1/4 1/10 1/20 1/50 1/100 1/200 1/400 1/2000 1/1万 1/1.5万 1/2.5万
Django CPU 42% 56% 65% 74% 78% 83% 84% 866.29 99% 101% 105% 102%
QPS 31 196 322 266 338 825 725 867 855.1 773.5 813.8 792.48
流量Mbps 0.06 0.4 0.66 0.54 0.69 1.6 1.5 1.7 1.7 1.6 1.6 1.6
最大耗时ms 325.42 459.37 1.08s 1.10s 1.12s 1.19s 2.00s 2.01s 1.91 1.99 2.00s 1.90s
90%分位耗时ms 13.74 13.87 15.62 18.75 22.84 52.61 112.39 100.74 86.81 116.19 128.3 103.89
99%分位耗时ms 148 147 639 434 364 734 900 830.1 569.8 802.6 875.9 605.44
平均耗时ms 15.10 14.15 27.42 28.78 23.69 56.71 98.16 98.34 70.11 97.05 103.7 86.67
错误率% 0 0 0 0 0 0 0(出现少量错误) 0 1.2 7.3 10(压垮) 17

结论:

  1. 同局域网压测,延迟增加,不能使用低并发去压测,因为大部分耗时都将耗费在网络延迟上,无法真正压出服务器的处理能力
  2. 处理能力下降,QPS最大值在800附近波动
  3. 持续增加连接数,当到达1线程/200连接时,开始少量出现错误;当连接数达到2000时,错误率上升至1.2%;当连接数持续增加至15000时,错误率上升至10%,系统被压垮 <==>相较于本机测试,相同连接数时,错误率相对更高。
  4. 错误类型以connect为主,详细分析:
    1. 通过在压测前后记录全连接/半连接队列满导致的丢包数量,发现大部分是因为队列满导致被丢弃,可通过修改队列长度获得性能提升

图1 - 本机并发线程压测时CPU消耗情况 perf-pandora

图2 - 局域网单线程压测时CPU消耗情况 perf-pandora

图3 - 广域网单线程压测时CPU消耗情况 perf-pandora