uWSGI python 高负载配置
uWSGI python highload configuration
我们有一个 32 核的大型 EC2 实例,目前 运行 Nginx、Tornado 和 Redis,平均每秒处理 5K 请求。一切似乎都正常,但 CPU 负载已经达到 70%,我们必须支持更多请求。其中一个想法是用 uWSGI 替换 Tornado,因为我们并没有真正使用 Tornado 的异步特性。
我们的应用程序由一个函数组成,它接收一个 JSON (~=4KB),做一些阻塞但非常快的事情 (Redis) 和 return JSON。
- 代理 HTTP 请求到 Tornado 实例之一 (Nginx)
- 解析 HTTP 请求 (Tornado)
- 读取 POST 正文字符串(字符串化 JSON)并将其转换为 python 字典(Tornado)
- 从位于同一台机器上的 Redis(阻塞)中取出数据(py-redis 和 hiredis)
- 处理数据(python3.4)
- 在同一台机器上更新 Redis(py-redis 和 hiredis)
- 准备字符串化 JSON 以进行响应 (python3.4)
- 向代理发送响应 (Tornado)
- 向客户端发送响应 (Nginx)
我们认为速度的提升将来自于 uwsgi 协议,我们可以在单独的服务器上安装 Nginx,并使用 uwsgi 协议将所有请求代理到 uWSGI。但是在尝试了所有可能的配置并更改了 OS 参数之后,即使在当前负载下我们仍然无法让它工作。
大多数时候 nginx 日志包含 499 和 502 错误。在某些配置中,它只是停止接收新请求,就像它达到了一些 OS 限制。
正如我所说,我们有 32 个内核、60GB 可用内存和非常快的网络。我们不做繁重的事情,只做非常快速的阻塞操作。在这种情况下最好的策略是什么?进程、线程、异步?应该设置哪些 OS 参数?
当前配置为:
[uwsgi]
master = 2
processes = 100
socket = /tmp/uwsgi.sock
wsgi-file = app.py
daemonize = /dev/null
pidfile = /tmp/uwsgi.pid
listen = 64000
stats = /tmp/stats.socket
cpu-affinity = 1
max-fd = 20000
memory-report = 1
gevent = 1000
thunder-lock = 1
threads = 100
post-buffering = 1
Nginx 配置:
user www-data;
worker_processes 10;
pid /run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
OS 配置:
sysctl net.core.somaxconn
net.core.somaxconn = 64000
我知道限制太高了,开始尝试每一个可能的值。
更新:
我最终得到了以下配置:
[uwsgi]
chdir = %d
master = 1
processes = %k
socket = /tmp/%c.sock
wsgi-file = app.py
lazy-apps = 1
touch-chain-reload = %dreload
virtualenv = %d.env
daemonize = /dev/null
pidfile = /tmp/%c.pid
listen = 40000
stats = /tmp/stats-%c.socket
cpu-affinity = 1
max-fd = 200000
memory-report = 1
post-buffering = 1
threads = 2
我认为您的请求处理大致如下:
- HTTP解析,请求路由,JSON解析
- 执行一些 python 产生 redis 请求的代码
- (阻塞)redis 请求
- 执行一些 python 处理 redis 响应的代码
- JSON序列化,HTTP响应序列化
您可以对接近空闲的系统的处理时间进行基准测试。我的预感是往返行程将减少到 2 或 3 毫秒。在 70% CPU 负载下,这将上升到大约 4 或 5 毫秒(不计算在 nginx 请求队列中花费的时间,只计算在 uWSGI worker 中的处理)。
在 5k req/s 时,您的平均处理中请求可能在 20 ... 25 范围内。与您的 VM 非常匹配。
下一步是平衡 CPU 个内核。如果你有 32 个核心,分配 1000 个工作进程是没有意义的。您可能最终会因上下文切换开销而阻塞系统。一个好的平衡将使工作人员(nginx+uWSGI+redis)的总数与可用的 CPU 核心数量级,可能还有一些额外的阻塞 I/O (即文件系统,但主要是对其他主机(如 DBMS)进行的网络请求)。如果阻塞 I/O 成为等式的重要组成部分,请考虑重写为异步代码并集成异步堆栈。
第一个观察结果:您正在为 nginx 分配 10 个 worker。然而 CPU nginx 花在一个请求上的时间比 uWSGI 花在它上面的时间要少得多。我会首先将大约 10% 的系统专用于 nginx(3 或 4 个工作进程)。
其余部分必须在 uWSGI 和 redis 之间分配。我不知道你在 redis 中索引的大小,或者你的 python 代码的复杂性,但我的第一次尝试是在 uWSGI 和 redis 之间进行 75%/25% 的分割。这将使 redis 有大约 6 个 worker,uWSGI 有大约 20 个 worker + 一个 master。
至于 uwsgi 配置中的线程选项:线程切换比进程切换更轻,但如果你的 python 代码的重要部分是 CPU-bound 它不会运行,因为吉尔。如果您的处理时间的很大一部分被 I/O 阻塞,则线程选项主要是有趣的。您可以禁用线程,或尝试使用 workers=10,threads=2 作为初始尝试。
我们有一个 32 核的大型 EC2 实例,目前 运行 Nginx、Tornado 和 Redis,平均每秒处理 5K 请求。一切似乎都正常,但 CPU 负载已经达到 70%,我们必须支持更多请求。其中一个想法是用 uWSGI 替换 Tornado,因为我们并没有真正使用 Tornado 的异步特性。
我们的应用程序由一个函数组成,它接收一个 JSON (~=4KB),做一些阻塞但非常快的事情 (Redis) 和 return JSON。
- 代理 HTTP 请求到 Tornado 实例之一 (Nginx)
- 解析 HTTP 请求 (Tornado)
- 读取 POST 正文字符串(字符串化 JSON)并将其转换为 python 字典(Tornado)
- 从位于同一台机器上的 Redis(阻塞)中取出数据(py-redis 和 hiredis)
- 处理数据(python3.4)
- 在同一台机器上更新 Redis(py-redis 和 hiredis)
- 准备字符串化 JSON 以进行响应 (python3.4)
- 向代理发送响应 (Tornado)
- 向客户端发送响应 (Nginx)
我们认为速度的提升将来自于 uwsgi 协议,我们可以在单独的服务器上安装 Nginx,并使用 uwsgi 协议将所有请求代理到 uWSGI。但是在尝试了所有可能的配置并更改了 OS 参数之后,即使在当前负载下我们仍然无法让它工作。 大多数时候 nginx 日志包含 499 和 502 错误。在某些配置中,它只是停止接收新请求,就像它达到了一些 OS 限制。
正如我所说,我们有 32 个内核、60GB 可用内存和非常快的网络。我们不做繁重的事情,只做非常快速的阻塞操作。在这种情况下最好的策略是什么?进程、线程、异步?应该设置哪些 OS 参数?
当前配置为:
[uwsgi]
master = 2
processes = 100
socket = /tmp/uwsgi.sock
wsgi-file = app.py
daemonize = /dev/null
pidfile = /tmp/uwsgi.pid
listen = 64000
stats = /tmp/stats.socket
cpu-affinity = 1
max-fd = 20000
memory-report = 1
gevent = 1000
thunder-lock = 1
threads = 100
post-buffering = 1
Nginx 配置:
user www-data;
worker_processes 10;
pid /run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
OS 配置:
sysctl net.core.somaxconn
net.core.somaxconn = 64000
我知道限制太高了,开始尝试每一个可能的值。
更新:
我最终得到了以下配置:
[uwsgi]
chdir = %d
master = 1
processes = %k
socket = /tmp/%c.sock
wsgi-file = app.py
lazy-apps = 1
touch-chain-reload = %dreload
virtualenv = %d.env
daemonize = /dev/null
pidfile = /tmp/%c.pid
listen = 40000
stats = /tmp/stats-%c.socket
cpu-affinity = 1
max-fd = 200000
memory-report = 1
post-buffering = 1
threads = 2
我认为您的请求处理大致如下:
- HTTP解析,请求路由,JSON解析
- 执行一些 python 产生 redis 请求的代码
- (阻塞)redis 请求
- 执行一些 python 处理 redis 响应的代码
- JSON序列化,HTTP响应序列化
您可以对接近空闲的系统的处理时间进行基准测试。我的预感是往返行程将减少到 2 或 3 毫秒。在 70% CPU 负载下,这将上升到大约 4 或 5 毫秒(不计算在 nginx 请求队列中花费的时间,只计算在 uWSGI worker 中的处理)。
在 5k req/s 时,您的平均处理中请求可能在 20 ... 25 范围内。与您的 VM 非常匹配。
下一步是平衡 CPU 个内核。如果你有 32 个核心,分配 1000 个工作进程是没有意义的。您可能最终会因上下文切换开销而阻塞系统。一个好的平衡将使工作人员(nginx+uWSGI+redis)的总数与可用的 CPU 核心数量级,可能还有一些额外的阻塞 I/O (即文件系统,但主要是对其他主机(如 DBMS)进行的网络请求)。如果阻塞 I/O 成为等式的重要组成部分,请考虑重写为异步代码并集成异步堆栈。
第一个观察结果:您正在为 nginx 分配 10 个 worker。然而 CPU nginx 花在一个请求上的时间比 uWSGI 花在它上面的时间要少得多。我会首先将大约 10% 的系统专用于 nginx(3 或 4 个工作进程)。
其余部分必须在 uWSGI 和 redis 之间分配。我不知道你在 redis 中索引的大小,或者你的 python 代码的复杂性,但我的第一次尝试是在 uWSGI 和 redis 之间进行 75%/25% 的分割。这将使 redis 有大约 6 个 worker,uWSGI 有大约 20 个 worker + 一个 master。
至于 uwsgi 配置中的线程选项:线程切换比进程切换更轻,但如果你的 python 代码的重要部分是 CPU-bound 它不会运行,因为吉尔。如果您的处理时间的很大一部分被 I/O 阻塞,则线程选项主要是有趣的。您可以禁用线程,或尝试使用 workers=10,threads=2 作为初始尝试。