конфигурация uwsgi python highload

у нас есть большой экземпляр EC2 с 32 ядрами, в настоящее время работает Nginx, Tornado и Redis, обслуживая в среднем 5K запросов в секунду. Все вроде работает нормально, но загрузка процессора уже достигает 70% и мы должны поддерживать еще больше просьб. Одной из мыслей было заменить Tornado на uWSGI, потому что мы действительно не используем асинхронные функции Tornado.

наше приложение состоит из одной функции, оно получает JSON (~=4KB), делая некоторые блокировки, но очень быстрые вещи (Redis) и верните JSON.

  • Прокси HTTP-запрос к одному из экземпляров Tornado (Nginx)
  • разбор HTTP-запроса (Торнадо)
  • прочитайте строку тела POST (stringified JSON) и преобразуйте ее в словарь python (Tornado)
  • возьмите данные из Redis (блокировка), расположенного на той же машине (py-redis с hiredis)
  • обработка данных (python3.4)
  • обновить Redis на той же машине (py-redis с hiredis)
  • подготовить stringified JSON для ответа (python3.4)
  • отправить ответ прокси (Торнадо)
  • отправить ответ клиенту (Nginx)

мы думали, что улучшение скорости придет от протокола uwsgi, мы можем установить Nginx на отдельный сервер и прокси-сервер все запросы к uWSGI с протоколом uwsgi. Но после попытки всех возможных конфигураций и изменения параметров ОС мы все еще не можем заставить его работать даже при текущей нагрузке. Большую часть времени журнал nginx содержит 499 и 502 ошибки. В некоторых конфигурациях он просто перестал получать новые запросы, как будто он достиг некоторого предела ОС.

Итак, как я уже сказал, у нас есть 32 ядра, 60 ГБ свободной памяти и очень быстрая сеть. Мы не делаем тяжелых вещей, только очень быстрые блокирующие операции. Какова наилучшая стратегия в этом случае? Процессы, Потоки, Асинхронность? Какие параметры ОС должны быть установлены?

текущая конфигурация:

[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

I знайте, что пределы слишком высоки, начали пробовать все возможные значения.

обновление:

Я закончил со следующей конфигурацией:

[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

1 ответов


Я думаю, что ваша обработка запроса примерно ломается следующим образом:

  • http разбор, маршрутизация запросов, JSON разбор
  • выполнить некоторый код python, который дает запрос redis
  • (блокировка) redis запрос
  • выполнить некоторый код python, который обрабатывает ответ redis
  • сериализация JSON, сериализация HTTP-ответа

вы можете проверить время обработки на почти неработающей системе. У меня предчувствие, что поездка туда и обратно свелась бы к 2 или 3 миллисекундам. При загрузке 70% CPU это составит около 4 или 5 мс (не считая времени, проведенного в очереди запросов nginx, только обработка в uwsgi worker).

при 5K req / s ваш средний запрос в процессе может быть в 20 ... Диапазон 25. Достойное соответствие вашей виртуальной машине.

следующий шаг-сбалансировать ядра процессора. Если у вас 32 ядра, нет смысла выделять 1000 рабочих процессов. Вы можете в конечном итоге задушить систему накладные расходы на переключение контекста. Хорошая балансировка будет иметь общее количество рабочих (nginx+uWSGI+redis) в порядке величины в качестве доступных ядер процессора, возможно, с небольшим дополнительным покрытием для блокировки ввода-вывода (т. е. файловой системы, но в основном сетевые запросы, выполняемые на другие хосты, такие как СУБД). Если блокировка ввода-вывода становится большой частью уравнения, подумайте о перезаписи в асинхронный код и интеграции асинхронного стека.

первое наблюдение: вы выделяете 10 работников nginx. Однако время процессора nginx тратит на запрос намного меньше, чем время uWSGI тратит на него. Я бы начал с выделения около 10% системы nginx (3 или 4 рабочих процессов).

остаток должен быть разделен между uWSGI и redis. Я не знаю о размере ваших индексов в redis или о сложности вашего кода python, но моя первая попытка будет 75%/25% разделить между uWSGI и redis. Это поставило бы redis около 6 рабочих и uWSGI около 20 рабочих + мастер.

Что касается опции потоков в конфигурации uwsgi: переключение потоков легче, чем переключение процессов, но если значительная часть вашего кода python связана с процессором, он не будет летать из-за GIL. Опция потоков в основном интересна, если значительная часть времени обработки заблокирована для ввода-вывода. Вы можете отключить потоки или попробовать с workers=10, threads=2 в качестве начальной попытки.