time

Настройка Gunicorn и uWSGI, сравнение производительности

Решил сменить связку Apache (mod_wsgi) + nginx на что-то более легковесное и проворное. Хоть выбор и велик решил пока ограничится двумя кандидатами - Gunicorn и uWSGI.

nginx

Начнем с установки nginx, он понадобиться как буфер перед gunicorn и для отдачи статики. До версии 0.8.40 нужно было собирать nginx с поддержкой модуля uwsgi (про uwsgi ниже), в последующих версиях модуль уже добавлен.

Под Ubuntu последнюю версию nginx можно установить из любого из двух PPA:

Stable version (на данный момент 1.0.5)

sudo apt-add-repository ppa:nginx/stable
sudo apt-get update
sudo apt-get install nginx

Development version (на данный момент 1.1.0)

sudo apt-add-repository ppa:nginx/development
sudo apt-get update
sudo apt-get install nginx

Под CentOS последнюю версию nginx можно установить из CentALT репозитария

rpm -ihv http://centos.alt.ru/repository/centos/5/i386/centalt-release-5-3.noarch.rpm
yum install nginx 

Проверим версию nginx и поддержку uwsgi

nginx -V

Gunicorn

Со слов разработчиков gunicorn'a это WSGI HTTP сервер, написанный на питоне и легкий в настройке. Среди заявленных возможностей:

  • поддержка WSGI, Django и Paster
  • async, sync worker
  • балансировка нагрузки через pre-fork и shared socket
  • разные хуки для расширение возможностей

Установим gunicorn через pip (если pip не работает то вам сюда). Я устанавливаю gunicorn сразу в виртуальное окружение (virtualenv) проекта.

pip install gunicorn setproctitle

В директории с проектом разместим файл с настройками - gunicorn.conf.py.

bind = "127.0.0.1:8888"
# или через сокет
# bind = "unix:/home/proft/projects/blog/run/blog.socket"
workers = 5
user = "www"
group = "www"
logfile = "/home/proft/projects/blog/log/gunicorn.log"
loglevel = "info"
proc_name = "blog"
#pidfile = "/home/proft/projects/blog/gunicorn.pid"

Число worker'ов расчитываеться по такой формуле 2xCPUs + 1.

Тестовый запуск для django проекта

gunicorn_django -c gunicorn.conf.py

Перезагрузка gunicorn (этот способ использовать в случаи, если не настроен supervisor и есть параметр pidfile в конфигурационном файле).

kill -HUP /home/proft/projects/blog/gunicorn.pid

Рецепт перезапуска gunicorn при работе из vim.

Для управления gunicorn'ом с консоли с curses-интерфейсом есть пакет gunicorn-console.

Настройки proxy_params для nginx остался таким же.

server {
    listen 127.0.0.1:80;
    server_name blog.local;
    server_tokens off;
    access_log /home/proft/projects/blog/log/nginx_access.log;
    error_log /home/proft/projects/blog/log/nginx_error.log;

    location / {
        proxy_pass http://127.0.0.1:8888;
        # или через сокет http://unix:/home/proft/projects/blog/run/blog.socket
        # proxy_pass 
        include proxy_params;
    }

    location /static {
        root /home/proft/projects/blog/apps;
        expires 24h;
    }
} 

Для запуска нескольких проектов на том же сервере - просто меняем порт в настройках gunicorn и nginx.

Осталось настроить supervisor для удобного управления gunicorn-сервером с консоли и автоматического перезапуска в случаи аварии.

Установим supervisor и зависимый модуль elementtree под Ubuntu

pip install elementtree
sudo apt-get install supervisor

Установим supervisor и зависимый модуль elementtree под CentOS

pip install elementtree supervisor

Все ниже описанные действия касательно supervisor относятся только к CentOS. Добавим скрипт для управления supervisord

wget http://proft.me/static/linux/supervisord.txt
mv supervisord.txt /etc/init.d/supervisord
chmod +x /etc/init.d/supervisord

В /etc/init.d/supervisord нужно подправить путь к python.

Добавим supervisord в автозагрузку

chkconfig supervisord on

Создадим директорию для файлов с настройками для проектов

mkdir -p /etc/supervisor/conf.d/

Добавим в /etc/supervisord.conf

[include]
files = /etc/supervisor/conf.d/*.conf

Настроем supervisor для тестового проекта на django, для этого сохраним в файл /etc/supervisor/conf.d/blog.conf следующие строки (для Ubuntu и CentOS)

[program:blog]
command=/home/proft/.virtualenvs/blog/bin/gunicorn_django -c /home/proft/projects/blog/gunicorn.conf.py
directory=/home/proft/projects/blog/apps
user=www
group=www
autostart=true
autorestart=true
redirect_stderr=True
daemon = False
debug = False
logfile = /home/proft/projects/blog/log/supervisor.log
loglevel = "info"

Если gunicorn.conf.py не лежит рядом с settings.py, то в command, через пробел, нужно дописать полный путь к settings.py.

Настроем supervisor для тестового проекта на flask, для этого сохраним в файл /etc/supervisor/conf.d/blog.conf следующие строки (для Ubuntu и CentOS)

[program:blog]
command=/home/proft/.virtualenvs/blog/bin/gunicorn -c /home/proft/projects/blog/gunicorn.conf.py main_pyfile:app
directory=/home/proft/projects/blog/apps
user=www
group=www
autostart=true
autorestart=true
redirect_stderr=True
daemon = False
debug = False
logfile = /home/proft/projects/blog/log/supervisor.log
loglevel = "info"
environment = PYTHON_EGG_CACHE=/home/proft/projects/blog/.python-eggs,PYTHONPATH=/home/proft/projects/blog:$PYTHONPATH

Изменилась команда вызова gunicorn в параметре command и добавился параметр environment.

Запустим supervisord

service supervisord start

Для управления созданными настройками проектов для supervisor'а можно воспользоваться supervisorctl

sudo supervisorctl {start,status,stop} blog

uWSGI

uWSGI — быстрый, легко конфигурируемый и самовосстановливаемый сервер, написанный на C. Про скорость еще ниже поговрим, а самовосстановливаемый означает, что если запрос длится больше заданого времени, то главный процесс прибьет текущего worker'a, обрабатующего текущий запрос и запустит нового. Разработчики называют это - Harakiri mode.

Еще из приятных моментов

  • опиця idle - убить worker'a, если он не используеться n секунд - удобно для экономии памяти
  • возможность перезапуска сервера при измении кода (как runserver в django) или по touch, как было для mod_wsgi
  • при большом количестве python-проектов можно создать общию директории с настройками и перезапуск по touch или редактированию конфигурационного файла - режим Emperor

Установим последнюю версию uWSGI

pip install http://projects.unbit.it/downloads/uwsgi-latest.tar.gz

Конфигурационный файл с настройками для django-проекта

[uwsgi]
djangoproject = /home/proft/projects/blog/apps/
env = DJANGO_SETTINGS_MODULE=settings
virtualenv = /home/proft/.virtualenvs/blog/
chdir = %(djangoproject)
module = django.core.handlers.wsgi:WSGIHandler()
socket = /home/proft/projects/blog/blog.sock
master = true
processes = 5
idle = 3600
uid = www
gid = www
harakiri = 30
max-requests = 3000
logto = /home/proft/projects/blog/log/uwsgi.log
buffer-size = 32768
post-buffering = 8192

Настройки supervisor для проекта сохраним в файл /etc/supervisor/conf.d/blog_uwsgi.conf

[program:blog_uwsgi]
command=/usr/local/bin/uwsgi -ini /home/proft/projects/blog/uwsgi.ini
directory=/home/proft/projects/blog/apps
user=www
group=www
autostart=true
autorestart=true
redirect_stderr=True
daemon = False
debug = False
logfile = "/home/proft/projects/blog/log/supervisor_uwsgi.log"
loglevel = "info"

Настройки для nginx

server {
    listen 127.0.0.1:80;
    server_name blog.local;
    server_tokens off;
    access_log /home/proft/projects/blog/log/nginx_access.log;
    error_log /home/proft/projects/blog/log/nginx_error.log;

    location / {
        uwsgi_pass unix:///home/proft/projects/blog/blog.sock;
        include uwsgi_params;
    }

    location /static {
        root /home/proft/projects/blog/apps;
        expires 24h;
    }
} 

Сравнение производительности Gunicorn vs uWSGI vs mod_wsgi

А теперь самое интресное - кто быстрее всех из трех заявленных конкурсантов :). Тесты проводились на двух ядерном ноуте, каждое ядро по 1.66Hz, 2 GB RAM, Ubuntu 11.04.

Замеры оценок проводились так:

  • для каждого типа сервера производился холодный старт, последовательность старта: nginx, процес supervisor(отвечающий за проект), ab. При повторном запуске ab (для того же типа сервера) показатили Requests per second возрастали примерно на 1 - 1.5 позиции.

Команда для тестирования

ab -c10 -n100 http://blog.local/

Замер используемой памяти (RSS (resident set size)) (в МБайт) проводилась с помощью команды

# для gunicorn
ps -eo fname,rss | grep gunicorn | awk '{sum +=$2} END {print sum/1024}'
# для uwsgi
ps -eo fname,rss | grep uwsgi | awk '{sum +=$2} END {print sum/1024}'
# для apache2 + mod_wsgi
ps -eo fname,rss | grep apache2 | awk '{sum +=$2} END {print sum/1024}'

после запуска ab.

Gunicorn + nginx uWSGI + nginx Apache2 + mod_wsgi + nginx
Сравнение производительности Gunicorn vs uWSGI vs mod_wsgi Сравнение производительности Gunicorn vs uWSGI vs mod_wsgi Сравнение производительности Gunicorn vs uWSGI vs mod_wsgi
Concurrency Level:      10
Time taken for tests:   8.906 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      1733400 bytes
HTML transferred:       1721900 bytes
Requests per second:    11.23 [#/sec]
Time per request:       890.607 [ms]
Time per request:       89.061 [ms]
Transfer rate:          190.07 [Kbytes/sec]
Concurrency Level:      10
Time taken for tests:   11.738 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      1733400 bytes
HTML transferred:       1721900 bytes
Requests per second:    8.52 [#/sec]
Time per request:       1173.770 [ms]
Time per request:       117.377 [ms]
Transfer rate:          144.22 [Kbytes/sec]
Concurrency Level:      10
Time taken for tests:   12.824 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      1736252 bytes
HTML transferred:       1721900 bytes
Requests per second:    7.80 [#/sec]
Time per request:       1282.441 [ms]
Time per request:       128.244 [ms]
Transfer rate:          132.21 [Kbytes/sec]
RSS: 95.9492 МБайт RSS: 98.1445 МБайт RSS: 133.008 МБайт

Таким образом gunicorn оказался самым проворным и менее прожордивый по ресурсам памяти.

blog comments powered by Disqus