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

Опубликовано admin в Пнд, 11/04/2011 - 10:21
Тема блочного кеширования и ssi не раз проскакивала на Хабре. Ниже я представлю еще одну реализацию, использующего блочное кеширование, а также исходники фреймворка, использующего эти принципы, которые можно найти тут: http://github.com/akalend/quickly. А как это работает — прочитать ниже.
схема кеширования блоками

Чуть-чуть об 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>


Пример части конфига:
  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;
    }
  }



По первому 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;
  }



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

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

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


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

PS. Если у Вас что-то не запустилось, не беда. Получится в другой раз, главное не падать духом. А пока передохните и почитайте Хабр.



Источник: http://habrahabr.ru/blogs/hi/109050/