uWSGI python高负载configuration

我们有一个32核的EC2实例,目前运行Nginx,Tornado和Redis,平均每秒处理5K个请求。 似乎一切正常,但CPU负载已经达到70%,我们不得不支持更多的请求。 其中一个想法是用uWSGI取代Tornado,因为我们并没有真正使用Tornado的asynchronousfunction。

我们的应用程序由一个函数组成,它接收一个JSON(〜= 4KB),做一些阻塞但非常快的东西(Redis)并返回JSON。

  • 代理HTTP请求到一个Tornado实例(Nginx)
  • parsingHTTP请求(Tornado)
  • 阅读POST正文string(stringified JSON)并将其转换为Python字典(Tornado)
  • 从位于同一台计算机上的Redis(阻塞)中取出数据(使用hiredis的py-redis)
  • 处理数据(python3.4)
  • 在同一台机器上更新Redis(py-redis with hiredis)
  • 为响应准备string化的JSON(python3.4)
  • 发送回应代理(龙卷风)
  • 发送回应给客户端(Nginx)

我们认为速度改进将来自uwsgi协议,我们可以在单独的服务器上安装Nginx,并使用uwsgi协议代理所有对uWSGI的请求。 但是在尝试所有可能的configuration和更改操作系统参数之后,即使在当前的负载下,我们仍然无法正常工作 大多数情况下,nginx日志包含499和502错误。 在某些configuration中,它只是停止接收新的请求,如它碰到一些操作系统限制。

正如我所说,我们有32核心,60GB的可用内存和非常快的networking。 我们不做重的东西,只有非常快的阻止操作。 这种情况下最好的策略是什么? 进程,线程,asynchronous? 什么OS参数应该设置?

当前的configuration是:

[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 

Nginxconfiguration:

 user www-data; worker_processes 10; pid /run/nginx.pid; events { worker_connections 1024; multi_accept on; use epoll; } 

OSconfiguration:

 sysctl net.core.somaxconn net.core.somaxconn = 64000 

我知道这个限制太高了,开始尝试每一个可能的价值。

更新

我结束了以下configuration:

 [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解析
  • 执行一些产生redis请求的python代码
  • (阻止)redis请求
  • 执行一些处理redis响应的python代码
  • JSON序列化,HTTP响应序列化

您可以在接近闲置的系统上对处理时间进行基准测试。 我的直觉是,往返程度会下降到2或3毫秒。 在70%的CPU负载情况下,这将会上升到大约4或5毫秒(不包括在nginx请求队列中花费的时间,只是在uWSGI worker中的处理)。

在5k req / s的情况下,您的平均处理请求可能会在20 … 25的范围内。 一个体面的匹配到您的虚拟机。

下一步是平衡CPU核心。 如果你有32个内核,分配1000个工作进程是没有意义的。 上下文切换开销可能会导致系统堵塞。 一个好的平衡会使工作人员总数(nginx + uWSGI + redis)与可用的CPU核心数量级相当,可能有一些额外的工作来阻止I / O(即文件系统,但主要是联网请求到像DBMS的其他主机)。 如果阻塞I / O成为等式的重要组成部分,可考虑重写异步代码并集成异步堆栈。

首先观察:你将10个工人分配给nginx。 然而,在一个请求上花费的CPU时间nginx比uWSGI花费的时间要少得多。 我会开始把nginx(3或4个工作进程)的约10%的系统专用。

其余部分必须在uWSGI和redis之间进行分割。 我不知道你的索引在redis中的大小,或者你的python代码的复杂性,但是我的第一个尝试是uWSGI和redis之间的75%/ 25%的分割。 这将使大约6名工人和uWSGI约20名工人+主人。

至于在uwsgi配置中的线程选项:线程切换比进程切换轻,但是如果你的python代码的很大一部分是CPU绑定的,它将不会因为GIL而飞。 线程选项主要是有趣的,如果你的处理时间的重要部分是I / O阻塞。 你可以禁用线程,或尝试worker = 10,threads = 2作为初始尝试。