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

Бекенд высоконагруженного 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