Проверка доступности сайта каждые 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, секретные куки и что захотите.

Запись опубликована в рубрике Web-мастеринг с метками , . Короткая ссылка для добавления в закладки: Проверка доступности сайта каждые N секунд.

One Response

  1. Для проверки доступности сайта я использую сервис, кстати бесплатно. Все элементарно и просто работает, когда на сервере проблема и сайт не доступен, мне в течении 5 минут приходит сообщение на почту. Можно даже настроить уведомления по смс. Если интересно пишите мне на блоге

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Иногда ваш комментарий может не отобразиться сразу после публикации - будто пропал. Не волнуйтесь, он не пропадёт и появится потом, после моего одобрения.