最近在做小程序后端开发,使用Docker+Nginx+uWSGI+MongoDB的架构
最终部署图如下:
最开始在对新增订单的接口进行压测时出现100并发就有大量失败的情况,日志显示"WriteConflict error: this operation conflicted with another operation.",分析后得出找出原因:
新增订单压测时是对单一用户进行,即单一用户新增100个订单,而每个新增订单的业务逻辑需要在事务中读写该用户的单个document,从而导致大量请求获取该document的写锁超时。由于业务逻辑没有问题,故将代码临时进行修改,使其压测时对不同的document进行读写,模拟出100个用户新增订单的场景。问题解决。
随后,又对该接口进行200、300、400的压测,均没有出现失败,但到500的时候又出现失败
MongoDB
查看日志后发现依旧是"WriteConflict error: this operation conflicted with another operation." 错误。经过一番搜索,通过提高事务锁最大等待时间可以解决问题
>>> db.adminCommand( { setParameter: 1, maxTransactionLockRequestTimeoutMillis: 3000 } );
该命令会将默认的等待5ms改为3000ms。如果你的mongodb是副本集,则需要在每个节点都执行一次上面的命令。
参考:https://segmentfault.com/a/1190000022518374
然后继续进行测试,500、600都没问题,700出现错误。
这时日志中没有出现"WriteConflict error: this operation conflicted with another operation." 错误。而是变为"SSL read failed (5) - closing connection",也就是说请求就没有到达Flask。这个问题原因就比较多了,可能是服务器的CentOS对请求进行了限流,也可能是容器中的Ubuntu,也可能是Nginx,也可能是uWSGI。所以要逐个进行排查。
uWSGI
uWSGI配置文件中对listen值进行修改
...
processes = 4
threads = 2
enable-threads=true
# 默认队列长度为100
listen = 2000
...
重启uWSGI后发现报错
*** Starting uWSGI 2.0.19.1 (64bit) on [Wed Jun 9 09:05:49 2021] ***
compiled with version: 9.3.0 on 07 June 2021 02:10:09
os: Linux-3.10.0-1160.24.1.el7.x86_64 #1 SMP Thu Apr 8 19:51:47 UTC 2021
nodename: c6232ed6e31d
machine: x86_64
clock source: unix
detected number of CPU cores: 8
current working directory: /app
detected binary path: /usr/local/bin/uwsgi
!!! no internal routing support, rebuild with pcre support !!!
uWSGI running as root, you can use --uid/--gid/--chroot options
*** WARNING: you are running uWSGI as root !!! (use the --uid flag) ***
chdir() to /app
your memory page size is 4096 bytes
detected max file descriptor number: 1048576
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
Listen queue size is greater than the system max net.core.somaxconn (500).
这里的意思是操作系统中的一个参数小于这个listen值,对于这个值的修改后面再进行说明
Nginx
主要是最大连接数worker_processes*worker_connections
worker_processes auto; #nginx 进程数,建议按照cpu 数目来指定,一般为它的倍数
worker_rlimit_nofile 20000; #一个nginx 进程打开的最多文件描述符数目,理论值应该是最多打开文件数(ulimit -n)与nginx 进程数相除,但是nginx 分配请求并不是那么均匀,所以最好与ulimit -n 的值保持一致
events {
worker_connections 2000; #每个进程允许的最多连接数, 理论上每台nginx 服务器的最大连接数为worker_processes*worker_connections
multi_accept on;
}
http {
keepalive_timeout 0;
}
Docker容器
version: "3.8"
services:
wxapp_api:
...
privileged: true
sysctls:
net.core.somaxconn: '2000'
...
进入容器中查看一下ulimit值,有的操作系统默认的ulimit值仅为几百(如Mac为256),如果过低则进行修改
# 查看
>>> ulimit -n
# 修改
>>> ulimit -n 100000
宿主CentOS
# 查看
>>> ulimit -n
# 修改
>>> ulimit -n 100000
# 临时生效
>>> sysctl -w net.core.somaxconn=4000
# 永久生效
>>> vim /etc/sysctl.conf
net.core.somaxconn= 4000
>>> sysctl -p
在做完上面所有工作后发现700的时候依旧存在"SSL read failed (5) - closing connection"
关键步骤来了
# TCP连接立即回收、回用(recycle、reuse)
>>> echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
>>> echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
# 不做TCP洪水抵御
>>> echo 0 > /proc/sys/net/ipv4/tcp_syncookies
>>> sysctl -p
在做完上面所有的设置后,800并发没有报错,900的时候出现少量错误,而且是"SSL read failed (5) - closing connection"错误,故还需要进一步分析。
我将请求方式改为GET,即请求到达Flask后,而不执行业务代码就直接返回,测试发现1000并发也没有问题,故问题出在业务代码部分。
由于800并发已经能满足需求了,就没有再继续深入探究。但明显感觉800并发并没有发挥出这套架构应有的性能。
参考:
https://segmentfault.com/a/1190000022518374
https://blog.csdn.net/apple9005/article/details/82556794
2021-06-17更新
在另外一个接口的压测过程中也出现800正常,900出现几十个异常的现象。
查看uWSGI日志后发现,出现异常的37个请求没有到达uWSGI,即问题还是出现在Nginx
再查看Nginx日志,发现
突然灵光一闪,瓶颈可能是我这个笔记本,毕竟需要一次产生900个请求,网卡、处理器、内存、操作系统都可能产生瓶颈。所以我换了一台服务器进行测试,发现1000并发也正常,所以基本可以确定之前出现问题的原因是测试机的瓶颈。
2021-06-26更新
>>> ab -c 1500 -n 1500 -T application/json -p testing-body.txt 'https://xxxx.comactivity/v1/order/new'
上面测试的接口会向数据库中findOneAndUpdate一条数据
每次优化均在base的基础上进行,并清空数据库
次数 | base | 编译 | 不输出日志 | 添加索引 | 编译&不输出日志&添加索引 |
---|---|---|---|---|---|
第一次 | 108.54 | 108.42 | 107.93 | 115.65 | 119.31 |
第二次 | 92.06 | 94.77 | 92.35 | 121.73 | 118.08 |
第三次 | 79.34 | 79.96 | 79.73 | 119.35 | 116.21 |
第四次 | 66.66 | 73.98 | 70.56 | 122.67 | 115.66 |
第五次 | 61.77 | 67.34 | 64.86 | 113.80 | 114.14 |
第六次 | 58.29 | 59.91 | 59.96 | 117.70 | 116.69 |
参考:https://www.cloudbees.com/blog/getting-every-microsecond-out-of-uwsgi