我们有一个拥有32个核心的大型EC2实例,目前运行Nginx,Tornado和Redis,每秒平均提供5K请求。一切似乎都运行良好,但CPU负载已经达到70%,我们必须支持更多的请求。其中一个想法是用uWSGI取代Tornado,因为我们并没有真正使用Tornado的异步功能。
我们的应用程序由一个函数组成,它接收一个JSON(〜= 4KB),执行一些阻塞但非常快的东西(Redis)并返回JSON。
我们认为速度提升将来自uwsgi协议,我们可以在单独的服务器上安装Nginx,并使用uwsgi协议代理对uWSGI的所有请求。但在尝试所有可能的配置并更改操作系统参数后,即使在当前负载下,我们仍然无法使其工作。 大多数时候nginx日志包含499和502错误。在某些配置中,它只是停止接收新的请求,就像它达到一些操作系统限制一样。
正如我所说,我们有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;
}
操作系统配置:
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
答案 0 :(得分:11)
我认为您的请求处理大致分解如下:
您可以在近乎空闲的系统上对处理时间进行基准测试。我的预感是往返可以降到2或3毫秒。在70%的CPU负载下,这将达到大约4或5毫秒(不计算在nginx请求队列中花费的时间,只是uWSGI工作器中的处理)。
在5k req / s时,您的平均进程中请求可能在20 ... 25范围内。与您的VM完美匹配。
下一步是平衡CPU核心。如果您有32个核心,则分配1000个工作进程没有意义。您最终可能会在上下文切换开销上阻塞系统。一个好的平衡将使工作者的总数(nginx + uWSGI + redis)在数量级上与可用的CPU核心数量级相同,可能需要额外的一点来覆盖阻塞I / O(即文件系统,但主要是网络请求正在完成)到DBMS等其他主机。如果阻塞I / O成为等式的重要组成部分,请考虑重写异步代码并集成异步堆栈。
首先观察:你将10名工人分配给nginx。但是,nginx在请求上花费的CPU时间比uWSGI花费的时间要少得多。我首先将大约10%的系统专用于nginx(3或4个工作进程)。
其余部分必须在uWSGI和redis之间分配。我不知道redis中索引的大小,或者你的python代码的复杂程度,但我的第一次尝试是uWSGI和redis之间的75%/ 25%分割。这将使大约6名工人和uWSGI成为大约20名工人+大师的重生。
至于uwsgi配置中的threads选项:线程切换比进程切换轻,但如果你的python代码的很大一部分受CPU限制,它就不会因为GIL而飞。如果您的处理时间的很大一部分被I / O阻塞,则Threads选项主要是有趣的。您可以禁用线程,或尝试使用workers = 10,threads = 2作为初始尝试。