В одном из наших проектов (социальная генеалогическая сеть), о котором я писал в данном топике, мы используем очередь отложенных событий, реализованную на мемкеше. Ее архитектура такова: приложение записывает в эту очередь различные события и данные, относящиеся к ним (тип события, входящие параметры, и функция обработчик этого события). После чего менеджер(-ы) очереди разбирают эту очередь и выполняют отложенные события. В частности такая очередь используется для сбора статистики, но также и для других более критичных к выполнению задач.
Поэтому очень важно обеспечить high availability для менеджера(-ов) очереди.
Но т.к. ф-ия обработчик очереди к нам приходит из вне, то за качество этого обработчика события мы не отвечаем, т.е. если обработчик вдруг выбросит ошибку, то нам ее нужно обработать и продолжить работу менеджера очереди. Но иногда случается, что обработчики выбрасывают фатальные ошибки (Fatal Error), и это может стать проблемой…
Для треккинга процессов (демонов), очень удобно пользоваться наблюдателями за процессами, такими как monit, мы используем monit для мониторинга сисстемных демонов. Кстати, на хабре недавно была статья о моните.
Но речь не о нем :-)
Я попросил одного из разработчиков моей команды сделать нормальный обработчик фатальной ошибки в коде менеджера очереди, а именно форк нового инстанса обработчика и логирование ошибки по типу события. На это я получил ответ, что в php фатальные ошибки обрабатывать в принципе невозможно и позорно об этом не знать и что: «компьютерные науки на текущем этапе своего эволюционного развития еще не располагают алгоритмами способными решить поставленную задачу опираясь на возможности php коректно...»
После этого я написал такой код, который обрабатывает фатальные, а также все другие ошибки в php приложении. Если кому-то еще он поможет, то я буду только рад.
Небольшое объяснение по текущему коду.
Fatal Error — мы ловим через буферизацию вывода в ф-ии fatal_error_handler
Остальные ошибки (все кроме фатальных) обрабатываются ф-ией handle_error
Если ошибок нет — код выполняется нормально :-)
Да, это также не является единственным средством high availability и отказоустойчивости.
Мы пытаемся запустить демон каждую минуту по крону, а код демона начинается ф-ией
т.е. если демон запущен, то мы прекращаем выполнение.
Открыт ко всем мнения и по возможности буду отвечать на все комментарии.
UPD: обратите внимание на ini_set('html_errors', 'on'); я потратил с пол часа времени не понимая почему обработчик не работает из-под CLI. Дело было как раз в HTML-ных ошибках. Т.к. из-под CLI они давались без HTML тегов, и условие preg_match("|(Fatal error:)(.+)(<br)|", $buffer, $regs) просто не выполнялось. // Вот так вот.
UUPD: Немного обновил код, дело в том, что форкая новый процесс через ф-ию system нужно позаботиться о том, чтобы убить процесс который форкал текущий, т.к. функция обработчик будет ждать результата выполнении ф-ии system, а он как извесно не вернется, ведь мы же создаем демона, в связи с этим вы получите кучу процессов, висящих в памяти, которые в конце концов забьют ее полностью.
Поэтому очень важно обеспечить high availability для менеджера(-ов) очереди.
Но т.к. ф-ия обработчик очереди к нам приходит из вне, то за качество этого обработчика события мы не отвечаем, т.е. если обработчик вдруг выбросит ошибку, то нам ее нужно обработать и продолжить работу менеджера очереди. Но иногда случается, что обработчики выбрасывают фатальные ошибки (Fatal Error), и это может стать проблемой…
Для треккинга процессов (демонов), очень удобно пользоваться наблюдателями за процессами, такими как monit, мы используем monit для мониторинга сисстемных демонов. Кстати, на хабре недавно была статья о моните.
Но речь не о нем :-)
Я попросил одного из разработчиков моей команды сделать нормальный обработчик фатальной ошибки в коде менеджера очереди, а именно форк нового инстанса обработчика и логирование ошибки по типу события. На это я получил ответ, что в php фатальные ошибки обрабатывать в принципе невозможно и позорно об этом не знать и что: «компьютерные науки на текущем этапе своего эволюционного развития еще не располагают алгоритмами способными решить поставленную задачу опираясь на возможности php коректно...»
После этого я написал такой код, который обрабатывает фатальные, а также все другие ошибки в php приложении. Если кому-то еще он поможет, то я буду только рад.
<?php
ini_set("display_errors", "on");
error_reporting(E_ALL);
ini_set('html_errors', 'on');
function fatal_error_handler($buffer) {
if (preg_match("|(Fatal error</b>:)(.+)(<br)|", $buffer, $regs) ) {
//Форкаем новый инстанс демона и готовимся к заавершению выполнения текущего скрипта
file_put_contents("php://stderr", "before fork (pid: " . getmypid() . ")\n");
system("php tester.php " . getmypid() . " &" );
return "ERROR CAUGHT, check log file" ;
}
return $buffer;
}
function handle_error ($errno, $errstr, $errfile, $errline)
{
if($errno & E_ALL){
// Логирование ошибки как в ф-ии выше
//switch в котором, собственно обрабатываем ошибку
switch ($errno) {
case E_USER_ERROR:
case E_USER_WARNING:
case E_USER_NOTICE:
default:
//do something
break;
}
ob_end_clean();
echo "CAUGHT OTHER THAN FATAL ERRORS!!! " . $errstr;
exit;
}
}
//code between ob_start and ob_end_flush is included by MQ Handler, so we know nothing about it, and this code could fire a Fatal Error
if(isset($_SERVER["argv"][1])){
file_put_contents("php://stderr", "kill {$_SERVER['argv'][1]}: ".var_export(posix_kill($_SERVER['argv'][1], 15), true)."\n");
}
ob_start("fatal_error_handler");
set_error_handler("handle_error");
while(true) {
//Just a Warning
//$a = 9/0;
sleep(10);
file_put_contents("php://stderr", "live\n");
//Fatal error - вызов необъявленной ф-ии
if(rand(1,10) % 2 == 1) {
ololo(123);
}
}
/*
Код без ошибок
*/
$a = rand(1,10);
echo $a."<br/>";
ob_end_flush();
echo "Program still executing....";
?>
* This source code was highlighted with Source Code Highlighter.
Небольшое объяснение по текущему коду.
Fatal Error — мы ловим через буферизацию вывода в ф-ии fatal_error_handler
Остальные ошибки (все кроме фатальных) обрабатываются ф-ией handle_error
Если ошибок нет — код выполняется нормально :-)
Да, это также не является единственным средством high availability и отказоустойчивости.
Мы пытаемся запустить демон каждую минуту по крону, а код демона начинается ф-ией
<?php
if (!checkSingleProcess()) {
exit;
}
function checkSingleProcess() {
$res = exec('ps aux | grep mq_manager.php | grep -v grep | grep -v '.getmypid(), $output, $return);
return $output == array();
}
* This source code was highlighted with Source Code Highlighter.
т.е. если демон запущен, то мы прекращаем выполнение.
Открыт ко всем мнения и по возможности буду отвечать на все комментарии.
UPD: обратите внимание на ini_set('html_errors', 'on'); я потратил с пол часа времени не понимая почему обработчик не работает из-под CLI. Дело было как раз в HTML-ных ошибках. Т.к. из-под CLI они давались без HTML тегов, и условие preg_match("|(Fatal error:)(.+)(<br)|", $buffer, $regs) просто не выполнялось. // Вот так вот.
UUPD: Немного обновил код, дело в том, что форкая новый процесс через ф-ию system нужно позаботиться о том, чтобы убить процесс который форкал текущий, т.к. функция обработчик будет ждать результата выполнении ф-ии system, а он как извесно не вернется, ведь мы же создаем демона, в связи с этим вы получите кучу процессов, висящих в памяти, которые в конце концов забьют ее полностью.