Бекенд высоконагруженного Web-проекта

    Web-сервер

    Если помните, мы начали работу над крупным и относительно высоконагруженным проектом. У нас всё ок, работа идёт по плану. Сегодня я расскажу вам о нашем «чёрном ящике» – о так называемом «бекенде» проекта (почему «так называемом» – читайте тут).

    Как я уже говорил, бекенд-часть реализована на PHP, исполняется через PHP-FPM. Бекендом обрабатывается любой HTML-документ, JSON и XML запросы. Поговорим сейчас об HTML-документах. Каждый HTML-документ состоит из шаблона, в котором прописаны позиции и компоненты. Например, на главной нашего блога у нас будут позиция вывода блога главной категории, компонент меню, вывод блока «Рекомендуем почитать» и другие. Схема обработки запросов приблизительно следующая:

    1. Файл инициализации подгружает библиотеку роутера. Относительно запрошенного URL, роутер выдаёт нам список правил. Каждое правило содержит в себе:
      1. Название компонента (например, «content») – текстовая строка.
      2. Позицию в шаблоне (например, «main») – текстовая строка.
      3. Параметры для компонента (например, вывод=блог, категория №5) – ассоциативный массив ключ-значение.
      Правила зависимы от типа устройства (User-Agent-а клиента), например, в мобильной версии переключение языков может быть не в верхней панели, а в меню. Также роутер может сменить название шаблона и некоторые другие параметры.
    2. Далее мы пробуем найти готовый HTML-блок (плюс ещё js, css, дату модификации, meta-теги и прочую лабуду) во внешнем кеше (memcached) и, скорее всего, он там будет. В идеале, тут мы и перейдём к выводу позиций в шаблон. Ключ для кеша генерируется относительно названия компонента, его параметров и типа устройства.
    3. Если мы чего-то в кеше не нашли – нам необходимо сгенерировать финальные данные относительно компонента и его параметров и сохранить их в кеш.
    4. Готовые HTML-блоки вставляются на необходимые позиции в шаблон.

    Вышеописанная схема реализуется следующим образом:

    1. //Основная функция обработки запросов
    2. public function run($URL,$host=''){
    3. 	//Получаем список правил
    4. 	$r=$this->parseurl($URL,$host);
    5. 	if($r===false){
    6. 		//Не получили список правил - 404 ошибка
    7. 		bimport('http.useragent');
    8. 		$device=BBrowserUseragent::detectDevice();
    9. 		if($device==DEVICE_TYPE_MPHONE)
    10. 			$suffix='.m';
    11. 		elseif($device==DEVICE_TYPE_TABLET)
    12. 			$suffix='.m'; else
    13. 			$suffix='.d';
    14. 		if(empty($this->templatename))
    15. 			$this->templatename='default';
    16. 		$fn=BTEMPLATESPATH.$this->templatename.
    17. 			DIRECTORY_SEPARATOR.'#error_404'.$suffix.'.php';
    18. 		if(!file_exists($fn))
    19. 			$fn=BTEMPLATESPATH.$this->templatename.
    20. 				DIRECTORY_SEPARATOR.'#error_404.d.php';
    21. 		header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
    22. 		if(file_exists($fn)){
    23. 			include($fn);
    24. 			}else{
    25. 			echo('<h1>404</h1>');
    26. 			}
    27. 		return false;
    28. 		}
    29. 	//Обрабатываем правила
    30. 	$this->render_positions();
    31. 	//Относительно типа выводим значения в позиции
    32. 	switch($this->ctype){
    33. 		case CTYPE_HTML:
    34. 			$this->generatepage_html();
    35. 			break;
    36. 		case CTYPE_JSON:
    37. 			$this->generatepage_json();
    38. 			break;
    39. 		case CTYPE_XML:
    40. 			$this->generatepage_xml();
    41. 			break;
    42. 		default:
    43. 			echo('Unknown content type!');
    44. 			break;
    45. 		}
    46. 	}
    47. //Обработка правил
    48. public function render_positions(){
    49. 	if(CACHE_TYPE){
    50. 		//Пробуем получить значения из кеша
    51. 		bimport('cache.general');
    52. 		$bcache=BCache::getInstance();
    53. 		$keys=array();
    54. 		bimport('http.useragent');
    55. 		$device=BBrowserUseragent::detectDevice();
    56. 		foreach($this->rules as &$c){
    57. 			$key='url:'.$c->com.':'.$this->langcode.$device;
    58. 			foreach($c->segments as $k=>$v)
    59. 				$key.=':'.$k.'='.$v;
    60. 			$keys[]=$key;
    61. 			$c->key=$key;
    62. 			}
    63. 		//Запрос в кеш
    64. 		$list=$bcache->mget($keys);
    65. 		foreach($this->rules as &$c)
    66. 			if($list[$c->key]!=false)
    67. 				$c=$list[$c->key];
    68. 		}
    69. 	foreach($this->rules as &$c)
    70. 		//Если в кеше значения нет
    71. 		if(!$c->rendered){
    72. 			$fn=BCOMPONENTSPATH.$c->com.
    73. 				DIRECTORY_SEPARATOR.'controller.php';
    74. 			//Проверяем наличие файла контроллера
    75. 			if(!file_exists($fn))
    76. 				continue;
    77. 			//Подключаем контроллер
    78. 			require_once($fn);
    79. 			$class='Controller_'.$c->com;
    80. 			//Проверяем наличие необходимого класса
    81. 			if(!class_exists($class))
    82. 				continue;
    83. 			//Создаём контроллер компонента
    84. 			$controller=new $class();
    85. 			$controller->componentname=$c->com;
    86. 			$controller->templatename=$this->templatename;
    87. 			//Передаём управление контроллеру
    88. 			$c->output=$controller->run($c->segments);
    89. 			//И получаем результат
    90. 			$c->title=$controller->title;
    91. 			$c->meta=$controller->meta;
    92. 			$c->link=$controller->link;
    93. 			$c->js=$controller->js;
    94. 			$c->modified=$controller->modified;
    95. 			$c->cachecontrol=$controller->cachecontrol;
    96. 			$c->cachetime=$controller->cachetime;
    97. 			$c->locationurl=$controller->locationurl;
    98. 			$c->locationtime=$controller->locationtime;
    99. 			$c->rendered=true;
    100. 			if(CACHE_TYPE&&$c->cachecontrol)
    101. 				$bcache->set($c->key,$c,$c->cachetime);
    102. 			}
    103. 	}

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

    Комментарии

    13.05.2014 11:45:38
    Avatar of BinaryBinary
    Андрюха, прикручивай HHVM пока не поздно :)
    13.05.2014 12:11:31
    Avatar of КонсервКонсерв
    @Binary
    С тестами справляемся и так. HHVM, говорят, даёт неплохой прирост в скорости выполнения скриптов, но это всё-равно не панацея.

    Было бы неплохо с reactPHP подружиться :-)
    13.05.2014 01:58:08
    Avatar of shiziksamashiziksama
    читая статью, если бы не знал о чем речь то никак не догадался бы
    14.05.2014 02:37:16
    Avatar of breaking innovationbreaking innovation
    "С тестами справляемся и так"
    А при чем здесь тесты? Речь же идет про высоко нагруженные проекты... HHVM действительно хорошая штука, а вот reactPHP хоть и не много задержала PHP на плаву, но все же просто получила отсрочку на пару лет, рухнет как и его предок... по этому смысла в нем ни какого нету...
    15.05.2014 02:16:25
    Avatar of КонсервКонсерв
    @breaking
    Речь о тестах производительности. В предел количества запросов в секунду вписываемся.

    А на будущее (если проект будет развиваться) уж лучше, как по мне, масштабировать горизонтально, а не просто чуть-чуть приподнять потолок.

    ReactPHP, хорош тем, что решает главную проблему производительности PHP - необходимость инициализировать PHP на каждый запрос.
    15.05.2014 11:51:13
    Avatar of BinaryBinary
    @Консерв
    Андрюшко, HHVM раз в 10 быстрее чем стандартный PHP-FPM, и ест гораздо меньше оперативы, прикрутить его вместо PHP займёт 1 час времени (установить и поменять в конфиге NGINX куда перенаправлять запросы), хотя бы тесты прогоните.
    16.08.2014 10:35:36
    Avatar of 202202
    Спасибо за статью. Пожалуйста, если вам не трудно, загружайте побольше таких статей. Я интерисуюсь програмированием.
    Captcha Обновить
    Go Top