Проверка доступности сайта каждые N секунд
Проверять доступность сайта считаю занятием ничуть не менее важным, чем своевременное резервное копирование. Есть немало сервисов, позволяющих мониторить доступность сайта с интервалом вплоть до 1 минуты. Но что делать, когда хочется проверять сайт чаще? К примеру, раз в 10 секунд.
Возможна ситуация, когда сервер внезапно подвисает меньше чем на 1 минуту. Обычный мониторинг этого не замечает. А хочется знать точное время, когда такое происходит, чтобы посмотреть по логам, что в этот момент происходило на сервере. Мне не удалось найти такие инструменты, которые бы проверяли сайт чаще, чем раз в минуту. Но на то мне даны навыки кодинга, чтобы написать свой скрипт с блэк-джеком и девушками с пониженной социальной ответственностью.
Кстати, для обычного мониторинга доступности (раз в минуту) я уже много лет использую сервис host-tracker, упоминал его в этой статье. Также он мне бывает полезен, когда лень вспоминать, как настраивать задание в cron на периодический запуск php-скрипта.
Мониторинг доступности на стероидах
Проверять стабильность хостинга, которому вы не доверяете, предлагаю php-скриптом, размещённом на другом хостинге. Хотя бы на другом сервере, особенно хорошо если в другом ДЦ — тогда стремится к нулю вероятность одновременных перебоев на обоих серверах. Но даже если php-скрипт будет проверять свой собственный сервер, то по косвенным уликам можно вычислить время возможного перебоя (например, если время между двумя соседними записями в лог скрипта превышает установленный таймаут).
Для работы нам понадобятся функции:
function bench($fn){ $start = microtime(true); if ( $r = $fn() ) return $r; return round( microtime(true) - $start, 5 ); } function geturl($url, $timeout) { $curl = curl_init(); $url .= strpos($url, '?') ? '&' : '?'; $url .= 'r='.time().rand(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); curl_setopt($curl, CURLOPT_HEADER, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_FORBID_REUSE, true); curl_setopt($curl, CURLOPT_FRESH_CONNECT, true); $r = curl_exec($curl); curl_close($curl); return $r; }
bench($fn)
измеряет время выполнения куска кода (мы будем измерять время выполнения запроса). geturl($url, $timeout)
отправляет get-запрос, ограниченный таймаутом, установленным в параметре $timeout
. Чтобы уменьшить вероятность кэширования запроса, к $url
я добавляю параметр 'r='.time().rand()
. Имейте ввиду, если в работе своего движка используете этот get-параметр.
Также нужна функция для логирования. Можно использовать просто file_put_contents()
, но более гибким решением будет написать свою функцию, например, такую:
function tolog($s, $log=LOG) { $t = explode(".", $_SERVER["REQUEST_TIME_FLOAT"]); $t = date('d.m H:i:s', $t[0]); file_put_contents($log, "$t\t$s\n", FILE_APPEND); }
Здесь предполагается, что имя файла LOG
будет задано константой перед вызовом функции.
А теперь сам скрипт мониторинга:
define('ABSPATH', dirname( __FILE__ ) .'/'); define('LOG', ABSPATH .'log.txt'); // лог всех запросов define('TIMEOUT', 20); // таймаут запроса, секунд define('LOGLONG', ABSPATH .'loglong.txt'); // лог долгих запросов define('LONGTIME', 10); // считать запросы долгими, если выполняются дольше N секунд define('DELAY', 5); // пауза между запросами, секунд if (trim(file_get_contents('stop'))) { tolog('stop'); exit; } define('URL', 'http://сайт-пациент/checkup.php'); if (! file_exists(LOG)) tolog('start'); // пнём скрипт, если он остановился if (@ $_GET['wake']) { if (time()-filemtime(LOG) < TIMEOUT*2) exit; else tolog('wake'); } // мониторинг $benchTime = bench( function() { $result = geturl(URL, TIMEOUT); if (! $result) { $r = 'HTTP timeout'; } else { $result = explode("\r\n\r\n", $result, 2); $h = explode("\r\n", $result[0], 2); $h = $h[0]."\t"; if (strpos($h, '200 OK')) $h = ''; $result = $h.$result[1]; } tolog($result); } ); // в отдельный лог долгие ответы if ($benchTime > LONGTIME) tolog($benchTime, LOGLONG); sleep(DELAY); // следующий шаг $url = 'http://'. $_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']; geturl($url, 2);
Первым делом (на всякий пожарный) в директории скрипта я создаю пустой файл stop
. В кодинге случается всякое, особенно под утро. Может получиться так, что скрипт из-за ошибки в логике выполнения начнёт плодить процессы и ддосить собственный сервер. Чтобы при этом рука судорожно не тянулась к кнопке reset, есть файл stop
, запись в который значения 1
останавливает безумие скрипта, делая в логе соответствующую пометку.
Как видно, мониторинг сайта осуществляем запросами к скрипту checkup.php
на сайте-пациенте. О нём расскажу чуть ниже. Функцией file_exists(LOG)
проверяем наличие файла LOG
, если отсутствует — создаём. Нужно, чтобы файл существовал, потому что дальше по коду проверяем время его модификации: filemtime(LOG)
. Дело в том, что никакой хостинг не идеален, и наш проверяющий скрипт может остановиться. Чтобы этого не происходило, скрипт нужно периодически «пинать». Можно добавить ежеминутное задание в cron. Я добавил на host-tracker проверку url monitoring.php?wake=1
, и когда вызов скрипта происходит с параметром $_GET['wake']
— значит, осуществляется контроль работы. Тогда скрипт проверяет дату модификации файла LOG
, и продолжает работу только в том случае, если лог модифицировался более чем TIMEOUT*2
секунд назад. Иначе делается вывод, что работа скрипта не прекращалась, и дополнительные действия не требуются.
Суть мониторинга в измерении времени выполнения geturl(URL, TIMEOUT)
. Если запрос выполняется дольше TIMEOUT
секунд, curl_exec
не дожидается ответа и возвращает false
. Тогда в логе делается пометка HTTP timeout
. Если ответ пришёл в пределах TIMEOUT
секунд, тогда мы имеем заголовки и тело ответа. Меня интересует только первая строка заголовков. Если она не содержит 200 OK
, тогда я записываю её в лог. Также меня интересует тело ответа (об этом ниже) — его тоже записываю. Думаю, также имеет смысл где-то рядом с tolog($result)
в строковом значении $result
поискать HTTP
— если найдено, то результат проверки записать также в отдельный лог LOGWRONG
. Это вам задание для самостоятельного изучения, если надо.
Далее надеюсь понятно, что в LOGLONG
пишем время выполнения долгого запроса $benchTime
. Больший интерес вызывает следующий шаг
. После паузы в DELAY
секунд скрипт вызывает сам себя, чтобы выполнить очередную проверку. Для вызова самого себя я поставил ожидание выполнения запроса 2 секунды. Когда ставил 1 секунду, этого не всегда хватало, и скрипт останавливался каждые несколько часов. Хоть я и предусмотрел функционал «пинка», всё равно не хочется, чтобы скрипт сам по себе отваливался. Можно ещё увеличить это значение, но не стоит делать его больше DELAY
. Меня удовлетворило значение в 2 секунды.
Продвинутый читатель должно быть заметил, что получился этакий вариант «phpDaemon для ленивых» без возни с запуском из консоли, без родительских и дочерних процессов. Конечно, слишком грубое сравнение с phpDaemon, но в данном случае для данной задачи простое функциональное программирование позволяет обойтись без создания демонов. Те, кто совсем уж в теме, скажут: «Так это ж пародия на call no wait
, почему-то через curl!» Традиционный вариант функции callNoWait
через fsockopen
:
function callNoWait($url) { $url = parse_url($url); $fp = fsockopen( $url['host'], isset($url['port']) ? $url['port'] : ( isset($url['scheme']) && $url['scheme'] == 'https' ? '443' : '80' ), $errno, $errstr, 30 ); if (! $fp) return; $uri = $url['path']; if (isset($url['query'])) { $uri .= '?'.$url['query']; } $out = "GET {$uri} HTTP/1.1\r\n" . "Host: {$parts['host']}\r\n" . "Connection: Close\r\n\r\n"; fwrite($fp, $out); // did not read anything fclose($fp); }
Только вот на виртуальных хостингах fsockopen
часто отключают, потому при необходимости реализовать технологию callNoWait
в клиентских проектах (парсер, импорт и т.д.) использую curl.
сайт-пациент/checkup.php
В моём конкретном случае мониторится сайт на WP, для проверки работоспособности сайта написан такой checkup.php
:
echo bench(function(){ include 'wp-load.php'; });
Объявление функции bench
писал выше. В результате запроса checkup.php
в теле ответа возвращается время загрузки основного функционала движка WP.
Disclaimer
На данной странице код приведён as is, это не продакшен. Код намеренно дан кусками, а не в архиве с системными требованиями и инструкцией по установке. Цель — помочь юзеру в реализации, а не выполнить реализацию за него. Юзер несёт ответственность за любые проблемы в продакшене, возникшие в результате использования кода!
По-хорошему, дисклеймер надо писать вначале. Но я решил предупредить только внимательных читателей. Остальные пусть с наскоку делают копипасту а потом хейтят меня в комментах — хоть какой-то фидбэк. Важная информация для внимательных читателей: здесь не реализовано никакой защиты
- от попыток нарушить работу скрипта,
- от недоброжелателей, которые знают, что у вас на сайте есть скрипт
monitoring.php
и как этот скрипт работает, - от поисковых ботов, которые где-то получили ссылку на
monitoring.php
на вашем сайте и пытаются его проиндексировать...
Предлагаю для вызова скрипта как минимум использовать get-параметр salt
, в котором передавать секретную строку, и завершать работу скрипта, если salt
не верна. Также в вашем распоряжении ограничение по IP, секретные куки и что захотите.
2 ответа к “Проверка доступности сайта каждые N секунд”
Павел, очень интересный код получился у вас, может проще сыпать HEAD запросами, хоть раз в секунду (они не будут особо нагружать тестируемого)
И смотреть 200 ОК ?
Делать выводы по времени ответа не очень, как помне, почему: путь запроса от сервера к серверу каждый раз разный (разбирался тут с работой traceroute — там все не однозначно), как отделить проблемы каналов связи от проблем на сервере.
код для HEAD запроса:
// нужно дописать проверку на 200 ОК, и если нет, отправлять email админу.
$dst_url='http://pavluha.net/'; // create a new cURL resource $ch = curl_init (); // set URL and other appropriate options curl_setopt ($ch, CURLOPT_URL, $dst_url); // This changes the request method to HEAD curl_setopt ($ch, CURLOPT_NOBODY, true); curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT ,5); // connect timeout curl_setopt ($ch, CURLOPT_TIMEOUT, 10); // curl timeout // grab URL and pass it to the browser if (FALSE === curl_exec ($ch)){ echo ('open '.$dst_url.' failed'."\n"); }else{ // Fetch the HTTP-code (cred: @GZipp) $retcode = curl_getinfo ($ch, CURLINFO_HTTP_CODE); echo ('HTTP return code='.$retcode."\n"); } // close cURL resource, and free up system resources curl_close ($ch);
PS:
Павел, я вам писал на почту по поводу оптимизации сайта, а в ответ тишина... "Видно он не вернулся из боя..." ©
Напишите пожалуйста по оптимизации моего сайта, со статикой сломал голову, почему не кешируется. И со временем первого ответа сервера не могу понять почему такое большое (800-1300мс)
Михаил, спасибо за проявленный интерес и дельное дополнение. Действительно, для проверки доступность можно использовать HEAD или другой удобный для конкретной задачи метод запроса.
В статье я неточно выразился — под долгими ответами имел ввиду долгое время выполнения (пациент при выполнении бенча измеряет время выполнения и отправляет его в ответе — для получения этого ответа я и использую GET). Таким образом, я не только получаю факт доступности ресурса (200 ОК), но и время выполнения эталонной задачи (например, чтение/запись 1К файлов, выполнение 1К запросов к БД и прочее). При желании, время выполнения каждого теста можно передать отдельным параметром, а также сообщить проверяющему скрипту текущую загрузку CPU, RAM etc.