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

Материал из 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;
}

[править] Смотрите также

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