Быстрый и производительный web сервера на nginx

В силу всё большей популярности web-сервера nginx и выходом уже релиза 1.0 решил понемногу переводить свои сервера на nginx. Но для успешной работы web-сайта одного nginx’a нам мало. Нужно ещё заставить выполнять php/cgi скрипты. Об этом собственно речь и пойдёт в статье.

Тестовый стенд: FreeBSD 8.1-RELEASE i386

1) Установка nginx (версия 1.05).

Ставить будем из портов:

#cd /usr/ports/www/nginx && make install clean

В качестве опций я выбрал такие:

[X] HTTP_MODULE               Enable HTTP module
[X] HTTP_CACHE_MODULE         Enable http_cache module
[X] HTTP_REWRITE_MODULE       Enable http_rewrite module
[X] HTTP_STATUS_MODULE        Enable http_stub_status module

Настройка сервера достаточно проста. Приводим конфигурационный файл /usr/local/etc/nginx/nginx.conf к такому виду:


#Число процессорных ядер
worker_processes 4;

#Уменьшаем количестве вызовов gettimeofday()
time_resolution 100ms;

error_log /var/log/nginx/error.log;

events {
worker_connections 10240;
use kqueue; # FreeBSD only
multi_accept on;
}

http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;

sendfile off;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
reset_timedout_connection on;
server_tokens off;

client_body_buffer_size 1K;
client_header_buffer_size 1k;
client_max_body_size 1k;
large_client_header_buffers 2 1k;
client_body_timeout 10;
client_header_timeout 10;
send_timeout 10;

gzip on;
gzip_min_length 1000;
gzip_buffers 16 8k;
gzip_types text/plain text/css text/xml application/x-javascript application/xml application/xhtml+xml;

server {
listen 80 default sndbuf=16k rcvbuf=8k accept_filter=httpready;
server_name _;

location / {
root /usr/local/www/nginx;
index index.html index.htm;
}

#restrict all ".files" (.htpasswd, .git,...)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/local/www/nginx-dist;
}
}

Примечание.

1) Приоритет рабочих процессов следует изменять с осторожно­стью, иначе они могут просто «задавить» все остальные сервисы, включая memcached и процессы базы данных. В идеале лучше протестировать работу nginx с дефолтными настройками и лишь затем приступать к экспериментам.

2) Опция use kqueue немного ускоряет работу nginx во FгeeBSD благодаря использованию механизма kqueue вместо более мед­ленного epoll.

3) Системный вызов sendfile() применяется для единовременной отправки содержимого целого файла в сокет. Метод с использованием этого вызова работает быстрее, чем стандартное последовательное копирование данных, и позволяет сэкономить на оперативной памяти. Однако, если сервер оснащен недостаточным количеством ОЗУ, sendfile() только вынудит nginx частосвопиться и тем самым за­медлит его работу.

Его лучше использовать, если отдаваемый контент
полностью помещается в оперативную память. aio лучше использовать, когда
оперативы мало, а активного контента — много.

4) Оnция tcp_nopush заставляет nginx отправлять НТТР-заголовки в одном пакете, но она бесполезна без sendfile.

5) GZIР-компрессия также может сыграть с сервером злую шутку. С одной стороны, такая комnрессия уменьшает объем пере­даваемых сервером данных, благодаря чему он может успеть обработать больше заnросов, с другой — повышает нагрузку на процессор, что приводит к прямо nротивоположному результату. Поэтому точно выяснить, нужна ли она тебе, можно только экспе­риментальным путем, причем эксnерименты следует проводить под предельной нагрузкой.

Добавляем строку запуска nginx’a в /etc/rc.conf:

#echo 'nginx_enable="YES"' >> /etc/rc.conf'

После, запускаем nginx и смотрим:

#/usr/local/etc/rc.d/nginx start
#sockstat | grep nginx
www nginx 51589 6 tcp4 *:80 *:*
www nginx 51589 10 stream -> ??
root nginx 45280 6 tcp4 *:80 *:*
root nginx 45280 9 stream -> ??
root nginx 45280 10 stream -> ??

2) Установка php (версия 5.3.6_1)

Для версии 5.3 поддержка fpm уже присутствует в ядре php. Для версии 5.2 нужно качать патч.

Ставим из портов:

#cd /usr/ports/lang/php5 && make install clean

В качестве опций я выбрал такие:

[X] CLI        Build CLI version
[X] CGI        Build CGI version
[X] FPM        Build FPM version (experimental)
[X] SUHOSIN    Enable Suhosin protection system

После установки приводим конфигурационный файл /usr/local/etc/php-fpm.conf к такому виду:

[global]
pid = run/php-fpm.pid
error_log = log/php-fpm.log
log_level = notice
[www]
listen = /tmp/php-fpm.sock
user = www
group = www
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 5
pm.max_spare_servers = 35

где параметры pid и error_log имеют путь относительно каталога /var

Примечание:

Если вы планируете использовать virtualhosts, то рекомендую создавать на каждый сайт отдельный пул, отдельного пользователя, отдельные настройки. Например:

[global]
...
[site1]
user=site1
group=www
listen=/tmp/php-fpm_site1.sock
...
[site2]
user=site2
group=www
listen=/tmp/php-fpm_site2.sock
...

Это добавит безопасности и независимости сайтов друг от друга

Добавляем строку запуска nginx’a в /etc/rc.conf:

#echo 'php_fpm_enable="YES"' >> /etc/rc.conf'

После, запускаем php_fpm и смотрим:

#/usr/local/etc/rc.d/php-fpm start
#sockstat | grep php-fpm
www php-fpm 45316 0 stream /tmp/php-fpm.sock
www php-fpm 45315 0 stream /tmp/php-fpm.sock
www php-fpm 45314 0 stream /tmp/php-fpm.sock
www php-fpm 45313 0 stream /tmp/php-fpm.sock
www php-fpm 45312 0 stream /tmp/php-fpm.sock
www php-fpm 45311 0 stream /tmp/php-fpm.sock
www php-fpm 45310 0 stream /tmp/php-fpm.sock
www php-fpm 45309 0 stream /tmp/php-fpm.sock
www php-fpm 45308 0 stream /tmp/php-fpm.sock
www php-fpm 45307 0 stream /tmp/php-fpm.sock
www php-fpm 45306 0 stream /tmp/php-fpm.sock
www php-fpm 45305 0 stream /tmp/php-fpm.sock
www php-fpm 45304 0 stream /tmp/php-fpm.sock
www php-fpm 45303 0 stream /tmp/php-fpm.sock
www php-fpm 45302 0 stream /tmp/php-fpm.sock
www php-fpm 45301 0 stream /tmp/php-fpm.sock
www php-fpm 45300 0 stream /tmp/php-fpm.sock
www php-fpm 45299 0 stream /tmp/php-fpm.sock
www php-fpm 45298 0 stream /tmp/php-fpm.sock
www php-fpm 45297 0 stream /tmp/php-fpm.sock
root php-fpm 45296 4 stream -> ??
root php-fpm 45296 5 stream -> ??
root php-fpm 45296 6 stream /tmp/php-fpm.sock

Теперь осталось изменить конфигурационный файл nginx’a так, что бы выполнение php-скриптов он передавал сокету /tmp/php-fpm.sock. Для этого добавляем такой код в файл nginx.conf, в секцию server :

server {
...
location ~ \.php$ {
root /usr/local/www/nginx;
fastcgi_pass unix:/tmp/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include /usr/local/etc/nginx/fastcgi_params;
}
...

Так же в блоке ‘location / {‘ меняем строку

index  index.html index.htm;

на такую

index  index.php index.html index.htm;

Теперь наш кореневой сайт может выполнять php-скрипты. А что если нам нужно добавить алиас и что бы в нём тоже выполнялись php-скрипты? Тогда добавляем такие блоки:

location /cacti {
alias /usr/local/www/cacti;
index index.php;
}

location ~ ^/cacti/(.*\.php)$ {
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /usr/local/www/cacti/$1;
fastcgi_param DOCUMENT_ROOT /usr/local/www/cacti;
fastcgi_pass unix:/tmp/php-fpm.sock;
include /usr/local/etc/nginx/fastcgi_params;
}

После этого перечитываем конфиг nginx’a и наслаждаемся успешной работой:

#/usr/local/etc/rc.d/nginx reload

3) Установка perl’a.

Подразумевается, что perl у вас уже установлен. Если нет, тогда ставьте версию 5.14 из портов.

Выполнять perl-скрипты мы тоже будем через FastCGI. В качестве обработчика мы выбрали fcgiwrap, который уже есть в портах. Ставим его:

#cd /usr/ports/www/fcgiwrap && make install clean

После установки добавляем такие строки в /etc/rc.conf:

fcgiwrap_enable="YES"
fcgiwrap_socket="unix:/var/run/fcgiwrap/fcgiwrap.sock"
fcgiwrap_user="www"

и запускаем:

#/usr/local/etc/rc.d/fcgiwrap start
#sockstat | grep fcgiwrap
www fcgiwrap 52145 0 stream /var/run/fcgiwrap/fcgiwrap.sock

Теперь переходим к настройкам nginx’a. Добавляем такий блок в секцию server:

location /cgi-bin2 {
alias /usr/local/www/cgi-bin2;
fastcgi_pass unix:/var/run/fcgiwrap/fcgiwrap.sock;
fastcgi_param SCRIPT_FILENAME /usr/local/www$fastcgi_script_name;
fastcgi_param SCRIPT_NAME /usr/local/www$fastcgi_script_name;
include /usr/local/etc/nginx/fastcgi_params;
}

Перечитываем конфиг nginx’a и радуемся:

#/usr/local/etc/rc.d/nginx reload

4) Установка eAccelerator (версия 0.9.6.1_1)

eAccelerator является PHP-акселератором, основное назначение которого состоит в кэшировании бинарного представления кода. Каждый раз при выполнении скрипта, PHP читает все подключаемые файлы и переводит их в бинарный код, при запросе скрипта операция повторяется. Задача eAccelerator состоит в сохранении бинарного кода для повторного использования, уменьшая время выполнения скрипта.

Внимание! eAccelerator работает с PHP в режиме fastcgi или mod_php.

Ставим из портов:

#cd /usr/ports/www/eaccelerator && make install clean

После установки открываем файл php.ini и вносим такие строки:

extension="eaccelerator.so"
eaccelerator.shm_size="32"
eaccelerator.cache_dir="/var/spool/eaccelerator"
eaccelerator.enable="1"
eaccelerator.optimizer="1"
eaccelerator.check_mtime="1"
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="3600"
eaccelerator.shm_prune_period="1800"
eaccelerator.shm_only="0"
eaccelerator.compress="1"
eaccelerator.compress_level="9"

После этого создаём директорию для кэша и назначаем соответствующие права:

#mkdir /var/spool/eaccelerator
#chmod 0700 /var/spool/eaccelerator
#chown www /var/spool/eaccelerator

После этого перезапускаем php-fpm:

#/usr/local/etc/rc.d/php-fpm reload

5) Accept-фильтры.

В 2000-ом году FreeBSD появились accept-фильтры. Они позволяют не передавать в accept() пришедшее соединение до тех пор, пока не придёт первый пакет с данными (фильтр dataready) или заголовок HTTP-запроса (фильтр httpready). Использование фильтров в Apache (а в нём они поддерживаются, начиная с версии 1.3.14) позволяет уменьшить число процессов. В серверах, использующих select(), poll() или kqueue(), например, в thttpd-2.22, фильтры уменьшают число открытых файлов. И наконец, в обоих случаях accept-фильтры уменьшают число переключений контекста процесса. (Взято с сайта Игоря Сысоева)

То есть они позволяют ускорять работу nginx’a. Замечу, что этот параметр работает только во FreeBSD. И так, для включения работы фильтров нужно либо пересобрать ядро с опциями

options ACCEPT_FILTER_HTTP
options ACCEPT_FILTER_DATA

Либо одну из них, в зависимости от того, какой фильтр вы будете использовать.

Если не хотите пересобирать ядро, можно подгрузить модули:

#kldload accf_http
#kldload accf_data

Если вы подгружаете модули, не забудьте добавить такие строки в /boot/loader.conf:

accf_http_load=YES
accf_data_load=YES

После этого переходим к настройке nginx’a. Вот какой параметр за это отвечает:

accept_filter=фильтр — задаёт название accept-фильтра. Работает только на FreeBSD, можно использовать два фильтра — dataready и httpready. По сигналу -HUP accept-фильтр можно менять только в последних версиях FreeBSD, начиная с 6.0, 5.4-STABLE и 4.11-STABLE.

Вот как будет выглядеть строка в конфиге nginx.conf:

listen 1.2.3.4:80 default sndbuf=16k rcvbuf=8k accept_filter=httpready;

6) Работа с сессиями php.

При использовании сессий в php, по умолчанию они сохраняются в /tmp и со временем забивают раздел. Что бы этого не произошло, нужно сделать так:

— изменить путь хранения сессий (в php.ini меняем параметр session.save_path = «/var/lib/php5» ну или другую папку, где места предостаточно)
— проверить значение переменных session.gc_probability и session.gc_divisor, что бы дробь session.gc_probability/session.gc_divisor существовала и имела более-менее нормальное значение
— настроить по крону сборщик мусора (garbage collector). Пример для debian’a

09,39 * * * * root [ -x /usr/lib/php5/maxlifetime ] && [ -d /var/lib/php5 ] && find /var/lib/php5/ -depth -mindepth 1 -maxdepth 1 -type f -cmin +$(/usr/lib/php5/maxlifetime) ! -execdir fuser -s {} 2>/dev/null \; -delete

Ложка дёгтя.

При установке на один из серверов появлялась ошибка в логах php-fpm:

[28-Jul-2011 12:36:28] WARNING: [pool www] child 2273 exited on signal 11 (SIGSEGV) after 2172.202986 seconds from start
[28-Jul-2011 12:36:28] NOTICE: [pool www] child 12535 started
[28-Jul-2011 12:36:35] WARNING: [pool www] child 2266 exited on signal 11 (SIGSEGV) after 2178.624133 seconds from start
[28-Jul-2011 12:36:35] NOTICE: [pool www] child 12552 started

При этом php-скрипты работали через раз: один раз работают, другой раз — пишет Сервис недоступен. Ну и когда писалось «Сервис недоступен», то в логах как раз светились заветные строки.

Как вариант лечения, я добавил такую строку

fastcgi_param PHP_ADMIN_VALUE "open_basedir=/usr/local/share/cacti/:/var/tmp/";

к блоку описания fastcgi для каждого из location’ов (пример приведён для location’а cacti). Может это быть связано с тем, что я использовал параметр open_basedir в описании location’a nagios. После этого всё заработало так, как должно быть.

Быстрый и производительный web сервера на nginx: 2 комментария

  1. Romanuy

    Подскажите пожалуйста у меня такая же проблема
    [28-Jul-2011 12:36:28] WARNING: [pool www] child 2273 exited on signal 11 (SIGSEGV) after 2172.202986 seconds from start
    [28-Jul-2011 12:36:28] NOTICE: [pool www] child 12535 started

    подскажите куда нужно это добавить
    fastcgi_param PHP_ADMIN_VALUE «open_basedir=/usr/local/share/cacti/:/var/tmp/»;

    Заранее большое спасибо.

    1. skeletor Автор записи

      Это нужно добавить в блок
      location ~ ^/cacti/(.*\.php)$ {
      (ну или как оно у вас там называется).

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *


*