Ограничение процессорной нагрузки (реализация)

Материал из 1GbWiki.

Перейти к: навигация, поиск

Содержание

Принцип работы

Каждую минуту запускается парсер запросов к серверу, который записывет время обработки запросов в базу данных MySQL, а в начало каждого php-скрипта добавляется префикс, получающий статистику нагрузки для своего сайта и принимающий решение о блокировании дальнейшей обработки скрипта.

Сама база данных работает в отдельном экземпляре MySQL с максимально упрощенной авторизацией, все таблицы держатся в памяти, поэтому дополнительные запросы статистики не создают заметной нагрузки на сервер.

Реализация

Статистика нагрузки рассчитывается сервером и записывается в базу, к которой можно подключиться из своих скриптов и понять помент, в который нужно заблокировать работу сайта для предотвращения выхода им за допустимую процессорную нагрузку.

Собственный алгоритм блокировки можно реализовать на любом из доступных языков программирования (ASP, PERL и т.д.), ниже приведено подробное описание формата данных и наша реализация блокировки на php.

Разработчикам, если Вы решили воспользоваться этим интерфейсом для получения информации о нагрузке настоятельно рекомендуем подписаться на изменения этой страницы (зарегистрироватся в Wiki и отметить эту страницу для наблюдения), т.к. при изменение структуры таблиц будет отражаться тут.

Формат конфигурационного файла

В конфигурационном для нашей реализаци используются пары ключ-значение по одному на строку в формате:

key=value

Сейчас подерживаются два параметра: FULL_BLOCK, IP_BLOCK в обоих указывается процент нагрузки от одного ядра процессора.

Пример содержания файла:

FULL_BLOCK=3.5
IP_BLOCK=1.5

Параметры для доступа к базе

Сервер: 127.0.0.1
Порт: 3399
База: ProcLimit;
Пользователь: user
Без пароля.

Структура таблиц

Список сайтов (таблица недоступна для свободного чтения)

Sites
ПолеТипПримерОписание
IDCHAR(32)6652D7688AF645EFF4FBD40B05A62C28MD5 имени сайта, буквы заглавные
NameVARCHAR(255)MYSITE.RUДоменное имя сайта заглавными буквами, без WWW.


Лог запросов

Logs
ПолеТипПримерОписание
HASHINT-1839737443Хеш строки из лог-файла, нужен для внутренних целей.
Site_IDCHAR(32)6652D7688AF645EFF4FBD40B05A62C28MD5 имени сайта, буквы заглавные
TimeDATETIME2008-04-03 21:10:15Время вызова файла скрипта
ProcessorTimeINT76Число миллисекунд процессорного времени, потраченых сервером на обработку запроса.
IPINT(4)-712745798IP-адрес, IP-адрес с которого был сделан запрос (алгоритм перевода IP-адреса в число можно посмотреть в примере реализации)


Статистика по сайту

Summary
ПолеТипПримерОписание
Site_IDCHAR(32)6652D7688AF645EFF4FBD40B05A62C28MD5 имени сайта, буквы заглавные
ProcessorTimeINT2037Число миллисекунд процессорного времени, потраченное сервером на обработку запросов к сайту за последний час.

Статистика по сайту и IP-адресу

IPSummary
ПолеТипПримерОписание
Site_IDCHAR(32)6652D7688AF645EFF4FBD40B05A62C28MD5 имени сайта, буквы заглавные
IPINT367IP-адрес, с которого поступали запросы (алгоритм перевода IP-адреса в число можно посмотреть в примере реализации)
ProcessorTimeINT2037Число миллисекунд процессорного времени, потраченное сервером на обработку запросов к сайту, поступивших с этого IP-адреса.

Пример реализации на PHP

<?php
	$path_1gb = $_SERVER["SCRIPT_FILENAME"];
	if ($path_1gb == )
		$path_1gb = $_SERVER["PATH_TRANSLATED"];
	$path_1gb = substr( $path_1gb, 0, - strlen($_SERVER['SCRIPT_NAME'])) . '/';
	
	$config_1gb = "$path_1gb/.cpu_limit.conf";

	if( !($cfg_1gb = @file($config_1gb ) ) )
		return;
       
	$logfile_1gb_path = $path_1gb . '/.proclimit_' . strtolower( @md5($path_1gb) );
	
	@mkdir($logfile_1gb_path);
	
	$logfile_1gb = "$logfile_1gb_path/.cpu_limit_".date('Y-m-d').".log";
	$logfile_1gb_debug = "$logfile_1gb_path/.cpu_limit_".date('Y-m-d')."_ok.log";

	$full_time_1gb = 60 * 60 * 1000;

	$ip_1gb = $_SERVER["REMOTE_ADDR"];
	@list($ip_1gb) = @split( ',', $ip_1gb );
	$ip_parts_1gb = @split( '\.', $ip_1gb );
	if (count ($ip_parts_1gb) != 4)
		return;
	$ip_1gb = @intval( $ip_parts_1gb[0] ) << 24;
	$ip_1gb |= @intval( $ip_parts_1gb[1] ) << 16;
 	$ip_1gb |= @intval( $ip_parts_1gb[2] ) << 8;
	$ip_1gb |= @intval( $ip_parts_1gb[3] );

	if( $ip_1gb > 2147483647 )
	{
		// Значит у нас 64-битная система. Нужно получить отрицательное число, как в 32-битной
		$ip_1gb |= 0xFFFFFFFF00000000;
	}

	$host_1gb = @addslashes(@strtoupper($_SERVER["HTTP_HOST"]));
	if( @substr($host_1gb, 0, 4) == "WWW." )
		$host_1gb = substr( $host_1gb, 4 );
	$site_id_1gb = @strtoupper( @md5( $host_1gb ) );
	
	$full_block_1gb = 0.50 * $full_time_1gb;
	$client_block_1gb = 0.50 * $full_time_1gb;
	foreach( $cfg_1gb as $lin1_1gb )
	{
		$lparts_1gb = @split("=", $lin1_1gb);
		if( count( $lparts_1gb) != 2 )
			continue;
		$name_1gb = strtoupper( trim( $lparts_1gb[0] ) );
		$val_1gb = trim( $lparts_1gb[1] );
		
		if( $name_1gb == "FULL_BLOCK" )
			$full_block_1gb = @floatval( $val_1gb ) / 100 * $full_time_1gb;
		if( $name_1gb == "IP_BLOCK" )
			$client_block_1gb = @floatval( $val_1gb ) / 100 * $full_time_1gb;
	}
	
	$con_1gb = @mysql_connect( "127.0.0.1:3399", "user" );
	if( !$con_1gb )
	{
		if( $logfile = @fopen( $logfile_1gb_debug, "at+" ) )
		{
			@fwrite( $logfile, date( "Y-m-d H:i:s" ) . ", accounting database is offline\n" );
			@fclose( $logfile );
		}
		return;	
	}
	@mysql_query( "use ProcLimit;", $con_1gb);

	
	$q_1gb = "SELECT ProcessorTime FROM Summary WHERE Summary.Site_ID = '$site_id_1gb'";
	$res_1gb = @mysql_query( $q_1gb, $con_1gb );
	if( $res_1gb )
		$res_1gb = @mysql_fetch_row( $res_1gb );

	if( $res_1gb )
	{
		$load_from_ip_1gb = @round ($res_1gb[0] * 100 / $full_time_1gb, 2);
		if( $res_1gb[0] >= $full_block_1gb )
		{
			if( $logfile = @fopen( $logfile_1gb, "at+" ) )
			{
				@fwrite( $logfile, date( "Y-m-d H:i:s" ) . ", blocked: $_SERVER[REMOTE_ADDR] ($load_from_ip_1gb %)\n" );
				@fclose( $logfile );
			}
			die( "Сервер перегружен, попробуйте зайти позже" );
		}
	}

				
	$q_1gb = "SELECT ProcessorTime FROM IPSummary WHERE IPSummary.Site_ID = '$site_id_1gb' AND IPSummary.IP = '$ip_1gb'";
	$res_1gb = @mysql_query( $q_1gb, $con_1gb );
	if( $res_1gb )
 		$res_1gb = @mysql_fetch_row( $res_1gb );
	
	if( $res_1gb )
	{
		$load_total_1gb = @round ($res_1gb[0] * 100 / $full_time_1gb, 2);
		if( $res_1gb[0] > $client_block_1gb )
		{
			if( $logfile = @fopen( $logfile_1gb, "at+" ) )
			{
				@fwrite( $logfile, date( "Y-m-d H:i:s" ) . ", blocked: $_SERVER[REMOTE_ADDR] (IP load = $load_total_1gb %)\n" );
				@fclose( $logfile );
			}
			die( "Сервер перегружен, попробуйте зайти позже" );
		}
	}
 
	
	if( $logfile = @fopen( $logfile_1gb_debug, "at+" ) )
	{
		@fwrite( $logfile, date( "Y-m-d H:i:s" ) . ", ok $_SERVER[REMOTE_ADDR] (IP load = $load_total_1gb %, total = $load_from_ip_1gb %)\n" );
		@fclose( $logfile );
	}
	
	
	unset( 
		$path_1gb, $config_gile, $ip_1gb, $host_1gb, $logfile_1gb, $logfile_1gb_debug, $logfile, $cfg_1gb, $full_1gb, $full_block_1gb, $client_block_1gb, 
		$lin1_1gb, $lparts_1gb, $name_1gb, $val_1gb, $q_1gb, $res_1gb, $ip_parts_1gb, $full_time_1gb, $load_from_ip_1gb, $load_total_1gb, $site_id_1gb, $logfile_1gb_path
		);
	
?>

Перевод IP-адреса в число на C#

private static int IPToInt(IPAddress ip)
{
  byte[] ip_bytes = ip;
  int iip = ip_bytes[0] << 24;
  iip |= ip_bytes[1] << 16;
  iip |= ip_bytes[2] << 8;
  iip |= ip_bytes[3];
  return iip;
}

Смотрите также

Личные инструменты