Pull to refresh

PHP Performance Series: Caching Techniques

Reading time 6 min
Views 10K
Original author: Mike Willbanks
Кеширование промежуточного кода (Opcode Caching)
Кэширование кода это один из самых легких и эффективных путей увеличения производительности в PHP. Использовании данного вида кэширования позволит избавиться от большого количества неэффективностей, возникающих при процессе запуска выполнения кода. Кэширование кода сохраняет промежуточный код в памяти для того чтобы не компилировать PHP-код каждый раз при запуске файла.

Существует множество библиотек для такого кэширования, например, APC, XCache, eAccelerator и Zend Platform.

Кэширование промежуточного кода файлов
Когда у нас есть большое количество кода и наш сервис отличается большой посещаемостью, скорее всего мы не будем ждать, когда каждый PHP-файл будет обработан при его вызове, логично в этом случае запустить некий скрипт перед выкладкой кода на сервера, который сразу создаст промежуточный код. Например, код такого скрипта может быть реализован так
/**
* Compile Files for APC
* The function runs through each directory and
* compiles each *.php file through apc_compile_file
* param string $dir start directory
* return void
*/
function compile_files($dir)
{
  $dirs = glob($dir. DIRECTORY_SEPARATOR. '*', GLOB_ONLYDIR);
  if (is_array($dirs) && count($dirs) > 0)
  {
    while(list(,$v) = each($dirs))
    {
      compile_files($v);
    }
  }
  $files = glob($dir. DIRECTORY_SEPARATOR. '*.php');
  if (is_array($files) && count($files) > 0)
  {
    while(list(,$v) = each($files))
    {
      apc_compile_file($v);
    }
  }
}
compile_files('/path/to/dir');


Кэширование переменных
Большинство библиотек кэширования позволяет кэшировать значения переменных. Очень полезно сохранять значения конфигурации или данные, которые сложно вычислить (получить) и которые не меняются (возможно не меняются в течение некоторого времени, тогда на базе такого кэширования можно реализовать кэширование с устареванием, примечание переводчика).
if (!$config = apc_fetch('config'))
{
  require('/path/to/includes/config.php');
  apc_store('config', $config);
}

Практический пример иллюстрируется на базе использования Zend Framework и простого запуска утилиты ab, в данном примере результат XML-конфигурации сохраняется в кэше. Ускорение времени разбора позволяет экстремально быстро получить доступ к параметрам конфигурации.
Код:
if (!$conf = apc_fetch('pbs_config'))
{
  $conf = new Zend_Config_Xml(PB_PATH_CONF. '/base.xml', 'production');
  apc_store('pbs_config', $conf);
}

Команда для теста ab -t30 -c5 www.example.com
Результат без кэширования
Concurrency Level: 5
Time taken for tests: 30.33144 seconds
Complete requests: 684
Failed requests: 0
Write errors: 0

Результат с кэшированием
Concurrency Level: 5
Time taken for tests: 30.12173 seconds
Complete requests: 709
Failed requests: 0
Write errors: 0

Как вы видите, мы получили около 3-4% в производительности, закэшировав значения конфигурационного файла. Существует много других мест, которые также можно оптимизировать, нахождение таких мест позволит увеличит количество обработанных запросов.

Файловое кэширование результатов
В некоторых случаях сервер обрабатывает запросы, результатом которых является одинаковый контент. Есть возможность закэшировать подобные вид контента (полностью или его часть)
В данном тексте иллюстрируется пример на основе пакета Pear::Cache_Lite.

Полное кэширование вывода
Полное кэширование довольно тяжело выполнить на большинстве сайтов с постоянно обновляющимися данными из большого количества источников. Все это правда, однако, нет необходимости обновлять данные каждую секунду. Даже 5-10 минутная задержка при экстремально высокой загрузке сайта позволит вам увеличить производительность.
Пример ниже, сохраняет слепок страницы для будущего использования. Такой подход может помочь большому количеству пользователей.
Я не рекомендую использовать данное решение, но если вам нужно что-то быстрое, вы можете его использовать, рано или поздно вы увидите недостатки этого метода.
The Bootstrap Cache Example:
require('/path/to/pear/Cache/Lite/Output.php');
$options = array(
  'cacheDir' => '/tmp/',
  'lifeTime' => 10
);
$cache = new Cache_Lite_Output($options);
if (!($cache->start($_SERVER['REQUEST_URI'])))
{
  require('/path/to/bootstrap.php');
  $cache->end();
}

Пример на основе .htaccess:
.htaccess
php_value auto_prepend_file /path/to/cache_start.php
php_value auto_append_file /path/to/cache_end.php
cache_start.php
require('Cache/Lite/Output.php');

$options = array(
  'cacheDir' => '/tmp/',
  'lifeTime' => 10
);
$cache = new Cache_Lite_Output($options);
if (($cache->start($_SERVER['REQUEST_URI'])))
  exit;

cache_end.php
$cache->end();

Cache Lite делает большинство тяжелой работы такой как блокирование файла, решение как сохранять контент для различных параметров (в данном примере используется REQUEST URI). Вам также может быть необходимы значения $_POST, $_COOKIE и $_SESSION.

Частичное кэширование
Частичное кэширование это типичный путь оптимизации. Скорее всего на вашем сайте есть части, которые очень редко изменяются или не должны изменяться в реальном времени. Это именно тот случай, когда необходимо применять частичное кэширование и оно позволит вам увидеть приращение в производительности.
Кэширование значения строк
require('Cache/Lite.php');
$options = array(
  'cacheDir' => '/tmp/',
  'lifeTime' => 3600 //1 hour
);
$cache = new Cache_Lite($options);
if (!($categories = $cache->get('categories')))
{
  $rs = mysql_query('SELECT category_id, category_name FROM category');
  $categories = '';
  $cache->save($categories, 'categories');
}
echo $categories;

Пока это чересчур упрощенный пример, он только показывает гибкость сохранения значения. Вы можете сохранять значения массивов для того чтобы обращаться к ним позже.
Кэширования значения массива
require('Cache/Lite.php');
$options = array(
  'cacheDir' => '/tmp/',
  'lifeTime' => 3600, //1 hour
  'automaticSerialization' => true
);
$cache = new Cache_Lite($options);
if (!($categories = $cache->get('categories')))
{
  $rs = mysql_query('SELECT category_id, category_name FROM category');
  $categories = array();
  while($row = mysql_fetch_assoc($rs))
  {
    $categories[] = $row;
  }
  $cache->store($categories, 'categories');
}
var_dump($categories);

Как вы видите, вы можете сохранять в кэше различные типы данных. Однако, использовать файловое кэширования я бы не рекомендовал для сохранения результатов запросов к базе данных.

Кэширование в оперативной памяти
Существует множетсво путей для того чтобы произвести кэширование в памяти: memcached, memory tables в базах данных, RAM disk и другие.
Memcached
С сайта memcache memcached это высокопроизводительная и распределенная кэширующая система, которая увеличивает скорость динамических веб-приложений путём снижения загрузки с базы данных.
О чем это говорит, о том, что можно сохранить данные на одном сервере, к которому будут обращаться другие сервера, это не зависит от вашего веб-сервера (как в случае кеширования промежуточного кода), так как memcached – это демон, который в большинстве случаев используется для кэширования результатов запросов к базам данных.
Пример работы с Memcache:
$post_id = (int) $_GET['post_id'];
$memcached = new Memcache;
$memcached->connect('hostname', 11211);
if (!$row = $memcached->get('post_id_'. $post_id))
{
  //yes this is safe, we type casted it already ;)
  $rs = mysql_query('SELECT * FROM post WHERE post_id = '. $post_id);
  if ($rs && mysql_num_rows($rs) > 0)
  {
    $row = mysql_fetch_assoc($rs);
    // cache compressed for 1 hour
    $memcached->set('post_id_'. $post_id, $row, MEMCACHE_COMPRESSED, time() + 3600);
  }
}
var_dump($row);

Это довольно простой пример работы с memcached. Мы сохранили простой элемент в памяти для будущего использования, до которого в будущем получим лёгкий доступ. Я рекомендую использовать данный метод для данных, к которым вы чаще всего будете обращаться.
Пример настройке параметров сессий для работы с Memcache
session.save_handler = memcache
session.save_path = «tcp://hostname:11211»

Как вы видите поддержка сессий довольно таки простая. Если у вас много серверов memcached переменная save_path должна содержать названия серверов через запятую with each server.
Memory Tables баз данных
Memory tables баз данных могут быть использованы для хранения данных сессии. Вы можете создать таблицу такого типа используя MySQL. Создайте ваш собственный обработчик сессий. Это один из способов увеличить производительность сессий.
RAM Disk
В то время как подход использования оперативной памяти как диска не является примером распределенности, данный подход легко может быть приспособлен для увеличения производительности сайта. Запомните информация находящаяся на таком диске исчезает после перезагрузки сервера.
Создание RAM-диска
mount --bind -ttmpfs /path/to/site/tmp /path/to/site/tmp

Я бы попытался избегать такого подхода, так как считаю, что риск в этом случае перевешивает выгоду, когда речь идет о большом количестве серверов. В таком случае лучшем выходом является использование memcached.

Надеюсь, что описанное выше было достаточно информативно. Здесь не описан весь потенциал кэширования, например использование кэширования в распределенных базах данных или использование Squid. В будущих статьях я опишу и это…
Tags:
Hubs:
+41
Comments 66
Comments Comments 66

Articles