Кэширование директорий в memcache php. Установка и настройка Memcached

Любой более или менее развивающийся web проект рано или поздно встречается с задачами, которые лучше всего решать с помощью кэширования. Например, сохранение в какое-либо хранилище агрегированных данных, подсчет и(или) получение которых занимает много времени. Существует несколько вариантов кэширования данных: сохранение данных на диск или в оперативную память. У каждого варианта есть свои плюсы и минусы. Сохранение на диск — медленное, запись происходит последовательно с использование блокировок, количество которых обычно ограниченно оперативной системой. Но данные сохраняются навсегда и будут доступны после перезагрузки сервера. Оперативная память же, напротив, быстрая, без блокировок, но не сохраняется в случае перезагрузки. Хранить в ней не продублированные данные опасно. Если с сохранением на диск все понятно и просто, то работа с памятью более сложна. Существуют приложения, которые берут на себя эту задачу. Одно из которых Memcached. В этой статье мы поговорим о нем.

Memcached — это сервер с открытым кодом, позволяющий кэшировать данные в оперативной памяти. Имеет высокие показатели скорости. Популярен. Обладает большим набором клиентских библиотек, написанных на всевозможных языках программирования.

В Php существует две версии библиотек: php-memcache и php-memcached. Первая старая и больше не развивается. Вторая новая и имеет больший набор функции. Различий в них больше и какую использовать решать вам. В данной статье будет использоваться php-memcached.

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 require_once __DIR__. "/func.php" ; if (! extension_loaded ("memcached" ) ) { die ("Memcached extension not installed" ) ; } $USE_CACHE_PROTECTION = true ; $AUTH_MAX_ATTEMPTS = 5 ; $AUTH_LOGIN = "demo" ; $AUTH_PASSWORD = "demo" ; $error = array () ; if (IS_POST() ) { $login = _POST("username" ) ; $password = _POST("password" ) ; if (! $login ) { $error [ "username" ] = "required" ; } if (! $password ) { $error [ "password" ] = "required" ; } // INIT MEMCACHE if ($USE_CACHE_PROTECTION ) { $cache = new Memcached() ; $cache -> addServer ("127.0.0.1" , 11211 ) ; $cacheKey = "prefix::login-protect-" . $login ; } //check cache protection if ($USE_CACHE_PROTECTION && ! $error ) { $attempts = $cache -> get ($cacheKey ) ; // CHECK FAILD ATTEMPTS if ($attempts >= $AUTH_MAX_ATTEMPTS ) { $error [ "global" ] = "max_attempts" ; } } //check auth if (! $error ) { //TODO: use db bro if ($login == $AUTH_LOGIN && $password == $AUTH_PASSWORD ) { // CLEAR DATA ON SUCCESS AUTH if ($USE_CACHE_PROTECTION ) { $cache -> delete ($cacheKey ) ; } stopAndRedirect("success.php" ) ; } if ($USE_CACHE_PROTECTION ) { //INCREMENT ATTEMPTS if (! $cache -> increment ($cacheKey ) ) { $cache -> set ($cacheKey , 1 , 60 ) ; } } $error [ "global" ] = "auth_faild" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

Демо использование memcached в php

Авторизация

Как видно из кода все предельно просто. Создается уникальный ключ, состоящий из префикса сайта, названия задачи и введенного логина. Далее читаем, наращиваем счетчик, удаляем. Стоит обратить внимание на участок кода, где скрипт наращивает счетчик неудачных попыток авторизации. Метод increment возвращает false в случае отсутствия записи в memcached. Поэтому, в случае неудачи мы устанавливаем запись со значением 1 ровно на 60 секунд.

Стоит заострить внимание на том, что мы используем метод increment , а не set(getted_value+1) . Дело в том метод set не дает возможности правильно увеличивать счетчик, так как в между промежутком чтения и записи, данные могут измениться. Например, в это же время другой пользователь будет тоже подбирать пароль. Метод increment же гарантированно увеличит счетчик правильно!

Модуль Memcache предоставляет удобные процедурный и объектно-ориентированный интерфейсы к механизму кэширования memcached. Memcache это очень эффективный демон кэширования, который был специально разработан для уменьшения нагрузки на базу данных для высоконагруженных, динамичных веб-приложений.

Memcache модуль также предоставляет обработчик сессии. Более подробную информацию о Memcached можно найти на сайте memcached.org

В принципе описания установки и настройки на русском языке уже готово и с ним можно ознакомится .

Имейте ввиду сохранять в кэше можно любые типы данных: и переменные, и объекты, и массивы, НО НЕ РЕСУРСЫ!

Если какой либо объект имеет в своём составе свойство - ресурс, например соединение с базой данных, или содержит другой объект имеющий подобные свойства, то эту ситуацию нужно обрабатывать отдельно, например переопределив метод __sleep() в котором этот ресурс нужно "убить" и метод __wakeup() в котором этот ресурс нужно возродить.

На самом деле все эти методы можно подразделить на 3 группы:

  1. Работа со значениями
  2. Работа с серверами и соединениями
  3. Получение информации

Работа со значениями

Методы этой группы позволяют делать следующее:

  • Устанавливать значения
  • Удалять эти значения
  • Заменять эти значения
  • Обращаться к этим значения по ключу
  • Управлять сроком жизни значений

Пара специфичных методов для инкремента и декремента целочисленных значений.

Memcache::set()
Memcache::add()

Позволяют установить значение, задать сжатие и время жизни для этого значения. Единственное различие в поведении этих методов это то, что метод Memcache::add - вернёт FALSE, если значение с таким ключём уже установлена.

bool Memcache::add
bool Memcache::set (string $key , mixed $var [, int $flag [, int $expire ]])

  • string $key - ключ значения, используя его мы оперируем значением.
  • mixed $var - значение.
  • константы
connect("localhost", 11211, 30); $cache->add("myfirst", 1000, MEMCACHE_COMPRESSED, 15); $cache->set("mysecond", 2000, MEMCACHE_COMPRESSED, 15);

Memcache::get()

Вернёт запрошенное значение или FALSE в случае неудачи, или, если значение с таким ключём ещё не установлено.

Можно передать массив ключей значений, тогда Memcache::get тоже вернёт массив, он будет содержать найденные пары ключ-значение.

Предупреждения!

Если в имени ключа присутствует обратный слэш, то результаты работы будут непредсказуемы!

Есть у Memcached и некоторые болячки: по некоторым данным, он (иногда) подвержен ошибкам переполнения буфера , который время от времени приводит к перезаписи [частичной] данных и, следовательно, делает невозможным десериализацию структур, не забудьте проверить, если в результате получения массива или объекта Memcache::get вернёт bool, dobule или long, значит что то пошло не так.

string Memcache::get (string $key [, int &$flags ])
array Memcache::get (array $keys [, array &$flags ])

  • string $key - ключ значения, или массив ключей, значения которых нужно получить.
  • int $flag - назначение этого параметра осталось для меня тайной. Я пробовал использовать некоторые целочисленные значения, но это не дало ни какого эффекта. Да и примеры в основном даны без его использования, из чего мною сделан вывод: можно прекрасно работать и без него:)
connect("localhost", 11211, 30); $cache->add("myfirst", 1000, MEMCACHE_COMPRESSED, 15); $cache->set("mysecond", 2000, MEMCACHE_COMPRESSED, 15); print_r($cache->get(array("myfirst","mysecond")));

Результат:

Array ( => 1000 => 2000)

Memcache::delete()

Удалить значение из кэша.

Замечание:

Этот метод работает не всегда (Зависит от версии программного обеспечения)! в конце страницы указаны случаи когда Memcache::delete работает, а когда нет.

Что ж, из стабильных вариантов остаётся только обнулять ненужные значения:

$cache->set("three", NULL);

Один только минус - ключ в этом случае ни куда не девается

string Memcache::delete (string $key [, int $timeout = 0 ])

  • string $key - ключ значение, которого нужно удалить.
  • int $timeout - Этот параметр так же устарел и не поддерживается, по умолчанию равен 0 секунд. Не используйте этот параметр.

Memcache::replace()

Перезаписать существующее значение.

Memcache::replace() должна использоваться, чтобы заменить существующее значение . В случае, если значение с таким ключом не существует, Memcache::replace() возвращает FALSE. В остальном Memcache::replace() ведет себя так же, как и Memcache::set(). Также можно использовать функцию memcache_replace()

string Memcache::replace (string $key , mixed $var [, int $flag [, int $expire ]])

  • string $key - ключ, значение которого нужно заменить.
  • mixed $var - новое значение.
  • int $flag - Флаг, указвающий, использовать ли сжатие (Здесь то и нужна Zlib) для сохраняемого значения, можно использовать константы
  • int $expire - Время жизни значения(кэша). Если равно нулю, то бессрочно. Вы также можете использовать метку времени или количество секунд, начиная с текущего времени, но тогда число секунд, не может превышать 2592000 (30 дней).
connect("localhost", 11211, 30); $cache->add("one", 111, 0, 15); $cache->add("two", 222, 0, 15); $cache->add("three", 333, 0, 15); $cache->replace("three", 777); print_r($cache->get(array("one","two","three"))); Array ( => 111 => 222 => 777)

Memcache::increment()

Увеличивает значение указанного ключа на указанное значение. Если значение указанного ключа не числовое и не может быть конвертировано в число, то оно изменит свое значение на значение, указанное как второй параметр. Memcache::increment() не создает элемент, если он еще не существует.

Не советуют использовать Memcache::increment() cо значениями, которые были сжаты (установлены с флагом MEMCACHE_COMPRESSED), потому, что потом будут "сюрпризы", когда вы попытаетесь получить эти значения при помощи Memcache::get()

Лично я проверил и ни каких глюков не заметил:)

int Memcache::increment

  • string $key - ключ, значение которого нужно инкрементировать.
  • int $value - значение инкремента.
connect("localhost", 11211, 30); $cache->set("someOne", 10, 0, 5); $cache->increment("someOne", 10, 0, 5); $cache->increment("someOne", 10, 0, 5); echo $cache->get("someOne");// выведет 30

Memcache::decrement()

Уменьшает значение указанного ключа на указанное значение. Аналогично Memcache::increment(), текущее значение элемента преобразуется в числовое и после этого уменьшается

int Memcache::decrement (string $key [, int $value = 1 ])

  • string $key - ключ, значение которого нужно декрементировать.
  • int $value - значение декремента.

Memcache::flush()

Memcache::flush() делает недействительными все существующие значения. Memcache::flush() на самом деле не освобождает ресурсы памяти, а только помечает все элементы как устаревшие, и занимаемая память будет доступна для использоавания новыми значениями. Также вы можете использовать функцию memcache_flush()

bool Memcache::flush (void)

Пример ниже не выведет ничего.

connect("localhost", 11211, 30); $cache->set("someOne", 111, 0, 5); $cache->set("someTwo", 222, 0, 5); $cache->set("someTree", 333, 0, 5); $cache->flush(); //Очистит кэш print_r($cache->get(array("someOne","someTwo","someTree")));

Работа с серверами и соединениями.

Memcache может работать, как с одним сервером, так и пулом (набором) серверов.

Если вы будете использовать только один сервер для обслуживания кэша, то для инициализации работы Вам будет достаточно создать объект кэша, и использовать метод Memcache::connect

Если вы собираетесь формировать пул серверов, то тут потребуется сначала создать объект кэша, а потом методом Memcache::addServer добавить в него требуемое количество серверов.

Memcache::connect()

Устанавливает соединение с сервером Memcached. Соединение, которое было открыто с помощью Memcache::connect() будет автоматически закрыто по завершению скрипта, но Вы можете закрыть его явно используя Memcache::close(). Также вы можете использовать функцию memcache_connect ().Возвращает TRUE в случае успешного завершения или FALSE в случае возникновения ошибки.

bool Memcache::connect

connect("localhost", 11211, 30);

Memcache::pconnect()

Этот метод похож на Memcache::connect() с той лишь разницей, что он устанавливает постоянное соединение. Это соединение не закрывается по окончанию выполнения скрипта и вызовом Memcache::close(). Также вы можете использовать функцию memcache_pconnect().Возвращает Memcache object в случае успешного завершения или FALSE в случае возникновения ошибки.

mixed Memcache::pconnect (string $host [, int $port [, int $timeout ]])

  • Этот параметр может также указать другие транспорты, как unix:///path/to/memcached.sock т.е. использовать UNIX сокеты, в этом случае порт должен быть установлен в 0.
  • Установите этот параметр в 0, если предполагается использование сокетов.
  • int $timeout - Лимит в секундах, для подключения к демону. Подумайте дважды, прежде чем изменять значение по умолчанию 1 секунда - вы можете потерять все преимущества кэширования, если ваше соединение будет происходить слишком долго.
pconnect("localhost", 11211, 30);

Memcache::close()

Закрывает соединение с сервером Memcached. Эта функция не закрывает постоянные соединения, которые закрываются только во время выключения/перезагрузки веб-сервера. Также можно использовать функцию memcache_close().

bool Memcache::close (void)

Memcache::addServer()

Добавляет сервер в пул соединений. Соединение, которое было открыто с помощью Memcache::addServer() будет автоматически закрыто по завершению скрипта, так же вы можете закрыть его вручную с Memcache::close(). Можно использовать функцию memcache_add_server()

При использовании Memcache::addServer (в отличие от Memcache::connect() и Memcache::PConnect()) сетевое соединение не установается, пока оно на самом деле не потребуется. Таким образом, нет накладных расходов при добавлении большого количества серверов в пул, даже если они и не потребуются вовсе.

Пул серверов обеспечивает более высокую отказоустойчивость. Любые ошибки сокетов, или ошибки Memcached на уровне сервера (кроме ошибок памяти) вызвают перераспределение ресурсов. Такие ошибки клиента, как добавление существующего ключа не вызовет сбоев.

Замечание:

Возвращает TRUE в случае успешного завершения или FALSE в случае возникновения ошибки.

bool Memcache::addServer (string $host [, int $port = 11211 [, bool $persistent [, int $weight [, int $timeout [, int $retry_interval [, bool $status [, callable $failure_callback [, int $timeoutms ]]]]]]]])

  • string $host - Хост Memcached. Этот параметр может также указать другие транспорты, как unix:///path/to/memcached.sock т.е. использовать UNIX сокеты, в этом случае порт должен быть установлен в 0.
  • int $port - порт, на котором Memcached слушает соединения. Установите этот параметр в 0, если предполагается использование сокетов.
  • bool $persistent - Устанавливает использование постоянного соединения. По умолчанию в значение TRUE.
  • int $weight - Чем больше, тем вероятнее, что данный сервер будет выбран для хранения значений. Т.е. это своеобразный "вес" сервера в общем пуле, косвенно это ещё и предполагаемая нагрузка на сервер.
  • int $timeout - Лимит в секундах, для подключения к демону. Подумайте дважды, прежде чем изменять значение по умолчанию 1 секунда - вы можете потерять все преимущества кэширования, если ваше соединение будет происходить слишком долго.
  • string $timeoutms - не описана.

Получение информации

Memcache::getExtendedStats()

Возвращает двумерный, ассоциативный массив статистики серверов. Массив ключей типа "хост:порт" сервера. Значения содержат отдельные параметры статистики. Отказавшие сервера будет иметь значение FALSE. Вы также можете использовать функцию memcache_get_extended_stats()

Замечание:

Эта функция была добавлена в Memcache в версии 2.0.0

array Memcache::getExtendedStats ([ string $type [, int $slabid [, int $limit = 100 ]]])

А теперь прикол:

Тип статистики "cachedump" был удален из Memcached по соображениям безопасности

Пример вывода информации (вызов без параметров) :

Array ( => Array ( => 5179 => 966862 => 1336505233 => 1.4.5 => 64 => 160.302880 => 483.975109 => 6 => 358080 => 13 => 15037476 => 3501979 => 46 => 11507898 => 3529578 => 0 => 0 => 0 => 76 => 0 => 0 => 0 => 0 => 0 => 0 => 0 => 1643906080 => 11298732744 => 67108864 => 1 => 0 => 4 => 0 => 3295454 => 7623 => 3501915 => 0 => 1825755) ...)

Memcache::getServerStatus()

Возвращает статус сервера "онлайн/оффлайн". Можно также использовать функцию memcache_get_server_status()

Замечание:

Эта функция была добавлена в Memcache в версии 2.0.0

int Memcache::getServerStatus (string $host [, int $port = 11211 ])

  • string $host - Хост Memcached.
  • int $port - порт, на котором Memcached слушает соединения.

Memcache::getStats()

Возвращает ассоциативный массив со статистикой сервера. Массив ключей соответствуют статистике параметры и значения для значения параметра. Также можно использовать функцию memcache_get_stats()

array Memcache::getStats s ([ string $type [, int $slabid [, int $limit = 100 ]]])

  • string $type - Тип статистики. Допустимые значения: {reset, malloc, maps, cachedump, slabs, items, sizes}. Согласно спецификации протокола Memcached эти дополнительные аргументы "могут быть изменены для удобства разработчиков".
  • int $slabid - Используется, когда первый параметр установлен в "cachedump" определяет сервер, который будет использоваться для отладки.
  • int $limit - Используется, когда первый параметр установлен в "cachedump" ограничивает количество записей в дамп.

Memcache::getVersion()

Возвращает строку - номер версии сервера. Также вы можете использовать функцию memcache_get_version() .

string Memcache::getVersion (void)

Memcache::setCompressThreshold()

Обеспечивает автоматическое сжатие больших значений. Вы также можете использовать функцию memcache_set_compress_threshold()

Замечание:

Эта функция была добавлена в Memcache в версии 2.0.0

bool Memcache::setCompressThreshold (int $threshold [, float $min_savings ])

  • int $threshold - Минимальная длина значения, которое требуется сжимать автоматически.
  • float $min_savings - Задает коэфициент сжатия. Указанное значение должно быть между 0 и 1. Значение по умолчанию составляет 0,2 дает минимум 20% экономии сжатия.

Memcache::setServerParams()

Изменяет параметры сервера прямо во время выполнения. Вы также можете использовать функцию memcache_set_server_params() .

Возвращает TRUE в случае успешного завершения или FALSE в случае возникновения ошибки.

Замечание:

Эта функция была добавлена в Memcache в версии 2.0.0

bool Memcache::setServerParams (string $host [, int $port = 11211 [, int $timeout [, int $retry_interval = false [, bool $status [, callable $failure_callback ]]]]])

  • string $host - Хост Memcached.
  • int $port - порт, на котором Memcached слушает соединения.
  • int $timeout - Лимит в секундах, для подключения к демону. Подумайте дважды, прежде чем изменять значение по умолчанию 1 секунда - вы можете потерять все преимущества кэширования, если ваше соединение будет происходить слишком долго.
  • int $retry_interval - Устанавливает, как часто отказавший сервер будет опрашиваться, значение по умолчанию составляет 15 секунд. Установка этого параметра в -1 отключает автоматический повтор запросов.
  • bool $status - Помечает сервер как ONLINE. Установка этого параметра в FALSE и retry_interval -1 позволяет отказавшему серверу хранится в пуле активных серверов, чтобы не влиять на ключевые алгоритмы распределения. Обращения к этому серверу будут распределятся между оставшимися доступными серверами. По умолчанию значение TRUE, то есть сервер должен рассматриваться как ONLINE.
  • callable $failure_callback - Позволяет пользователю указать функцию обратного вызова для запуска при обнаружении ошибки. Функция должна принимать два параметра, имя хоста и порт вышедшего из строя сервера.

Данная статья носит характер «Скорой помощи». Развернутое описание конкретных действий, которые позволять установить и настроить memcached на сервере.

Начнем с определения.

Memcached - программное обеспечение, реализующее сервис кэширования данных в оперативной памяти на основе хеш-таблицы.

Начальные данные:

  • VPS , работающий на операционной системе Debian ;
  • действующий сайт, к которому можно обратиться по FTP, SSH.

Начнем с последнего пункта. За счет запущенного интернет-ресурса (или его подобия) мы можем просто и быстро выяснить что у нас установлено (быть может, что среди прочего мы найдем и memcached). Создадим файл с названием info.php , в котором напишем:

Выполним код, обратившись в по адресу http://example.com/info.php:

Если среди всего прочего вы отыскали похожую запись — значит все в порядке и дело уже сделано. В противном случае — этот результат мы получим по итогам действий, описанных в данной публикации.

Установка сервера Memcached

Запустим установку командой

Apt-get install memcached php5-memcache

Ожидаем завершения установки. После проверим результаты командой netstat

В результате получаем:

Tcp 0 0 *:11211 *:* LISTEN 13053/memcached

Как мы видим, по умолчанию memcached по-умолчанию «слушает» порт 11211. Следовательно, через данный порт любой желающий сможет подключится и использовать наш memcached-сервер. Обезопасим себя, дадим такое право только своему серверу (localhost). Откроем файл /etc/memcached.conf через панель управления, либо через консоль:

Vi /etc/memcached.conf

Данный файл содержит подробные комментарии. Обязательно ознакомьтесь с ними.

Добавьте строку -l 127.0.0.1 .

Перезагружаем memcached-сервер командой:

/etc/init.d/memcached restart

И проверим еще раз:

Netstat -tap | grep memcached

В результате мы должны получить что-то вроде

Tcp 0 0 localhost.localdo:11211 *:* LISTEN 13092/memcached

Теперь memcached работает только на локальный сервер.

Установка memcache

Перечень команд (версия memcache может отличаться!):

Apt-get install php5-dev libmemcache-dev pecl download memcache tar xzvf memcache-2.2.6.tgz cd memcache-2.2.6/ phpize && ./configure --enable-memcache && make cp modules/memcache.so /usr/lib/php5/20060613/ echo "extension=memcache.so" >> /etc/php5/apache2/php.ini

Перезагружаем Apache:

Apachectl graceful

Проверим скрипт, размещенный нами ранее по адресу http://example.com/info.php . Теперь мы должны найти там пункт Memcache.

Проверим работу Memcached

Создадим файл result.php и запустим его.

connect("localhost", 11211) or exit("Невозможно подключиться к серверу Memcached"); $version = $memcache->getVersion(); echo "Server"s version: ".$version."
\n"; $tmp_object = new stdClass; $tmp_object->str_attr = "test"; $tmp_object->int_attr = 123; $memcache->set("key", $tmp_object, false, 10) or die ("Не получилось оставить запись в Memcached"); echo "Записываем данные в кеш Memcached (данные будут храниться 10 секунд)
\n"; $get_result = $memcache->get("key"); echo "Данные, записанные в Memcached:
\n"; var_dump($get_result); ?>

Результат работы скрипта:

Server"s version: 1.4.13 Записываем данные в кеш Memcached (данные будут храниться 10 секунд) Данные, записанные в Memcached: object(stdClass)#3 (2) { ["str_attr"]=> string(4) "test" ["int_attr"]=> int(123) }

Все так или почти так? Тогда все в порядке. Если нет — попробуйте повторить процедуры.

Статья для новичков . Memcached – это такая штука для кэширования данных в оперативной памяти сервера.

В чем суть. Сайт обычно берет данные из базы, а база - это большой файл на диске, а чтение с диска априори медленнее чем из памяти. Это начинает проявляться не сразу - как только посещаемость переваливает за несколько десятков тысяч человек, а таблицы в базе вырастают до сотен тысяч строк. Более того, сама база данных по определению не эффективна. Допустим, в базе храним 1 000 000 постов, но за последние несколько дней 95% всех просмотров составляют только 100 постов. Но каждый раз нам приходится лезть в огромный файл базы и искать в нем несколько часто запрашиваемых записей - а это увеличивает нагрузку на сервер и время открытия сайта. Если же мы положим эти записи в кэш, то мы ускорим сайт и не нужно покупать мощные сервера. Одним словом, кэш - это профит !

Кэширование бывает разным. Самое простое - кэширование на файлах . Минус в том, что данные по-прежнему хранятся на диске, а это может привести к печальным последствиям . Можно кэшировать промежуточные результаты в базе (например, результаты поиска в некоторых форумных движках хранятся в базе). Ну и самое эффективное это конечно хранение в оперативной памяти. Для этого существует куча сторонних программ: Memcached, eAccelerator, APC, XCache. Кстати, MySQL тоже умеет хранить данные в своем кэше (речь не об индексах в памяти).

Вообще, пишут, что eAccelerator и XCache эффективней чем Memcached, если вы используете один сервер, так как в случае Memcached необходимо открывать TCP-соединение. Но у Memcached есть главное преимущество - это возможность разнесения данных по нескольким серверам. Например, кэш ЖЖ не уместится в памяти ни одного самого мощного сервера. Собственно, Memcached и был придуман для ЖЖ, чтобы можно было хранить данные на нескольких серверах. Но нам, новичкам, пока рано об этом думать.

Особенности Memcached

  • Простая структура хранения данных (ключ-значение).
  • Максимальное время жизни кэша - 30 дней.
  • Максимальный объем одного элемента - 1 Mb
  • Можно хранить объекты, массивы как есть. При кэшировании в файлах или в базе подобные вещи нужно загонять в строку при помощи сериализации перед сохранением.
  • Нет авторизации (пароль-логин). Т.е. если на сервере стоит Memcached, то любой пользователь на этом же сервере может получить к нему доступ.
  • Скорость доступа к данным не зависит от кол-ва элементов в кэше. Да-да, именно так.

Установка

В сети есть куча инструкций по установке, хоть на Unix, хоть на Windows. Кроме самого Memcached нужно еще поставить либу для обращения к Memcached через PHP (по аналогии с базой MySQL - кроме самой базы нужно еще поставить расширение mysql или mysqli).

Но проще всего написать хостеру. На fastvps при заказе сервера Memcached ставят по умолчанию. Главное, указать сколько памяти нужно выделить под кэш. По умолчанию это 67 Mb. У меня 4 Gb оперативы, то можно смело выделить 1 Gb. Вообще, самый простой способ оценить сколько нужно памяти под кэш, это умножить объем базы на 2. Например, базы на всех наших сайтах весят 300 Мб, то под кэш выделяем 600 Мб, а лучше брать 1 Гб, с запасом.

Memcached можно увидеть в phpinfo

Проверка

Обычно Memcached стоит на localhost и доступен через порт 11211
Смотрим статистику

connect("localhost",11211); print_r($memcache->getStats()); ?>

Результат:
Array
=> 5915
=> 583
=> 1309538445
=> 1.2.2
=> 64
=> 0.000000
=> 0.004000
=> 0
=> 0
=> 0
=> 1
=> 2
=> 2
=> 0
=> 0
=> 0
=> 0
=> 0
=> 7
=> 0
=> 1073741824
=> 1
)

Через какое-то время статистика будет выглядеть примерно так

Array
=> 5915
=> 6202245
=> 1315740107
=> 1.2.2
=> 64
=> 3.464216
=> 10.868679
=> 298
=> 17728
=> 120366
=> 1
=> 28654
=> 4
=> 133296
=> 17728
=> 124758
=> 8538
=> 0
=> 11125692
=> 103815319
=> 1073741824
=> 1
)

Оновные параметры:
=> 298 - сколько текущих элементов в кэше.
=> 17728 - сколько всего было элементов в кэше (в том числе и удаленных)
=> 120366 - сколько байт сейчас лежит в кэше
=>1073741824 - сколько байт вообще доступно под кэш (тут 1 Gb)
=> 124758 - сколько раз мы взяли данные из кэша
=> 8538 - сколько раз мы пытались взять данные из кэша, но его там не было или время жизни кэша истекло.

Отношение get_misses/get_hits показывает эффективность использования кэша. Чем оно меньше, тем эффективней используется кэш. В данном случае у нас получается, что 93% данных берется из кэша. Если у вас get_misses/get_hits=1, то значит вы делаете что-то не так (скорее всего ставите слишком малое время жизни кэша).

визуальная статистика
Код выше выводит статистику в сухом виде типа print_r()
Есть красивый вывод статистики - phpMemcachedAdmin

Это обычный php-скрипт. Открываете его у себя на сайте и получаете красивое оформление.
Настраивать ничего не нужно. По умолчанию он коннектится к localhost:11211
Скачать можно на официальной странице .

Примеры использования Memcache

Допустим, у нас есть строка "test111", мы хотим ее закэшировать на 1 день. Придумаем ей какой-нибудь ключ "key1".

connect("localhost",11211); $memcache->set("key1", "test111", false, 86400); // кэшируем на 1 день. $get_result = $memcache->get("key1"); // получаем данные print_r($get_result); ?>

Усложним немного

get($key)) { $get_result = $memcache->get($key); print_r($get_result); } else { $result = "test222"; // $result – результат каких-то вычислений или выборка из БД $memcache->set($key, $result, false, 86400); echo "записали кэш на 1 сутки"; } ?>

Только со второго запуска этого скрипта мы увидим наши данные.

Еще забыл добавить про время жизни кэша. Memcached имеет ограничение на время жизни - 1 месяц. Так вот, если вы поставите 365 дней, то Memcached просто не сохранит их, при этом не выдаст ни какой ошибки. Поэтому, если ваши данные долго не меняются и вы хотите поставить максимальный срок жизни, то указывайте false
$memcache->set($key, $result, false, false);

Особенность именования ключей. Лучше в качестве ключа брать md5(ключ), потому что максимальная длина ключа 250 символов и нельзя использовать пробелы. А когда вы будет кэшировать SQL-запросы с условием, то ключ будет типа $key = "blog_id_1 WHERE activity=1 AND … AND … LIMIT 10"

Более того, в ключ нужно еще добавить какую-то константу, которая определяет, к какому сайт принадлежит кэш.
$key = md5(PROJECT."key2"); // где константа PROJECT="site.com"

Если этого не сделать, то второй сайт на том же сервере может перезаписать данные первого сайта с тем же ключом. Дело в том, что Memcached не имеет авторизации, как например база данных, поэтому приходится вот таким способом ограничивать доступ. Короче говоря, Memcached - это такая большая свалка пар ключ-значение. Поэтому все сайты хранят кэш в одном Memcached. При этом мы не можем, например, взять 10 последних записанных элементов (типа как в базе LIMIT 10). Структура Memcached необычайна проста, но за счет этого мы получаем высокую производительность.

Тегирование

Так как Memcached чрезвычайно прост (данные никак не связаны между собой - есть только связь ключ-значение), то возникают некоторые трудности на практике. Допустим, у нас есть блоги как на Хабре. Мы написали пост, сохранили его в кэш. Создали несколько пар ключ-значение: кэш под сам пост, кэш для блога, в котором отображается этот пост, кэш для прямого эфира, кэш для вывода постов пользователя в профиле этого пользователя и т.д.

$memcache->set("post_id_2211", "данные");
$memcache->set("post_blog_id_11", "данные");
$memcache->set("live_posts", "данные");
$memcache->set("post_user_id_331", "данные");

Но вдруг модератор удалил пост. Нам нужно очистить все кэши, которые связаны с этим постом.

$memcache->delete("post_id_2211");
$memcache->delete("post_blog_id_11");
$memcache->delete("live_posts");
$memcache->delete("post_user_id_331");

Короче, из-за простоты Memcached нам приходится ручками плодить лишний код. Более того, мы должны постоянно помнить какие кэши связаны с другим кэшем. Решение проблемы очень простое. Мы к каждому элементу кэша прикрепляем тег или несколько тегов.
Подробнее можно почитать .

Практика

На практике чистый Memcached никто не использует. Обычно используют какой-то класс-обертку, который поддерживает тегирование. Самым распространенным является решение ZendCache

Скачать одним архивом со всем примерами
Положим класс ZendCache в папку lib

Структура должна быть такой, если смотреть от корня
/lib/DklabCache/...
/class/Cache.class.php
/stat.php
/get_post.php
/update_post.php

Класс-обертка (или как еще называют - врапер (wrapper)) с использованием ZendCache

array(array("host" => MEMCACHED_HOST, "port" => MEMCACHED_PORT, "persistent" => false),), "compression" => false,); self::$instance = new Cache; self::$instance->memcache = new Dklab_Cache_Backend_TagEmuWrapper(new Zend_Cache_Backend_Memcached($aConfigMem)); } else { return NULL; } } return self::$instance; } public function get($key) { return $this->memcache->load($key); } public function set($key, $value, $tags=array(), $timeLife=false) { return $this->memcache->save($value, $key, $tags, $timeLife); } public function delete($key) { $this->memcache->remove($key); } public function clean($cMode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) { return $this->memcache->clean($cMode,$tags); } public function __construct() { } public function __clone() { } } ?>

Это у нас класс-синглтон, говоря простыми словами, при первом вызове создается экземпляр класса (читай, подключение к Memcached) и используется при следующем вызове. Таким образом, в пределах одного скрипта мы не плодим лишние подключения и экономим ресурсы.

Константа CACHE_USE прописывается отдельно в конфиге. С помощью нее можно включать/выключать кэширование.

Параметр "compression" => false означает, что мы не сжимаем данные в кэше. Сжатие нужно для экономии места в памяти, но сжатие требует некоторого времени. Поэтому, если для вас не критичен объем памяти, то сжатее отключаем.

Параметр "persistent" => false означает выключение постоянного соединения (по аналогии с mysql_pconnect())

Кстати говоря, тут видно как использовать несколько серверов. Если у нас 1 сервер

"servers" => array(array("host" => "localhost", "port" => 11211, "persistent" => false),)

Например у нас 3 сервера Memcached

"servers" => array(array("host" => "11.33.45.11", "port" => 11211, "persistent" => false), array("host" => "11.33.45.12", "port" => 11211, "persistent" => false), array("host" => "11.33.45.13", "port" => 11211, "persistent" => false),)

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

Теперь подключаем этот класс в скрипт, в котором мы хотим что-то кэшировать
Допустим скрипт вывода поста (в архиве это get_post.php)
Примеры конечно не самые лучшие, но лучше так, чем совсем ничего.
Работу с базой я специально отключил, что бы у вас было меньше проблем.

get($key_cache))){ echo "данные взяли из кэша"; return $data; } else { // находим пост в базе (выполняем select) //$data = $DB->selectRow("SELECT * FROM post WHERE id=?d", $postID); // для упрощения примера беем готовый массив (пост привязан к блогу с ID=3) $data = array("id"=>$postID, "blog_id"=>3, "title"=>"Новость 111", "text"=>"какой-то текст"); if (!empty($data)) { if (isset($Cache)) { $Cache->set($key_cache, $data, array("post_update", "post_update_".$postID, "post_blog_".$data["blog_id"]), 3600*24*10); echo "сохранили данные в кэш"; } return $data; } else return null; } } $postID = 25; $post = get_post($postID); print_r($post); ?> Хотим взять пост с id=25. При первом вызове вы должны увидеть надпись "сохранили данные в кэш". При повторных вызовах вы увидите надпись "данные взяли из кэша". Теперь попробуем обновить пост (запускаем скрипт update_post.php) query("UPDATE post SET blog_id =?d, title=?, text=? WHERE id=?d", $blogID, $title, $text, $postID); // чистим теги, связанные с эим постом $Cache = Cache::getInstance(); if (isset($Cache)) { $Cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array("post_update", "post_update_".$postID, "post_blog_".$blogID)); } return true; } $postID = 25; update_post($postID, 3, "test", "test test"); ?>

После чего запускаем скрипт get_post.php и видим, что данных в кэше не было и мы снова сохранили их туда: "сохранили данные в кэш".

На самом деле самое сложное в кэшировании, это простановка правильных тегов . Чтобы при обновлении поста обновилась данные, в которых лежит этот пост.

На примере выше это были теги

  • "post_update_".$postID - сам пост (обычно страница вывода поста)
  • "post_blog_".$blogID - страница блога, где выводится список постов этого блога
  • "post_update" - тег связанный с главной страницей или списком самых лучших постов

Dog-pile эффект

Переводится как «стая собак». Допустим, у вас есть какая-то сложная выборка из базы на 2 секунды. В кэше ее еще нет (или мы сбросили кэш). Приходит первый пользователь, идет запрос в базу, и только спустя 2 секунды эти данные появятся в кэше. Так вот, за эти 2 секунды на сайт могут зайти еще 10 человек, которые инициируют еще 10 таких же сложных запросов в базу (так как данных в кэше еще нет, а первый запрос всё еще выполняется). Отсюда и получается стая собак, которые нагружают сервак.

выглядит это примерно как-то так:)

Решение проблемы описано .
Код выше не предусматривает защиты от dog-pile эффекта. В моем случае нет такой высокой посещаемости и долгих запросов.

Сегодня мы будем разбираться с интересным механизмом кеширования данных: Memcache php . Замечательная сторона memcahe состоит в том, что мы можем кешировать все что угодно, от SQL запросов, до объектов и любых других структур данных.

Что такое Memcache

Memcache – это непросто технология или алгоритм кеширования, в первую очередь это серверная программа, запущенная на веб-сервере. Если вы пользуетесь услугами хостинга, то перед использованием мемкеша в приложении, необходимо убедиться, что memcahe доступен. Сделать это можно с помощью функции phpinfo() .

Концепция

Убедившись в том, что memcahe сервер доступен для использования, давайте познакомимся с концепцией сего механизма кеширования. Самое привлекательное в использовании memcahe php это то, что все закешированные данные хранятся в оперативной памяти сервера. Такой принцип хранения данных, не только экономит процессорное время на формировании их вновь и вновь, но и очень увеличивает скорость доступа к ним.

В упрощенном варианте концепцию работы memcahe можно изложить таким образом: часто используемые php объекты кешируются и сохраняются в ОЗУ сервера в виде набора пар "ключ — объект", при необходимости сохраненные раннее объекты берутся из памяти сервера минуя многочисленные подключения к БД проверки и циклы. В случае когда алгоритм memcahe php не обнаруживает запрашиваемого объекта в памяти, необходимый объект создается обычным образом и сохраняется в кеш.

Как работать с сервером Memcache в PHP

Для работы с серевром Memcache нам потребуется одна из PHP библиотек php-memcache либо php-memcached .

Выбор той или иной библиотеки зависит от индивидуальных потребностей разработчика, в большей своей части, они очень похожи друг на друга, с той разницей, что php-memcached предоставляет несколько большие возможности для работы такие как:

  • CAS токены для версионирования ключей
  • Обратные вызовы (callbacks)
  • Метод getDelayed() позволяющий уменьшить время ожидания, откладывая фактическое чтение ключей
  • Поддержка бинарного протокола
  • Возможность избежать сериализации используя igbinary

Оба модуля php-memcach и php-memcached , не являются стандартными для php, но доступны в репозитории PECL.

В случае работы не на собственном сервере, а на сервере хостинга, выбирать вам не придется, все будет уже настроено.

Пример работы

Сразу обращу ваше внимание на то, что данный пример будет работать на php-memcach и php-memcached одинаково.

Memcach php использует следующие методы для работы с данными:

  1. get — для получения объекта из кэша;
  2. set — запись объекта по ключу в кэш;
  3. add — запись объекта в кэш, если в нем еще нет такого ключа;
  4. replace — запись объекта в кэш, в случае, если ключ уже есть.

Приведу примитивный пример использования кеширования с помощью сервера memcache.

1
2
3
4
5
6
7

$memcache = new Memcache() ;
$memcache -> pconnect ("localhost" , 3211 ) ; //Cоединяемся. По умолчанию можно использовать любой порт
$key = "key_1" ;
$value = array (1 , 2 , 3 , 4 , 5 ) ;
$expire = 600 ;
$memcache -> set ($key , $value , 0 , $expire ) ; // Сохраняем значение на 10 минут
$memcache -> get ("key_1" ) ; // получаем записанный ранее массив

При использовании сервера мемкеша сразу для нескольких проектов, помните, что данные записываются в одну оперативную память. Это значит, что если вы с разных проектов запишите новые значения по одному и тому же ключу, то для обоих будет доступно последнее значение, попавшее в память.