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

В основу построения фреймворка лег принцип «as simple as better». Если рассмотреть ход сборки выходного HTML по паттерну MVC, то существует два подхода: push & poll.
При первом подходе фронт-контроллер вызывает множество контроллеров, которые запускают модели и с помощью View формируют множество блоков, которые потом собираются в фронт View.
Второй подход — обрабатывается один View шаблон, в котором присутствуют функции обратного вывода (callback), которые вызывают соответствующие контроллеры, а они в свою очередь формируют HTML блока. Такой подход, например, использован в ZendFramework.
В данном случае использован первый подход, но с тем отличием, что сборкой занимается не РНР-скрипт, а непосредственно WEB-сервер (nginx) посредством ssi (Server side include). Используются SSI директивы: include и echo. Это позволяет:
Классическая схема MVC в WEB — каждый контролер привязан к какой-то определенной части url. Часть url привязана к action и часть к параметрам.
Как упоминалось выше, часть функций контроллера берет на себя nginx. Разруливание по url осуществляется с помощью директив location. Можно все разрулить по контроллерам (страницам), действиям ( actions)и параметрам. Но это более гибче, чем в том же ZF. Обязательно в директиве location должен присутстввовать fastcgi параметр page, по значению которого выбирается соответствующий класс. Считаем, что вызвали соответствующий контроллер блока.
Пример части конфига:
В данном примере показано, что будет передан fcgi-параметры page=catalog, cat_name равен последней части url. Будет вызван РНР скрипт: run_app.php, который из каталога page инстанцирует класс catalogPage (расположение page/catalogPage.php) и запустит метод run(). В переменной окружения cat_name для урла /catalog/bmv примет значение bmv.
Разруливание по location делятся на две части: внешние и внутренние. Внешние — это выбор соответствующего ssi шаблона с использованием реврайта. Внутренние — это локейшены частных контроллеров.
Пример SSI шаблона (index.tpl):
Пример части конфига:
По первому location осуществляется реврайт на index.tpl с установкой переменной $js = js/catalog.js
В шаблоне index.tpl осуществляется подстановка нужного js скрипта, а также вызов нужных блоков путем использования ssi-директив #include. В нашем примере сработает внутренний location /ssi/catalog/ и вызовит PHP скрипт run_app, который синстанцирует класс catalogPage и запустит метод run(), а также аналогичным образом отработает блок top10.
смотрим рисунок. там все предельно ясно: вместо обращения по location /top10 мы обращаемся напрямую к мемкешу по location /mc. Если кеш инвалиден (пустой), то модуль ngx_memcache_module дает нам 404 ошибку. Обрабатываем 404 ошибку и делаем внутренний редирект на именованный location mcb. РНР-скрипт должен сформировать HTML и положить его в кеш. Особо беспокоиться об этом не надо, это происходит в базовом классе, если указать в нашем классе параметры:
protected $_Cached = true;
public $CachingKey = '/top_$top_name';
Пример конфига:
Особенности кеширования:
если вы используете расширение php_memcache, то ни каких особенностей нет.
Если используется библиотека libmemcached и php_memcached, то по умолчанию отрабатывает сжатие контента.
Возможны следующие варианты:
В первую очередь хочется высказать благодарность в адрес Игоря Сысоева sysoev.ru, без него не было бы и этого кода да и многих высокопроизводительных проектов рунета.
А также спасибо Константину Барышникову (fixxxer) за идеи использования location в качестве фронт-контроллера.
Хочется отблагодарить Алексея Рыбака ( fisher) за его blitz, который я активно использую в своих проектах, в частности и в этом фреймворке, уже более трех лет.
Ну и автору php-fpm Андрею Нигматулину (anight), который своим проектом внес не малый вклад в hiload рунета.
PS. Если у Вас что-то не запустилось, не беда. Получится в другой раз, главное не падать духом. А пока передохните и почитайте Хабр.

Чуть-чуть об 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 сервером и бэкэндом
- кешировать блоки средствами сервера, не дергая скрипты бэкэнд(РНР)
Немного о том как это работает вообще
Классическая схема 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. Если у Вас что-то не запустилось, не беда. Получится в другой раз, главное не падать духом. А пока передохните и почитайте Хабр.