php-fpm – PHP FastCGI менеджер процессов. Используется в связке с nginx + php. По моему мнению лучшая связка для веб-сайтов.
Цель
Разобраться в параметрах конфигурации, и решить проблему, которая возникла на продакшен сервере с чрезмерным потреблением оперативной памяти. Произошло это потому, что php-fpm породил множество дочерних процессов, которые с радостью съели память, и, в один прекрасный момент, когда еще запустился парсер, OOM-killer положил мою машину. Причем ночью. На 6 часов. Почему она именно зашатдаунилась, а не ребутнулась – это другой вопрос, но неприятный впечатлений была масса.
Конфигурация и термины
/etc/php-fpm.conf – глобальная конфигурация
/etc/php-fpm.d/* – конфигурация пулов
Pool – это группа процессов, выделенная для обработки запросов, поступающих на определённый порт или Unix-сокет. В PHP-FPM возможно использовать отдельные пулы для каждого сайта и точно распределять ресурсы, а также использовать разных пользователей и разные группы для каждого пула.
Если пулы работают от имени одного и того же пользователя, разделение приложений по пулам позволяет предотвратить ситуацию, когда высоконагруженное приложение постоянно держит занятыми процессы-обработчики, не давая таким образом нормально работать лёгким интерактивным приложениям.
Для каждого из пулов можно задавать разные настройки PHP. Причем эти настройки будут иметь приоритет выше, чем те, что в php.ini либо те, которые могут задаваться напрямую из php-скриптов.
Конфигурация и параметры
Итак, начнем с глобального конфига:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
;;;;;;;;;;;;;;;;;;;;;
; FPM Configuration ;
;;;;;;;;;;;;;;;;;;;;;
; Подключаем пулы из указанной директории
include=/etc/php—fpm.d/*.conf
;;;;;;;;;;;;;;;;;;
; Global Options ;
;;;;;;;;;;;;;;;;;;
[global]
; Расположение PID—файла
pid = /var/run/php—fpm/php—fpm.pid
; Путь до лога с ошибками и уровень логирования (alert, error, warning, notice, debug)
error_log = /var/log/php—fpm/error.log
log_level = notice
; Если количество процессов достигнет указанного, то произойдет рестарт ...
emergency_restart_threshold = 8
; ... через 1 минуту ...
emergency_restart_interval = 1m
; ... с лимит времени реакции в 10 секунд на сигнал от мастера
process_control_timeout = 10s
; Демонизация процесса
daemonize = no
|
Теперь можно рассмотреть конфигурацию пула (для примера /etc/pfp-fpm.d/blog.conf):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
; Имя пула (не должно совпадать с именем пользователя в системе)
[www]
; Адрес с портом или сокет, на который будут идти запросы FastCGI запросы
listen = /tmp/php—fpm.sock
; listen.backlog это параметр backlog функции TCP listen того сокета, на котором висит fpm
; параметр backlog отвечает за размер очереди одновременно ожидающих подключений к сокету,
; то есть инициированных (SYN — SYN,ACK — ACK), но еще не принятых сервером (established)
; —1 использует текущий hard limit net.core.somaxconn
listen.backlog = —1
; Список ip клиентов, которым разрешено подключение
listen.allowed_clients = 127.0.0.1
; Владелец Unix—сокета, его группа и права доступа к сокету
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
; Воркеры пула будут работать от имени указанного пользователя и группы
user = nginx
group = nginx
; Метод порождения процессов
; static — строго постоянное количество процессов—обработчиков,
; dynamic — переменное количество обработчиков, для которых
;указываетсяминимальноеимаксимальное
; количество процессов, а также количество процессов—обработчиков
; «на подхвате»,которыедержатся
; готовыми на случай внезапного наплыва нагрузки, чтобы
; не терять время на порождение новых процессов—обработчиков,
; ondemand — режим, при котором обработчики порождаются только
; при поступлении запросов и завершаются спустя указанный период простоя.
pm = dynamic
; Число дочерних процессов, созданных для static, либо
; максимальное число, когда pm установлен в dynamic
pm.max_children = 7
; Число дочерних процессов, содаваемых при запуске (только dynamic)
pm.start_servers = 5
; Желаемое минимальное число неактивных процессов сервера (только dynamic)
pm.min_spare_servers = 5
; Желаемое максимальное число неактивных процессов сервера (только dynamic)
pm.max_spare_servers = 7
; Число запросов дочернего процесса, после которого процесс будет перезапущен
; Тут можно ограничить количество запросов, последовательно обслуживаемых одним процессом
; После этого процесс будет завершён и запущен снова — это может помочь от утечек памяти
pm.max_requests = 300
; Ссылка, по которой можно посмотреть страницу состояния FPM
;pm.status_path = /status
; Ссылка на ping—страницу мониторинга FPM (можно собрать своеобразный health—check)
;ping.path = /ping
; Эта директива может быть использована на настройки ответа на ping—запрос
;ping.response = pong
; Таймаут для обслуживания одного запроса, после чего рабочий
; процесс будет завершен (если не сработает max_execution_time)
;request_terminate_timeout = 0
; Таймаут для обслуживания одного запроса, после чего PHP backtrace
; будет сохранен в файл ‘showlog’
; Доступные единицы измерения: s(секунды), m(минуты), h(часы) или d(дни)
request_slowlog_timeout = 2s
; Лог—файл для медленных запросов
slowlog = /var/log/php—fpm/www—slow.log
; Устанавливает лимит дескрипторов открытых файлов rlimit.
; Значение по умолчанию: определяется значением системы.
;rlimit_files = 1024
; Устанавливает максимальное количество используемых ядер rlimit
;rlimit_core = 0
; Директория chroot окружения при старте
;chroot =
; Chdir изменяет текущую директорию при старте
;chdir = /var/www
; Перенаправление STDOUT и STDERR рабочего процесса в главный лог ошибок.
; Если не установлен, STDOUT и STDERR будут перенаправлены в /dev/null в
; соответствии со спецификацией FastCGI
;catch_workers_output = yes
; ограничение выполнение файлов по расширению имени
security.limit_extensions = .php .php3 .php4 .php5
; Передача переменных окружения и настроек PHP пулу
;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp
;php_admin_value[sendmail_path] = /usr/sbin/sendmail —t —i —f www@my.domain.com
;php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php—fpm/www—error.log
php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 128M
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/session
|
Также, отредактируем php.ini файл, указав:
1
|
cgi.fix_pathinfo = 0
|
О подсчетах параметров:
Остается закономерный вопрос: а как же выбрать комфортные параметры для сайта? С дефолтным конфигом сделаем следующее:
1. Предположим, что у нас есть VDS с 512 Mb оперативной памяти, из которой 200 Mb мы можем выделить под PHP-FPM.
2. Возьмем “тяжелые” страницы сайта и откроем их (желательно почти параллельно) в том колличестве, сколько потенциально их может быть открыто параллельно в пиковые нагрузки (при этом количество страниц возьмем с небольшой дельтой в большую сторону)
3. Смотрим через htop количество памяти, которое забрали под себя процессы php-fpm. Добустим это 20 Mb на каждый. Тогда добавив дельту в 10%, будем считать что это 22 Mb.
4. Считаем значение для параметра pm.max_children:
200 / 22 = 9.09
Приемлемым значением pm.max_children будет 9. Это значение основано на среднем значении и возможно далее его необходимо будет изменить, когда вы заметите длительное время использования памяти процессом. После быстрого тестирования несложно выбрать значения pm.start_servers, pm.min_spare_servers и pm.max_spare_servers. Максимальное количество запросов на процесс по умолчанию не ограничено, но хорошо бы установить какое-нибудь небольшое значение, например 200, и избежать проблем с памятью. Такого вида настройка может обрабатывать большое количество запросов, даже если значение параметра невелико.
В итоге, получаем примерно такой блок конфигурации:
1
2
3
4
5
|
pm.max_children = 9
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200
|