Недавно я перешел с Apache mpm-prefork (модуль PHP) на mpm-worker (PHP-FPM) из-за проблем с памятью. Я запускаю довольно большое приложение PHP, которое требует ~ 20-30 Мбайт на процесс предварительной вилки.
В целом сервер работает стабильно и быстро. Однако время от времени страница недоступна для некоторые пользователей на несколько минут.
Рабочая гипотеза 1 (= приблизительная идея) заключается в том, что один из процессов (обычно 2, иногда до 5 или 6) зависает, и каждый клиент, назначенный этому процессу (например, 50% клиентов), получает сообщение об ошибке.
Рабочая гипотеза 2 заключается в том, что за это отвечает MaxRequestsPerProcess. После 500 вызовов процесс пытается завершить работу, mod_fcgid не выполняет корректного уничтожения, и пока процесс ожидает уничтожения, другие клиенты назначаются (и отклоняются) процессу. Но я не могу представить, что Apache был бы таким глупым.
Моя проблема: в журналах ошибок ничего нет, кроме некоторых
[warn] mod_fcgid: process ???? graceful kill fail, sending SIGKILL
У меня заканчиваются идеи, где отследить проблему. Он появляется спорадически, и спровоцировать мне пока не удалось. Производительность сервера (ЦП / ОЗУ) не должна быть проблемой, поскольку общая нагрузка в последние недели была в более низком диапазоне.
Спасибо за любые подсказки. Какие-либо комментарии к моим гипотезам (которые пока не помогли мне найти решение - я попытался отключить MaxRequestsPerProcess, но еще не знаю, помогло ли это)? Я был бы очень признателен за идеи, как отследить эту проблему.
Конфигурация Apache
<Directory /var/www/html>
...
# PHP FCGI
<FilesMatch \.php$>
SetHandler fcgid-script
</FilesMatch>
Options +ExecCGI
</Directory>
<IfModule mod_fcgid.c>
FcgidWrapper /var/www/php-fcgi-starter .php
# Allow request up to 33 MB
FcgidMaxRequestLen 34603008
FcgidIOTimeout 300
FcgidBusyTimeout 3600
# Set 1200 (>1000) for PHP_FCGI_MAX_REQUESTS to avoid problems
FcgidMaxRequestsPerProcess 1000
</IfModule>
Конфигурация модуля Apache
<IfModule mod_fcgid.c>
AddHandler fcgid-script .fcgi
FcgidConnectTimeout 20
FcgidBusyTimeout 7200
DefaultMinClassProcessCount 0
IdleTimeout 600
IdleScanInterval 60
MaxProcessCount 20
MaxRequestsPerProcess 500
PHP_Fix_Pathinfo_Enable 1
</IfModule>
Примечание. Тайм-аут был установлен на 2 часа, потому что в редких случаях приложению может потребоваться некоторое время для запуска (например, ночное задание cron, которое выполняет оптимизацию базы данных).
Стартовый сценарий
#!/bin/sh
PHP_FCGI_MAX_REQUESTS=1200
export PHP_FCGI_MAX_REQUESTS
export PHPRC="/etc/php5/cgi"
exec /usr/bin/php5-cgi
#PHP_FCGI_CHILDREN=10
#export PHP_FCGI_CHILDREN
Версии пакета
Я думаю, что вы на правильном пути с Гипоптезой 1. Совет mc0e довольно солидный, поэтому я в основном добавляю к нему.
Те сообщения журнала, которые вы видите, предполагают, что отдельные процессы блокируются под Prefork MPM, который обеспечивает лучшую изоляцию процесса, чем рабочий. Я видел это раньше в производственной среде, и это означает, что у вас есть некорректный код.
Между вашими максимальными запросами на ребенка и вашими зависающими процессами это создает почву для раздувания памяти. В документации конкретно говорится о том, что ненулевое значение помогает защитить от утечек памяти, но если вы установите слишком высокое значение, преимущества будут потеряны. Если ваши процессы висят поверх этого, это еще больше увеличивает общий объем памяти.
Это оставляет вам два немедленных вывода:
MaxRequestsPerChild
со значительным отрывом, как предполагал mc0e. Это помогает предотвратить достаточно долгую жизнь отдельных процессов для накопления значительных утечек памяти ... но, по его словам, 20-30M, вероятно, не так уж и важно.lsof
на ваших больших процессах может предоставить подсказку в зависимости от того, что делает код (например, утечка дескриптора файла и достижение максимального значения дескриптора файла может быть связано с тупиками процесса), но в противном случае вы смотрите на отладку кода.Я считаю, что 20-30 МБ на процесс довольно мало. На самом деле все относительно, но, например, для большинства приложений CMS потребуется не менее 100 МБ. Также ваш максимальный размер загрузки будет ограничен максимальным размером процесса, если это имеет значение.
Когда ваш сервер недоступен, вполне вероятно, что все рабочие процессы php заняты, но это только приблизительная причина. Что-то замедляет ваш сервер, так что, по крайней мере, какое-то время процессы php не успевают за входящими запросами. Трудно судить, что замедляет ваш сервер, но «изящное завершение с ошибкой» заставляет меня думать, что процесс, который должен был быть завершен, скорее всего, ожидает на диске.
Вы вошли в систему, пока это происходит? Система чувствует себя отзывчивой?
Вверху посмотрите на состояния процесса и найдите те, которые ожидают ввода-вывода. Их много? «Wa» в сводке вверху - это общее количество времени, которое процессы проводят в ожидании ввода-вывода. (Там написано в процентах, но это, скорее всего, процент времени одного процессора). Такие инструменты, как iotop, atop и vmstat, также могут быть полезны для получения представления о том, какие процессы привязаны к диску, и насколько диск ограничивает вашу общую производительность.
Ваше понимание того, что происходит, когда рабочий процесс недоступен для приема новых запросов, неверно. Новые запросы ему не присваиваются.
1000 запросов перед убийством воркера - это высокий уровень. Я бы посоветовал снизить его до 10-50.