
Если помните, мы начали работу над крупным и относительно высоконагруженным проектом. У нас всё ок, работа идёт по плану. Сегодня я расскажу вам о нашем «чёрном ящике» – о так называемом «бекенде» проекта (почему «так называемом» – читайте тут).
Как я уже говорил, бекенд-часть реализована на PHP, исполняется через PHP-FPM. Бекендом обрабатывается любой HTML-документ, JSON и XML запросы. Поговорим сейчас об HTML-документах. Каждый HTML-документ состоит из шаблона, в котором прописаны позиции и компоненты. Например, на главной нашего блога у нас будут позиция вывода блога главной категории, компонент меню, вывод блока «Рекомендуем почитать» и другие. Схема обработки запросов приблизительно следующая:
- Файл инициализации подгружает библиотеку роутера. Относительно запрошенного URL, роутер выдаёт нам список правил. Каждое правило содержит в себе:
- Название компонента (например, «content») – текстовая строка.
- Позицию в шаблоне (например, «main») – текстовая строка.
- Параметры для компонента (например, вывод=блог, категория №5) – ассоциативный массив ключ-значение.
- Далее мы пробуем найти готовый HTML-блок (плюс ещё js, css, дату модификации, meta-теги и прочую лабуду) во внешнем кеше (memcached) и, скорее всего, он там будет. В идеале, тут мы и перейдём к выводу позиций в шаблон. Ключ для кеша генерируется относительно названия компонента, его параметров и типа устройства.
- Если мы чего-то в кеше не нашли – нам необходимо сгенерировать финальные данные относительно компонента и его параметров и сохранить их в кеш.
- Готовые HTML-блоки вставляются на необходимые позиции в шаблон.
Вышеописанная схема реализуется следующим образом:
//Основная функция обработки запросов
public function run($URL,$host=''){
//Получаем список правил
$r=$this->parseurl($URL,$host);
if($r===false){
//Не получили список правил - 404 ошибка
bimport('http.useragent');
$device=BBrowserUseragent::detectDevice();
if($device==DEVICE_TYPE_MPHONE)
$suffix='.m';
elseif($device==DEVICE_TYPE_TABLET)
$suffix='.m'; else
$suffix='.d';
if(empty($this->templatename))
$this->templatename='default';
$fn=BTEMPLATESPATH.$this->templatename.
DIRECTORY_SEPARATOR.'#error_404'.$suffix.'.php';
if(!file_exists($fn))
$fn=BTEMPLATESPATH.$this->templatename.
DIRECTORY_SEPARATOR.'#error_404.d.php';
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
if(file_exists($fn)){
include($fn);
}else{
echo('<h1>404</h1>');
}
return false;
}
//Обрабатываем правила
$this->render_positions();
//Относительно типа выводим значения в позиции
switch($this->ctype){
case CTYPE_HTML:
$this->generatepage_html();
break;
case CTYPE_JSON:
$this->generatepage_json();
break;
case CTYPE_XML:
$this->generatepage_xml();
break;
default:
echo('Unknown content type!');
break;
}
}
//Обработка правил
public function render_positions(){
if(CACHE_TYPE){
//Пробуем получить значения из кеша
bimport('cache.general');
$bcache=BCache::getInstance();
$keys=array();
bimport('http.useragent');
$device=BBrowserUseragent::detectDevice();
foreach($this->rules as &$c){
$key='url:'.$c->com.':'.$this->langcode.$device;
foreach($c->segments as $k=>$v)
$key.=':'.$k.'='.$v;
$keys[]=$key;
$c->key=$key;
}
//Запрос в кеш
$list=$bcache->mget($keys);
foreach($this->rules as &$c)
if($list[$c->key]!=false)
$c=$list[$c->key];
}
foreach($this->rules as &$c)
//Если в кеше значения нет
if(!$c->rendered){
$fn=BCOMPONENTSPATH.$c->com.
DIRECTORY_SEPARATOR.'controller.php';
//Проверяем наличие файла контроллера
if(!file_exists($fn))
continue;
//Подключаем контроллер
require_once($fn);
$class='Controller_'.$c->com;
//Проверяем наличие необходимого класса
if(!class_exists($class))
continue;
//Создаём контроллер компонента
$controller=new $class();
$controller->componentname=$c->com;
$controller->templatename=$this->templatename;
//Передаём управление контроллеру
$c->output=$controller->run($c->segments);
//И получаем результат
$c->title=$controller->title;
$c->meta=$controller->meta;
$c->link=$controller->link;
$c->js=$controller->js;
$c->modified=$controller->modified;
$c->cachecontrol=$controller->cachecontrol;
$c->cachetime=$controller->cachetime;
$c->locationurl=$controller->locationurl;
$c->locationtime=$controller->locationtime;
$c->rendered=true;
if(CACHE_TYPE&&$c->cachecontrol)
$bcache->set($c->key,$c,$c->cachetime);
}
}
Компоненты работают пр принципу MVC, детальнее о компонентах и других нюансах я расскажу в следующих статьях.
Комментарии
С тестами справляемся и так. HHVM, говорят, даёт неплохой прирост в скорости выполнения скриптов, но это всё-равно не панацея.
Было бы неплохо с reactPHP подружиться
А при чем здесь тесты? Речь же идет про высоко нагруженные проекты... HHVM действительно хорошая штука, а вот reactPHP хоть и не много задержала PHP на плаву, но все же просто получила отсрочку на пару лет, рухнет как и его предок... по этому смысла в нем ни какого нету...
Речь о тестах производительности. В предел количества запросов в секунду вписываемся.
А на будущее (если проект будет развиваться) уж лучше, как по мне, масштабировать горизонтально, а не просто чуть-чуть приподнять потолок.
ReactPHP, хорош тем, что решает главную проблему производительности PHP - необходимость инициализировать PHP на каждый запрос.
Андрюшко, HHVM раз в 10 быстрее чем стандартный PHP-FPM, и ест гораздо меньше оперативы, прикрутить его вместо PHP займёт 1 час времени (установить и поменять в конфиге NGINX куда перенаправлять запросы), хотя бы тесты прогоните.
Adding comments is temporarily disabled for unregistered users.