【问题标题】:uWSGI python highload configurationuWSGI python高负载配置
【发布时间】:2015-06-11 07:25:17
【问题描述】:

我们有一个 32 核的大型 EC2 实例,目前运行 Nginx、Tornado 和 Redis,平均每秒处理 5K 请求。一切似乎都运行良好,但 CPU 负载已经达到 70%,我们必须支持更多请求。其中一个想法是用 uWSGI 替换 Tornado,因为我们并没有真正使用 Tornado 的异步特性。

我们的应用程序由一个函数组成,它接收 JSON (~=4KB),执行一些阻塞但非常快的操作 (Redis) 并返回 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。但是在尝试了所有可能的配置并更改了操作系统参数之后,即使在当前负载下,我们仍然无法使其正常工作。 大多数时候 nginx 日志包含 499 和 502 错误。在某些配置中,它只是停止接收新请求,就像它达到了某些操作系统限制一样。

正如我所说,我们有 32 个内核、60GB 可用内存和非常快的网络。我们不做繁重的事情,只做非常快速的阻塞操作。在这种情况下,最好的策略是什么?进程、线程、异步?应该设置哪些操作系统参数?

当前配置为:

[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

【问题讨论】:

    标签: python nginx tornado uwsgi high-load


    【解决方案1】:

    我认为您的请求处理大致如下:

    • HTTP 解析、请求路由、JSON 解析
    • 执行一些产生redis请求的python代码
    • (阻塞)redis 请求
    • 执行一些处理redis响应的python代码
    • JSON 序列化、HTTP 响应序列化

    您可以在接近空闲的系统上对处理时间进行基准测试。我的预感是往返将归结为 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% 分割。这将使 redis 在大约 6 个 worker 上运行,uWSGI 在大约 20 个 worker + 一个 master 上运行。

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

    【讨论】:

    • 非常感谢,我能够让它在 16 个进程和 20 个线程的情况下每秒运行高达 9K 的请求。现在 CPU 大约是 45%。我可以尝试减少线程数,尽管该服务器正在生产中,我不能玩太多。我还根据article 调整了操作系统参数。 redis 的问题是它只适用于一个核心,但我可以减少 nginx 上的工作人员。如果我理解正确,正确的工人数量是当他们每个人都使用接近 100% 的核心时。
    • 很高兴听到您做出了不错的改进(5000 req/s @ 70% CPU --> 9000 req/s % 45%)!我认为 uWSGI threads 设置仍然太高了。现在你有 16 个工人 * 20 个线程。当 python 代码是 95% 的 I/O 绑定时,每个 worker 20 个并发线程只会最大化 CPU。我会启用更多的工作线程(比如 24 个)并将 threads 设置减少到 2 或 4。运行 htop 以查看负载在 CPU 上的平衡情况。
    • 有什么方法可以知道多少时间进程是 I/O 绑定的,以及多少时间 CPU 在实时运行的进程上进行计算?
    • top 命令给出指示:%Cpu(s): 7,8 us, 3,3 sy, 0,0 ni, 89,0 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st(用户/系统/空闲/等待/硬件中断/软件中断/被盗)。有关说明,请参阅linuxaria.com/howto/understanding-the-top-command-on-linux。按顶部的1 可查看每个 CPU 核心的统计信息。
    • 更新到我的服务器上的最新配置。加上代码更改,我能够以 30k/s 的速度运行
    猜你喜欢
    • 2017-04-11
    • 1970-01-01
    • 2019-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-18
    • 2012-12-24
    • 1970-01-01
    相关资源
    最近更新 更多