Nginx Performance Test
写在最前面, 这个问题还在研究中, 我目前还没有合适的模型用来研究这个问题, Pending….
Nginx 性能测试与压力计算
使用al2023 + 默认的yum仓库软件版本, 具体信息记录如下:
按照下面的配置, 在Nginx上面发布一个目录, 其中放了一个Fedora镜像, 大小大约2.3GB, 固定Nginx处理请求的大小, 控制大部分的因素来尝试获取精确的结果。
OS Version
[root@ip-172-31-53-146 ~]# cat /etc/os-release NAME="Amazon Linux" VERSION="2023" ID="amzn" ID_LIKE="fedora" VERSION_ID="2023" PLATFORM_ID="platform:al2023" PRETTY_NAME="Amazon Linux 2023" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023" HOME_URL="https://aws.amazon.com/linux/" BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023" SUPPORT_END="2028-03-01"
在一个机器上面部署MySQL 然后初始化配置一个密码和用户。
[root@ip-172-31-53-200 ~]# mysql --version mysql Ver 8.0.32 for Linux on x86_64 (MySQL Community Server - GPL)
直接使用bitnami的wordpress, 仅部署wordpress在另一个机器上面,然后配置连接到上面的那台数据库。
额外配置一下Docker-compose 的CPU affinity, 绑到cpu0上面, 让这个Container只能用cpu0, 模拟只有一个核心, 后面应该还会给他放开。
--- version: '2' services: wordpress: cpuset: "0" network_mode: host 5. 尝试给点压力, 做个基准测试。 ### 测试1 单线程计算QPS > 公式: 1000ms/RT = QPS > > 这个是单线程的QPS 与 RT 的关系, 试图走一把流程验证这个。 运行命令 wrk 配置 1connection 1thread 进行测试, 测试时间5分钟。 获取 RT,HTTP请求从发出到响应的时间 : ![2023-04-27_10-38.png](https://s2.loli.net/2023/04/27/pf2Z5erTzcayPFJ.png) 基于当前获取到的RT就可以计算出单线程的QPS : ```bash RT:45ms # 这个时候获取的RT是客户端从发出数据包 到 收到完整的请求的页面返回的时间, 基于上面的抓包结果计算。 1000ms/45ms = 22 QPS
实际命令返回的结果:
~ ❯❯❯ wrk -t1 -c1 -d5m --latency http://nginx.liarlee.site:8080
Running 5m test @ http://nginx.liarlee.site:8080
1 threads and 1 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 45.00ms 3.60ms 169.02ms 88.75%
Req/Sec 22.24 4.27 30.00 76.72%
Latency Distribution
50% 44.28ms
75% 46.23ms
90% 48.51ms
99% 54.40ms
6668 requests in 5.00m, 337.69MB read
Requests/sec: 22.23 # 确实是 22左右, 基本上和计算得出的结果一致。
Transfer/sec: 1.13MB
测试2 计算服务当前的最佳线程数量
默认的情况下, 当一个进程处理的时候, 线程接收到请求, OnCPU处理这个请求, 发出数据库查询,切换到Sleep状态,数据库处理完成,线程回到CPU继续运算,发送结果给客户端,这样完成一个Request。
计算服务的最佳线程数量其实是 需要多少个线程占用CPU,最终可以填满 CPU 1s 的时间, 让CPU尽可能都用来处理业务请求。
所以最大的线程数量是变化的, 与CPU time 相关 或者说 与RT相关, 对于一些特定的请求,控制了大部分变量的场景下, 可以计算一个最佳的线程数量。 计算公式:
最佳线程数: CPU TIme + Wait Time / CPU Time = 2 + 42 / 2 = 22
打开文件数限制可能的并发数量
测试 conntrack 的问题, 发现连接数不太高, 试图通过wrk 提高并发的连接数量。
使用命令:
wrk -t2 -c30000 -d30s http://reg.liarlee.site:80
如果我的理解没有问题, 那么我应该可以创建30000个连接 在 客户端 和 服务端 之间。
但是结果:
# 客户端尝试建立 8000c
ec2-user@arch ~> wrk -t1 -c8000 -d30s http://reg.liarlee.site:80
Running 30s test @ http://reg.liarlee.site:80
1 threads and 8000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 49.94ms 42.36ms 428.68ms 92.65%
Req/Sec 21.15k 8.19k 41.30k 66.08%
608596 requests in 30.04s, 218.23MB read
Socket errors: connect 6980, read 0, write 0, timeout 0
Requests/sec: 20260.60
Transfer/sec: 7.27MB
# 服务端看到的是:
[root@reg tools]# ss -s
Total: 282
TCP: 1103 (estab 28, closed 1063, orphaned 0, timewait 2)
Transport Total IP IPv6
RAW 0 0 0
UDP 8 4 4
TCP 40 36 4
INET 48 40 8
FRAG 0 0 0
这和预期的差距挺大的, 1103 让我非常容易的想到了 1024 的文件描述符限制。 于是
ec2-user@arch ~> ulimit -n
1024
客户端 wrk 的文件描述符可用调大到 100000 。
# 客户端
ec2-user@arch ~ [127]> ulimit -n
100000
ec2-user@arch ~> wrk -t2 -c30000 -d30s http://reg.liarlee.site:80
Running 30s test @ http://reg.liarlee.site:80
2 threads and 30000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 515.29ms 411.20ms 2.00s 78.35%
Req/Sec 1.76k 2.70k 11.96k 85.71%
39021 requests in 30.28s, 13.99MB read
Socket errors: connect 0, read 298852, write 16, timeout 3115
Requests/sec: 1288.65
Transfer/sec: 473.17KB
# 服务端
[root@reg tools]# ss -s
Total: 282
TCP: 7544 (estab 28, closed 7504, orphaned 1641, timewait 2)
Transport Total IP IPv6
RAW 0 0 0
UDP 8 4 4
TCP 40 36 4
INET 48 40 8
FRAG 0 0 0
如果从上面的角度来看, 那么客户端的打开文件数是限制, 现在打开文件数调大了, 服务端还是无法建立更多的连接, 这个还得继续看看 , 我感觉是cpu的问题, 毕竟我访问的页面的是有内容的, 不是一个空请求。
# 尝试看了以下服务端的ss 命令
ec2-user@arch ~> ss -s
Total: 31669
TCP: 31268 (estab 7959, closed 1182, orphaned 1250, timewait 1)
Transport Total IP IPv6
RAW 1 0 1
UDP 4 2 2
TCP 30086 30080 6
INET 30091 30082 9
FRAG 0 0 0
确实发起了30000 但是 进入estab状态的连接只有 8000 左右。这基本上可以确定是服务端的限制了,具体是什么地方限制了待查。
客户端 : c5 实例类型上面的 wrk
服务端 : t3.micro - docker compose 部署的 harbor
连接异常导致的orphans
一个遗憾的事情, 最近遇到了一个可能是 cilium 的一个问题, 在小规模集群的场景下, Cilium 会错误的处理FIN , FIN ACK, 导致安全组的 Connection Track 数量被打满。 之前没有注意过这个行为,在Docker的环境中测试下, 尝试使用 iptables 屏蔽容器发送出来的FIN, 记录步骤和命令:
dnf install -y docker
systemctl enable --now docker
docker pull reg.liarlee.site/docker.io/library/nginx/nginx:latest
docker run -dt --rm --name nginx -p 81:80 reg.liarlee.site/docker.io/library/nginx:latest
docker ps 看到容器已经启动就可以了
查看容器的IP地址:
[root@ip-172-31-54-32 ~]# docker inspect nginx | grep -i ipaddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2",
添加一个规则在 DOCKER-ISOLATION-STAGE-1 链上。
[root@ip-172-31-54-32 ~]# iptables -I DOCKER-ISOLATION-STAGE-1 1 -p tcp -s 172.17.0.2 --tcp-flags FIN FIN -j DROP
[root@ip-172-31-54-32 ~]# iptables -I OUTPUT 1 -p tcp -s 172.31.47.174 -tcp-flags FIN FIN -j DROP
查看iptables 链表规则, DOCKER-ISOLATION-STAGE-1。
[root@ip-172-31-54-32 ~]# iptables -nvL --line-numbers
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain FORWARD (policy DROP 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 47M 13G DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
2 47M 13G DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
3 24M 1713M ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
4 12371 742K DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
5 24M 11G ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
6 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain DOCKER (1 references)
num pkts bytes target prot opt in out source destination
1 11933 716K ACCEPT tcp -- !docker0 docker0 0.0.0.0/0 172.17.0.2 tcp dpt:80
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
num pkts bytes target prot opt in out source destination
1 24M 11G DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
2 30690 1596K DROP tcp -- * * 172.17.0.2 0.0.0.0/0 tcp flags:0x01/0x01 # 在这里
3 47M 13G RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
2 24M 11G RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-USER (1 references)
num pkts bytes target prot opt in out source destination
1 47M 13G RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
使用 wrk 在客户端发起测试:
ec2-user@arch ~> wrk -t1 -c1000 -d3000s http://172.31.54.32:81
Running 50m test @ http://172.31.54.32:81
1 threads and 1000 connections
在系统内部可以看到有一部分 orphans 连接:
[root@ip-172-31-54-32 ~]# ss -s
Total: 233
TCP: 2019 (estab 5, closed 2009, orphaned 1006, timewait 1)
Transport Total IP IPv6
RAW 1 0 1
UDP 4 2 2
TCP 10 8 2
INET 15 10 5
FRAG 0 0 0
记录下GPT告诉我的答案:
TCP orphans 是指没有与任何文件描述符关联的 socket,也就是说应用程序已经关闭了这个 socket,但是内核还没有释放它的资源,因为 TCP 还没有完成它的状态转换。
TCP orphans 通常出现在以下几种 TCP 状态:FIN-WAIT-1, FIN-WAIT-2, LAST-ACK, CLOSING 和 TIME-WAIT。这些状态都是在 TCP 四次挥手的过程中产生的,表示连接正在关闭或已经关闭,但是还需要等待一段时间才能完全释放。
TCP orphans 的产生可能有以下几种原因:
应用程序关闭了 socket,但是对方没有及时回复 ACK 或 FIN 报文,导致连接一直处于 FIN-WAIT-1 或 FIN-WAIT-2 状态。
应用程序关闭了 socket,但是对方回复了 FIN 报文,导致连接进入 CLOSING 状态,然后双方都没有及时回复 ACK 报文,导致连接一直处于 CLOSING 状态。
应用程序关闭了 socket,但是对方回复了 ACK 报文,导致连接进入 LAST-ACK 状态,然后对方没有及时回复 FIN 报文,导致连接一直处于 LAST-ACK 状态。
应用程序关闭了 socket,但是对方回复了 FIN 和 ACK 报文,导致连接进入 TIME-WAIT 状态,然后需要等待 2MSL 的时间才能释放连接。
应用程序关闭了 socket,但是由于网络延迟或丢包等原因,导致上述状态转换过程中的报文丢失或重传,延长了连接的关闭时间。
新的测试步骤:
# 清理IPtables规则
iptables -Z && iptables -X && iptables -F
iptables -t raw -Z && iptables -t raw -F && iptables -t raw -X
# 创建新的IPtables规则, 屏蔽 FIN , RST
# Server block self FIN RST
# Role Nginx Server 172.31.54.32
iptables -I OUTPUT 1 -p tcp -s 171.31.54.32 --tcp-flags FIN FIN -j DROP
iptables -I OUTPUT 1 -p tcp -s 172.31.54.32 --tcp-flags RST RST -j DROP
# Client block self FIN RST
# Role Client 172.31.36.129
iptables -I OUTPUT 1 -p tcp -s 172.31.36.129 --tcp-flags FIN FIN -j DROP
iptables -I OUTPUT 1 -p tcp -s 172.31.36.129 --tcp-flags RST RST -j DROP
按照这个设置, 使用tcpdump抓报包, 双方是没有 FIN 和 RST 的, 说明iptables规则层面是生效。
查看 ss -s 的数据, 客户端的连接数是在 10000 左右的, 服务端也是;
使用命令中断客户端的wrk程序之后,客户端的连接数会直接被释放;
服务端会维持, 维持一段时间之后, 下降到 5000 , 然后完全消失。
造成这个现象的原因是: Nginx配置文件中的参数。
vim /etc/nginx/nginx.conf
worker_rlimit_nofile 1024000;
http{
sendfile on;
tcp_nopush on;
types_hash_max_size 4096;
keepalive_timeout 3000s; # 服务端维持连接的最大时间, 超过这个时间之后的连接会被nginx释放。
keepalive_requests 1000000; # 在一个长连接上可以服务的最大请求数目。
}
Refer Link: https://www.cnblogs.com/kevingrace/p/9364404.html