Стабилизируем PHP на бою - что и почему "роняет" веб-серверИсточник: habrahabr AlexSerbul
Вы отвечаете за стабильность работы веб-проекта на PHP. Нагрузка постоянно растет, добавляются фичи, клиенты довольны. В один прекрасный день начинают появляться загадочные ошибки… Ошибки серверного софта… которые программисты не знают как исправить, т.к. "ломается" серверный софт, например связка apache-PHP - а клиент получает в ответ на запрос страницу о регламентных работах. Веб-разработчик часто не обладает глубокими знаниями в программировании на C в unix/linux, а сисадмин нередко, к сожалению, глубже bash в систему не погружается. Настоящий хардкор :-) Нестабильная работа серверных скриптовНередко, определенные страницы веб-проекта начинают сходить с ума. Например выполняться по 15 минут и выяснить, чем же они занимаются, непросто. В прошлом посте на данную тему я описал одну из методик определения, чем занимается PHP-скрипт на боевом сервере, но чувствуется, что нужен более мощный инструмент. На практике я часто встречаю проекты, которые сталкиваются с подобным классом ошибок "серверного софта", и в команде не всегда знают, что делать. В логе apache часто появляются сообщения о нарушении сегментации (segmentation fault), клиенты получают страницу об ошибке, а веб-разработчик с сисадмином ломают себе голову, играются с разными версиями PHP/apache/прекомпилятора, собирают PHP из исходников с разными опциями снова и снова, пишут о багах, а им доказывают, что это баги не PHP, а их кода и так до бесконечности… В статье я хочу рассказать как можно просто и быстро найти причину, почему PHP рассыпался на боевом сервере и устранить ее - не погружаясь в прекрасный мир системного программирования на C для unix :-) От вас потребуется желание и одна чашечка кофе. Смотрим в лог ошибок веб-сервераЕсли в логе ошибок apache вы видите что-то подобное, то статья для вас: [Mon Oct 01 12:32:09 2012] [notice] child pid 27120 exit signal Segmentation fault (11) В данном случае бесполезно искать подробную информацию в логе ошибок PHP - ведь грохнулся сам процесс, а не скрипт. Если заранее не сделать на nginx симпатичную страничку о регламентных работах, то клиенты увидят аскетичную ошибку "50*". Хочется дать кому-нибудь в морду, но кому? :-) Чтобы отвлечься от деструктивных решений, вспомним теорию. Что такое "signal"? Это, можно сказать, средство, которое операционная система использует, чтобы сказать процессу, что он, например, не прав :-) Берет и, нарушая законы математики, делит на… 0, или насильственными действиями вызывает переполнение стека. В данном случае мы видим сигнал с номером 11 и названием "SIGSEGV". Список сигналов можно посмотреть, выполнив "kill -l": ... 11) SIGSEGV ... А за что убили то?Теперь найдем причину, за что же убили процесс apache-PHP? Для этого нужно настроить создание дампа памяти процесса в момент убийства :-) или coredump. Да, да - до сих пор используется устаревший лет эдак на 50 термин, означающий сохранение данных с магнитных сердечников. Как только в следующий раз процесс будет убит операционной системой, будет ядром создан файл - место и его название можно настроить. Если вы в консоли, просто наберите "man 5 core". Например, можно складывать файлы в папочку так: echo "/tmp/httpd-core.%p" > /proc/sys/kernel/core_pattern Если ничего не задать, система создаст файл с именем "core.#process_number#" в рабочей директории процесса. Только проследите, чтобы процесс apache-PHP имел туда право записи. Это еще не всё. По-умолчанию, скорее всего, в вашей системе отключена генерация coredump-файлов. Ее можно включить, вставив в начало скрипта запуска веб-сервера строку: ulimit -с unlimited или, чтобы сделать настройку постоянной, отредактировать файлик "/etc/security/limits.conf". Туда можно вставить: apache - core -1 Подробности по формату файла - " man limits.conf". Однако, пока я для apache не настроил папку для coredump-файлов, ничего не работало("/etc/httpd/conf/httpd.conf"): CoreDumpDirectory /tmp Теперь перезапускаем апач: service httpd restart Тестируем. Вручную убьем процесс: ps aux / grep httpd … kill -11 12345 Смотрим в "/var/log/httpd/error_log": [Mon Oct 01 16:12:08 2012] [notice] child pid 22596 exit signal Segmentation fault (11), possible coredump in /tmp В "/tmp" теперь найдем файлик с названием типа "/tmp/httpd-core.22596" Вы научились получать дамп памяти убитого процесса. Теперь ждем, когда процесс будет убит естественным образом. На месте преступления - толкуем coredumpВажно знать, что если PHP собрана без отладочных символов (ключик --enable-debug, -g для gcc при компиляции) - мы потеряем много полезной информации. Однако, если вы собрали PHP из исходников даже без этой опции, и исходники лежат рядом - этого может хватить для анализа. Еще есть очень распространенное заблуждение, что отладочная сборка влияет на производительность и потребляемую процессом память (memory footprint). Не влияет, а лишь увеличивается размер исполняемого файла. Поэтому, если не сможете разобраться в причине ошибки без отладочной сборки - попросите сисадмина собрать модуль PHP с отладочными символами. Чем открыть coredump? Конечно старой и "очень доброй" утилитой - gdb, изначально написанной верховным апостолом движения Разобраться, как работает отладчик, не займет много времени. Можно за пару часиков поглотить один из самых занимательных учебников, а можно попросить это сделать сисадмина ;-) Обычно открывают coredump так: gdb путь_к_выполняемому_файлу_веб-сервера путь_к_coredump Все уважающие себя разработчики на C в unix конечно умеют пользоваться этим отладчиком, делают это, наверное, каждый день, но, к сожалению, их может не быть в вашей команде. И есть еще одно неприятное НО… Отладка PHP в gdb - черная магияДело в том, что скомпилированный в байткод скрипт PHP это… не совсем программа на C ;-) Нужно, правда совсем немного, разобраться во внутренностях движка Zend - и вы все поймете довольно быстро. А именно - нужно найти в трейсе последний вызов функции execute, перейти в этот frame стека и исследовать локальные переменные (op_array), а также заглянуть в глобальные переменные движка Zend:
В op_array можно запутаться, поэтому полезна команда просмотра типа этой структуры:
Процесс отладки заключается в хождении между фреймами стека ("frame N"), переходе в каждый вызов функции "execute" и исследовании ее локальных аргументов ("print name", "ptype name"). Чем меньше номер фрейма, тем вы глубже. Иногда полезно зайти в гости в экстеншн PHP и посмотреть, где произошла ошибка и почему (хотя бы попытаться понять причину).
И так далее… Если вы поперхнулись кофе :-), то просто запомните, что переходя между фреймами стека вызовов с помощью команды "frame #N#", можно смотреть всего определенные элементы этой структуры - и вы точно сможете установить в каком файле PHP была вызвана функция PHP, какую функцию она вызвала и т.п. - и доберетесь до причины "Segmentation Fault" или другой ошибки, убившей процесс. И объясните программистам - в чем причина и ее поправят! Быстро и, надо быть оптимистами - навсегда. Распространенные причины ошибокНачните просматривать coredump-файлы (или поручите это сисадмину) и вы довольно быстро научитесь классифицировать ошибки по группам: 1) Проблемы в расширениях PHP. В этом случае либо отключите расширение, либо попробуйте поиграть его настройками. Вы точно знаете, что проблема в нем, дело за малым. 2) Проблема с рекурсией, стеком. Вы можете наступить на ошибку, при которой функция библиотеки, например, pcre, входит в рекурсию и вызывает себя тысяч двадцать раз. Можно либо настроить параметры библиотеки или, если лень, добавить процессу побольше стека ("/etc/init.d/httpd"): ulimit -s "ставим значение больше" А текущее значение можно посмотреть командой: "ulimit -a" (man ulimit, далее ищем "ulimit"). 3) Проблемы в ядре PHP - тут нужно писать разработчикам PHP :-) В общем, круг причин ошибки будет серьезно сокращен. Что нам и нужно. Отладка запущенного процессаЭто еще не все. Если вы не можете получить coredump - можно подключиться к запущенному процессу и погулять по нему. Пока вы внутри процесса, его выполнение приостанавливается ("ps aux / grep apache / grep 'T '" - он будет в состоянии трейсинга). Когда покинете его - он снова продолжит выполняться. Подключиться можно так: gdb -p ид_процесса ИтогиВ статье мы научились "правильно готовить" ошибки серверного софта, делать отладочные сборки apache-PHP, создавать coredump-файлы и правильно их толковать, используя символьный отладчик. Еще мы узнали, что из coredump-файла можно найти конкретный файл PHP и функцию, вызвавшую ошибку. Теперь можно составить чеклист для менеджера для борьбы с загадочными серверными ошибками, в которых не могут разобраться ни веб-разработчики, ни сисадмины:
В заключение приглашаю всех на наш облачный сервис Битрикс24, в котором мы эффективно используем все описанные в статье технологии. Всем удачи и стабильной работы веб-проектов! |