30 November 2010

Повышение производительности за счет блочного кеширования

High performance
Тема блочного кеширования и ssi не раз проскакивала на Хабре. Ниже я представлю еще одну реализацию, использующего блочное кеширование, а также исходники фреймворка, использующего эти принципы, которые можно найти тут. А как это работает — прочитать ниже.
схема кеширования блоками

Чуть-чуть об MVC подходах


В основу построения фреймворка лег принцип «as simple as better». Если рассмотреть ход сборки выходного HTML по паттерну MVC, то существует два подхода: push & poll.

При первом подходе фронт-контроллер вызывает множество контроллеров, которые запускают модели и с помощью View формируют множество блоков, которые потом собираются в фронт View.

Второй подход — обрабатывается один View шаблон, в котором присутствуют функции обратного вывода (callback), которые вызывают соответствующие контроллеры, а они в свою очередь формируют HTML блока. Такой подход, например, использован в ZendFramework.

В данном случае использован первый подход, но с тем отличием, что сборкой занимается не РНР-скрипт, а непосредственно WEB-сервер (nginx) посредством ssi (Server side include). Используются SSI директивы: include и echo. Это позволяет:
  • упростить код контроллера, снизить нагрузку на РНР скрипты
  • выполнять несколько скриптов одновременно
  • уменьшить поток байт, передаваемый между WEB сервером и бэкэндом
  • кешировать блоки средствами сервера, не дергая скрипты бэкэнд(РНР)
Сам фреймворк (дал ему название quickly, так как он по производительности в 4-ре раза быстрее ZF) тесно интегрирован с nginx, и часть nginx конфига — является непосредственной частью проекта. (кому-то это не нравится… На вкус и цвет товарища нет) Для этого в nginx.conf должн быть включена директива include /path/to/project/conf/local.nginx.conf

Немного о том как это работает вообще


Классическая схема MVC в WEB — каждый контролер привязан к какой-то определенной части url. Часть url привязана к action и часть к параметрам.

Как упоминалось выше, часть функций контроллера берет на себя nginx. Разруливание по url осуществляется с помощью директив location. Можно все разрулить по контроллерам (страницам), действиям ( actions)и параметрам. Но это более гибче, чем в том же ZF. Обязательно в директиве location должен присутстввовать fastcgi параметр page, по значению которого выбирается соответствующий класс. Считаем, что вызвали соответствующий контроллер блока.
Пример части конфига: set $app_script run_app.php;
. . .
location ~ ^/catalog/(\w+)/? {
fastcgi_pass localhost:9000;
fastcgi_param page catalog;
fastcgi_param cat_name $1;
include fastcgi_params;
}

В данном примере показано, что будет передан fcgi-параметры page=catalog, cat_name равен последней части url. Будет вызван РНР скрипт: run_app.php, который из каталога page инстанцирует класс catalogPage (расположение page/catalogPage.php) и запустит метод run(). В переменной окружения cat_name для урла /catalog/bmv примет значение bmv.

Как это работает с SSI


Разруливание по location делятся на две части: внешние и внутренние. Внешние — это выбор соответствующего ssi шаблона с использованием реврайта. Внутренние — это локейшены частных контроллеров.

Пример SSI шаблона (index.tpl):
<script>
<!--# include virtual="$js" -->
</script>
<table >
<tr >
<td >left block
<!--#include virtual="$top" -->
</td>
content
<td valign="top">content block<br>
<!--#include virtual="$int" -->
</td>
</tr>
</table>

* This source code was highlighted with Source Code Highlighter.

Пример части конфига:
set $int "/ssi$request_uri";
set $top "/ssi/top10$request_uri";
. . .
location /catalog {
set $js "js/catalog.js";
rewrite ^(.*)$ /index.tpl;
}

location /ssi {
internal; # ставим директиву на продакшене, при отладке убираем
location /ssi/catalog/(\w+)/? {
fastcgi_pass localhost:9000;
fastcgi_param page catalog;
fastcgi_param cat_name $1;
fastcgi_param ssi 1;
include fastcgi_params;
}

location /ssi/top10/(\w+)/? {
fastcgi_pass localhost:9000;
fastcgi_param page top10;
fastcgi_param top_name $1;
fastcgi_param ssi 1;
include fastcgi_params;
}
}

* This source code was highlighted with Source Code Highlighter.


По первому location осуществляется реврайт на index.tpl с установкой переменной $js = js/catalog.js
В шаблоне index.tpl осуществляется подстановка нужного js скрипта, а также вызов нужных блоков путем использования ssi-директив #include. В нашем примере сработает внутренний location /ssi/catalog/ и вызовит PHP скрипт run_app, который синстанцирует класс catalogPage и запустит метод run(), а также аналогичным образом отработает блок top10.

Как это работает с memcached


смотрим рисунок. там все предельно ясно: вместо обращения по location /top10 мы обращаемся напрямую к мемкешу по location /mc. Если кеш инвалиден (пустой), то модуль ngx_memcache_module дает нам 404 ошибку. Обрабатываем 404 ошибку и делаем внутренний редирект на именованный location mcb. РНР-скрипт должен сформировать HTML и положить его в кеш. Особо беспокоиться об этом не надо, это происходит в базовом классе, если указать в нашем классе параметры:
protected $_Cached = true;
public $CachingKey = '/top_$top_name';

Пример конфига:
location ~ ^/catalog/(\w+) {
rewrite ^(.*)$ /index.tpl;
set $memkey "top_$1";
}

location /mc {
set $memcached_key $memkey;
default_type text/html;
memcached_pass localhost:11211;
error_page 404 @mcb; // если данные по ключу отсутствуют,
//то идет переход на именованный локейшен
}

location @mcb {
fastcgi_pass localhost:9000;
fastcgi_param page block;
fastcgi_param blocknum $blocknum;
include fastcgi_params;
}

* This source code was highlighted with Source Code Highlighter.


Особенности кеширования:
если вы используете расширение php_memcache, то ни каких особенностей нет.

Если используется библиотека libmemcached и php_memcached, то по умолчанию отрабатывает сжатие контента.
Возможны следующие варианты:
  • не использовать сжатие, установить параметр Memcached::OPT_COMPRESSION = false.
  • установить в location default_type gzip/deflate; Но при маленьких объемах, где-то до 64 байт сжатие не производится.
  • пропатчить ngx_memcache_module. В зависимости от значения принятого из memcache параметра выдавать заголовок text/html или gzip/deflate (патч 10 строк)

Благодарности


В первую очередь хочется высказать благодарность в адрес Игоря Сысоева sysoev.ru, без него не было бы и этого кода да и многих высокопроизводительных проектов рунета.
А также спасибо Константину Барышникову (fixxxer) за идеи использования location в качестве фронт-контроллера.
Хочется отблагодарить Алексея Рыбака ( fisher) за его blitz, который я активно использую в своих проектах, в частности и в этом фреймворке, уже более трех лет.
Ну и автору php-fpm Андрею Нигматулину (anight), который своим проектом внес не малый вклад в hiload рунета.

PS. Если у Вас что-то не запустилось, не беда. Получится в другой раз, главное не падать духом. А пока передохните и почитайте Хабр.
Tags:nginxкешированиеmemcachememcachedhighloadвысоконагруженные проектыphpphp-fpm
Hubs: High performance
+42
5.1k 186
Comments 60