Кэширование директорий в 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 же гарантированно увеличит счетчик правильно!

Этим постом хочу открыть небольшую серию постов по материалам доклада на HighLoad++-2008 . Впоследствии весь текст будет опубликован в виде одной большой PDF-ки.

Введение

Для начала, о названии серии постов: посты будут и о кэшировании в Web’е (в высоконагруженных Web-проектах), и о применении memcached для кэширования, и о других применениях memcached в Web-проектах. То есть все три составляющие названия в различных комбинациях будут освещены в этой серии постов.

Кэширование сегодня является неотъемлемой частью любого Web-проекта, не обязательно высоконагруженного. Для каждого ресурса критичной для пользователя является такая характеристика, как время отклика сервера. Увеличение времени отклика сервера приводит к оттоку посетителей. Следовательно, необходимо минимизировать время отклика: для этого необходимо уменьшать время, требуемое на формирование ответа пользователю, а ответ пользователю требует получить данные из каких-то внешних ресурсов (backend). Этими ресурсами могут быть как базы данных, так и любые другие относительно медленные источники данных (например, удаленный файловый сервер, на котором мы уточняем количество свободного места). Для генерации одной страницы достаточно сложного ресурса нам может потребоваться совершить десятки подобных обращений. Многие из них будут быстрыми: 20 мс и меньше, однако всегда существует некоторое небольшое количество запросов, время вычисления которых может исчисляться секундами или минутами (даже в самой оптимизированной системе один могут быть, хотя их количество должно быть минимально). Если сложить всё то время, которое мы затратим на ожидание результатов запросов (если же мы будем выполнять запросы параллельно, то возьмем время вычисления самого долгого запроса), мы получим неудовлетворительное время отклика.

Решением этой задачи является кэширование: мы помещаем результат вычислений в некоторое хранилище (например, memcached), которое обладает отличными характеристиками по времени доступа к информации. Теперь вместо обращений к медленным, сложным и тяжелым backend’ам нам достаточно выполнить запрос к быстрому кэшу.

Memcached и кэширование

Принцип локальности

Кэш или подход кэширования мы встречаем повсюду в электронных устройствах, архитектуре программного обеспечения: кэш ЦП (первого и второго уровня), буферы жесткого диска, кэш операционной системы, буфер в автомагнитоле. Чем же определяется такой успех кэширования? Ответ лежит в принципе локальности: программе, устройству свойственно в определенный промежуток времени работать с некоторым подмножеством данных из общего набора. В случае оперативной памяти это означает, что если программа работает с данными, находящимися по адресу 100, то с большей степенью вероятности следующее обращение будет по адресу 101, 102 и т.п., а не по адресу 10000, например. То же самое с жестким диском: его буфер наполняется данными из областей, соседних по отношению к последним прочитанным секторам, если бы наши программы работали в один момент времени не с некоторым относительно небольшим набором файлов, а со всем содержимым жесткого диска, буферы были бы бессмысленны. Буфер автомагнитолы совершает упреждающее чтение с диска следующих минут музыки, потому что мы, скорее всего, будем слушать музыкальный файл последовательно, чем перескакивать по набору музыки и т.п.

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

Кэш всегда лучше, чем исходный источник данных: кэш ЦП на порядки быстрее оперативной памяти, однако мы не можем сделать оперативную память такой же быстрой, как кэш – это экономически неэффективно и технически сложно. Буфер жесткого диска удовлетворяет запросы за данными на порядки быстрее самого жесткого диска, однако буфер не обладает свойством запоминать данные при отключении питания – в этом смысле он хуже самого устройства. Аналогичная ситуация и с кэшированием в Web’е: кэш быстрее и эффективнее, чем backend, однако он обычно в случае перезапуска или падения сервера не может сохранить данные, а также не обладает логикой по вычислению каких-либо результатов: он умеет возвращать лишь то, что мы ранее в него положили.

Memcached

Memcached представляет собой огромную хэш-таблицу в оперативной памяти, доступную по сетевому протоколу. Он обеспечивает сервис по хранению значений, ассоциированных с ключами. Доступ к хэшу мы получаем через простой сетевой протокол, клиентом может выступать программа, написанная на произвольном языке программирования (существуют клиенты для C/C++, PHP, Perl, Java и т.п.).

Самые простые операции – получить значение указанного ключа (get), установить значение ключа (set) и удалить ключ (del). Для реализации цепочки атомарных операций (при условии конкурентного доступа к memcached со стороны параллельных процессов) используются дополнительные операции: инкремент/декремент значения ключа (incr/decr), дописать данные к значению ключа в начало или в конец (append/prepend), атомарная связка получения/установки значения (gets/cas) и другие.

Memcached был реализован Брэдом Фитцпатриком (Brad Fitzpatrick) в рамках работы над проектом ЖЖ (LiveJournal). Он использовался для разгрузки базы данных от запросов при отдаче контента страниц. Сегодня memcached нашел своё применение в ядре многих крупных проектов, например, Wikipedia, YouTube, Facebook и другие.

В общем случае схема кэширования выглядит следующим образом: frontend’у (той части проекта, которая формирует ответ пользователю) требуется получить данные какой-то выборки. Frontend обращается к быстрому как гепард серверу memcached за кэшом выборки (get-запрос). Если соответствующий ключ будет обнаружен, работа на этом заканчивается. В противном случае следует обращение к тяжелому, неповоротливому, но мощному (как слон) backend’у, в роли которого чаще всего выступает база данных. Полученный результат сразу же записывается в memcached в качестве кэша (set-запрос). При этом обычно для ключа задается максимальное время жизни (срок годности), который соответствует моменту сброса кэша.

Такая стандартная схема кэширования реализуется всегда. Вместо memcached в некоторых проектах могут использоваться локальные файлы, иные способы хранения (другая БД, кэш PHP-акселератора и т.п.) Однако, как будет показано далее, в высоконагруженном проекте данная схема может работать не самым эффективным образом. Тем не менее, в нашем дальнейшем рассказе мы будем опираться именно на эту схему.

Архитектура memcached

Каким же образом устроен memcached? Как ему удаётся работать настолько быстро, что даже десятки запросов к memcached, необходимых для обработки одной страницы сайта, не приводят к существенной задержке. При этом memcached крайне нетребователен к вычислительным ресурсам: на нагруженной инсталляции процессорное время, использованное им, редко превышает 10%.

Во-первых, memcached спроектирован так, чтобы все его операции имели алгоритмическую сложность O(1), т.е. время выполнения любой операции не зависит от количества ключей, которые хранит memcached. Это означает, что некоторые операции (или возможности) будут отсутствовать в нём, если их реализация требует всего лишь линейного (O(n)) времени. Так, в memcached отсутствуют возможность объединения ключей «в папки», т.е. какой-либо группировки ключей, также мы не найдем групповых операций над ключами или их значениями.

Основными оптимизированными операциями является выделение/освобождение блоков памяти под хранение ключей, определение политики самых неиспользуемых ключей (LRU) для очистки кэша при нехватке памяти. Поиск ключей происходит через хэширование, поэтому имеет сложность O(1).

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

По сути, можно сказать, что время отклика сервера memcached определяется только сетевыми издержками и практически равно времени передачи пакета от frontend’а до сервера memcached (RTT). Такие характеристики позволяют использовать memcached в высоконагруженных web-проектов для решения различных задач, в том числе и для кэширования данных.

Потеря ключей

Memcached не является надежным хранилищем – возможна ситуация, когда ключ будет удален из кэша раньше окончания его срока жизни. Архитектура проекта должна быть готова к такой ситуации и должна гибко реагировать на потерю ключей. Можно выделить три основных причины потери ключей:
  1. Ключ был удален раньше окончания его срока годности в силу нехватки памяти под хранение значений других ключей. Memcached использует политику LRU, поэтому такая потеря означает, что данный ключ редко использовался и память кэша освобождается для хранения более популярных ключей.
  2. Ключ был удален, так как истекло его время жизни. Такая ситуация строго говоря не является потерей, так как мы сами ограничили время жизни ключа, но для клиентского по отношению к memcached кода такая потеря неотличима от других случаев – при обращении к memcached мы получаем ответ «такого ключа нет».
  3. Самой неприятной ситуацией является крах процесса memcached или сервера, на котором он расположен. В этой ситуации мы теряем все ключи, которые хранились в кэше. Несколько сгладить последствия позволяет кластерная организация: множество серверов memcached, по которым «размазаны» ключи проекта: так последствия краха одного кэша будут менее заметны.

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

«Можно потерять» . К этой категории относятся кэши выборок из базы данных. Потеря таких ключей не так страшна, потому что мы можем легко восстановить их значения, обратившись заново к backend’у. Однако частые потери кэшей приводят к излишним обращениям к БД.

«Не хотелось бы потерять» . Здесь можно упомянуть счетчики посетителей сайта, просмотров ресурсов и т.п. Хоть и восстановить эти значения иногда напрямую невозможно, но значения этих ключей имеют ограниченный по времени смысл: через несколько минут их значение уже неактуально, и будет рассчитано новое значение.

«Совсем не должны терять» . Memcached удобен для хранения сессий пользователей – все сессии равнодоступны со всех серверов, входящих в кластер frontend’ов. Так вот содержимое сессий не хотелось бы терять никогда – иначе пользователей на сайте будет «разлогинивать». Как попытаться избежать? Можно дублировать ключи сессий на нескольких серверах memcached из кластера, так вероятность потери снижается.

Статья для новичков . 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 эффекта. В моем случае нет такой высокой посещаемости и долгих запросов.

В интернете достаточно много информации на данную тему, но, несмотря на это, многие обходят её стороной. Цель данного поста, разъяснить на пальцах основы взаимодействия с Memcached.

Что такое Memcache и какое отношение он имеет к PHP?

Memcache разработан для кэширования данных, генерация которых требует большого количества ресурсов. Такого рода данные могут содержать что угодно, начиная с результатов запроса к базе данных и заканчивая тяжеловесным куском шаблона. Memcached не входит в базовый набор модулей, поставляемых с PHP, однако он доступен в репозитории pecl.

Установка и настройка

В качестве рассматриваемого дистрибутива я решил использовать Debian, потому как он наиболее часто используется при создании web-серверов. Модуль Memcached для PHP доступен в репозитории уже скомпилированным (php5-memcached), но я опишу процесс установки из исходного кода, так как не все репозитории настолько богаты, как дебиановский.

Устанавливаем сервер Memcached

#apt-get install memcached
Для начала, Вам хватит следующего конфига:
#/etc/memcached.conf
#Memcached будет работать, как демон
-d
#Лог будет складывать туда
logfile / var/ log/
#Отведём 256 мегабайт ОЗУ под хранилище
-m 256
#Слушать будет этот порт
-p 11211
#В последствии желательно поменять
-u nobody
#Слушаем localhost
-l 127.0.0.1

#/etc/init.d/memcached restart
Проверяем
# netstat -tap | grep memcached
tcp 0 0 localhost:11211 * :* LISTEN 13036 /

Компилируем и устанавливаем модуль для PHP

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/ / usr/ lib/ php5/ 20060613 /

echo "extension=memcache.so" >> / etc/ php5/ apache2/ php.ini
/ etc/ init.d/ apache2 restart


Вот и всё! Совсем не сложно.

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

1. Базовые операции

  1. //Создаём новый объект. Также можно писать и в процедурном стиле
  2. = new ;
  3. -> connect ("127.0.0.1" , 11211 ) or die («Could not connect» ) ;
  4. //Попытаемся получить объект с ключом our_var
  5. $var_key = @ -> get ("our_var" ) ;
  6. if (! empty ($var_key ) )
  7. //Если объект закэширован, выводим его значение
  8. echo $var_key ;
  9. else
  10. //Если в кэше нет объекта с ключом our_var, создадим его
  11. //Объект our_var будет храниться 5 секунд и не будет сжат
  12. -> set ("our_var" , date ("G:i:s" ) , false , 5 ) ;
  13. //Выведем закэшированные данные
  14. echo -> get ("our_var" ) ;
  15. -> close () ;

В результате выполнения этого кода каждый раз будет выводиться время с точностью до секунд. Однако обновляться оно будет раз в 5 секунд, пока не очистится кэш. В данном примере проиллюстрированы самые простые операции, но в производительности мы скорее потеряем, чем выиграем. Ведь нам каждый раз придётся подключаться к серверу…

2. Повышаем производительность

2.1 С кэшированием
  1. < ? php
  2. function LoadCPU()
  3. //Функция, которая должна зугрузить процессор
  4. $image = imagecreate(800 , 600 ) ;
  5. //Белый фоновый цвет
  6. $color = imagecolorallocate($image, 255 , 255 , 255 ) ;
  7. //Чёрный
  8. $color2 = imagecolorallocate($image, 0 , 0 , 0 ) ;
  9. for ($i = 0 ; $i < 10000 ; $i++ ) {
  10. imagesetpixel($image, rand (0 , 800 ) , rand (0 ,600 ) , $color2) ;
  11. //Выбрасываем указатель
  12. return $image;
  13. //Создаём новый объект Memcache
  14. = new ;
  15. //Соединяемся с нашим сервером
  16. - > connect("127.0.0.1" , 11211 ) or die("Could not connect" ) ;
  17. //Попытаемся получить объект с ключом image
  18. $image_bin = - > get("image" ) ;
  19. if (empty($image_bin) ) {
  20. //Если в кэше нет картинки, сгенерируем её и закэшируем
  21. imagepng(LoadCPU() ,getcwd() ."/tmp.png" ,9 ) ;
  22. $image_bin = file_get_contents(getcwd() ."/tmp.png" ) ;
  23. unlink(getcwd() ."/tmp.png" ) ;
  24. - > set("image" , $image_bin, false , 30 ) ;
  25. //Выведем картинку из кэша
  26. header("Content-type: image/png" ) ;
  27. echo $image_bin;
  28. //Закрываем соединение с сервером Memcached
  29. - > close() ;
  30. ? >

В данном примере приведена функция, которая создаёт изображение размером 800x600 и расставляет на нём 10 000 точек. Один раз, сгенерировав такое изображение, в дальнейшем мы лишь выводим его на экран, не генерируя заново.
2.2 Без кэширования
  1. function LoadCPU()
  2. //Функция, которая должна загрузить процессор
  3. //Создадим изображение 800x600
  4. $image = imagecreate (800 , 600 ) ;
  5. //Белый фоновый цвет
  6. $color = imagecolorallocate ($image , 255 , 255 , 255 ) ;
  7. //Чёрный
  8. $color2 = imagecolorallocate ($image , 0 , 0 , 0 ) ;
  9. for ($i = 0 ; $i < 10000 ; $i ++ ) {
  10. //Расставим 10 000 точек в случайном порядке
  11. imagesetpixel ($image , rand (0 , 800 ) , rand (0 , 600 ) , $color2 ) ;
  12. //Выбрасываем указатель
  13. return $image ;
  14. //Выводим изображение, не кэшируя
  15. header ("Content-type: image/png" ) ;
  16. imagepng (LoadCPU() , "" , 9 ) ;

Тут всё гораздо проще и привычней: генерируем изображение каждый раз заново.
Результаты
Я протестировал оба скрипта на производительность. Одна и та же машина в первом случае выдала 460 ответов в секунду, а во втором лишь 10. Чего и следовало ожидать.

Ещё несколько полезных функций

addServer - в случае, если у вас в распоряжении несколько кэширующих серверов, вы можете создать некий кластер, добавляя сервера в пул. Следует обратить внимание на параметр weight . Он указывает на то, сколько памяти вам будет доступно на конкретном сервере.
delete - из названия понятно, что данный метод удаляет из кэша объект с заданным ключом.
replace - заменяет значение объекта с заданным ключом. Используйте в случае, если Вам понадобится изменить содержимое объекта, раньше чем истечёт время его жизни.

Итог

С моей точки зрения, применять кэширование стоит только на высоконагруженных ресурсах. Ведь каждый раз, подключаясь к серверу Memcached, вы тратите драгоценное время, что скорее всего не будет оправданным. Что касается больших проектов, лучше сразу написать больше строк кода, чем потом делать это в попыхах, с мыслью о том, что ваш сервис лежит. Также не стоит забывать о расходовании памяти! Учтите, что положив 300 мегабайт в кэш, вы отняли у себя 300 мегабайт ОЗУ...
В завершение хочу сказать, что данная статья не раскрывает все прелести технологии, однако я надеюсь, что она стимулирует Вас к самосовершенствованию. Спасибо за прочтение, многоуважаемый %username%!

UPD: Ещё один интересный момент. Memcached, есть PHP API к libmemcached. А Memcache, библиотека для php, не использующая libmemcached.

Ранее уже была сделана публикация с . Давайте вернемся к данной теме и рассмотрим практику работы с memcached на примерах.

К сожалению, у меня по прежнему не доходят руки активно заниматься блогом, но наконец-то появился появился первый человек, откликнувшийся на . Его имя Владислав Клименко и именно он является автором данного поста, а я лишь выступаю в роли редактора. Может быть данный пример подтолкнет и других читателей поучаствовать в возвращении Insight IT к жизни.

С уважением,
Иван Блинков

Итак, пара слов о предмете разговора. memcached - это распределенная система кэширования объектов в оперативной памяти. Разрабатывается фирмой (кстати, они являются авторами не только memcached, но и других интересных проектов). Но о них, возможно, в следующий раз. Обычно memcached используется приложениями для временного хранения данных, которые надо часто читать. Приложения не взаимодействуют (обычно) напрямую с сервером memcached, а работают при помощи клиентских библиотек. На настоящее время созданы библиотеки для многих языков программирования (а для некоторых еще и по нескольку альтернативных) - полный список клиентских библиотек доступен на . В целом, данная схема похожа на работу с БД, знакомую многим разработчикам.

Будем рассматривать установку и использование memcached для Linux. Так же при рассмотрении примеров на PHP и обзоре кэширования сессий потребуются PHP и Apache. Возможно, их придется установить, но мы не будем заострять внимание на вопросах установки.

Сервер memcached

Давайте приступим к установке memcached. Практически во всех дистрибутивах Linux memcached можно установить из репозитариев. Если есть желание собрать самую свежую версию, то можно заглянуть на (на момент написания этих строк последняя версия - ). Также, возможно, понадобится установить libevent. Последняя стабильная версия -

Собираем, устанавливаем и запускаем memcached в режиме вывода сообщений. Интересно же посмотреть, что с ним происходит:

Процесс запускается и ждет подключений (по умолчанию на порту 11211). Серверная часть готова обрабатывать подключения клиентов и кэшировать полученные данные.

Но для разработчика приложений это только полпути. Необходимо поддержать работу с memcached в своем приложении. Для этого, рассмотрим некоторые существующие клиентские библиотеки memcached.

Клиенты memcached

Из всего многообразия клиентских библиотек рассмотрим две:

  • libmemcached (для Си);
  • PECL extension для PHP (построенный на базе предыдущей библиотеки).

Си

Библиотека libmemcached на данный момент активно развивается и представляется наиболее подходящим выбором при работе с Си и PHP. Также, в комплекте с самой клиентской библиотекой поставляются дополнительные утилиты для работы с memcached, позволяющие просматривать, устанавливать, удалять значения в кэше memcached. Кстати, удивляет, что набор утилит идет не с серверной частью, а с клиентской библиотекой.

Итак, приступим к установке libmemcached. На момент написания этих строк текущая версия libmemcached - . Компилируем, устанавливаем. Для начала, наслаждаемся чтением страниц man:

man libmemcached man libmemcached_examples

C библиотекой поставляются описание несложных примеров использования. За более интересными же способами применения имеет смысл заглянуть в исходные тексты утилит, благо все идет вместе.

  • memstat - выдает информацию о сервере memcached
  • memcat - выдает значение по ключу
  • memrm - удаляет значение по ключу
  • memdump - выдает список ключей

Для начала посмотрим, что скажет сервер memcached, запущенный нами немного ранее в режиме выдачи сообщений. Запросим статистику сервера при помощи утилиты memstat:

memstat --servers localhost Listing 1 Server Server: localhost (11211) pid: 14534 uptime: 1950 time : 1247390264 version: 1.4.0 pointer_size: 32 rusage_user: 0.0 rusage_system: 0.0 curr_items: 0 total_items: 0 bytes: 0 curr_connections: 10 total_connections: 11 connection_structures: 11 cmd_get: 0 cmd_set: 0 get_hits: 0 get_misses: 0 evictions: 0 bytes_read: 0 bytes_written: 0 limit_maxbytes: 67108864 threads: 5

Получили статистику - следовательно memcached функционирует и откликается на запросы.

Итак, на настоящий момент готовы к использованию сервер memcached и клиентская библиотека. Осталось дело за малым - внедрить использование memcached в разрабатываемое приложение. Что касается приложения - все в руках разработчиков, а мы рассмотрим небольшой пример работы с базовыми функциями.

memcached предоставляет следующий набор основных функций (их, конечно, больше, но здесь приведены основные):

  • set - занести в кэш пару ключ-значение
  • add - занести в кэш значение при условии, что значения с таким ключом в кэше еще нет
  • replace - обновляет кэш при условии, что значение с таким ключом в кэше уже есть
  • get - получает значение из кэша по указанному ключу

Пример программы на C

#include "stdio.h" #include "string.h" #include "memcached.h" int main ( void ) { char * key = "key" ; char * value = "value" ; uint32_t flags = 0 ; size_t length = 0 ; char * value2 = NULL ; memcached_return rc ; // 1. создать структуру для работы с кэшем memcached_st * memc = memcached_create (NULL ); // 2. указать сервер с которым будем работать memcached_server_add (memc , "localhost" , 11211 ); // 3. занести пару ключ-значение в кэш rc = memcached_set (memc , key , strlen (key ), value , strlen (value ) + 1 , (time_t ) 0 , flags ); if (rc == MEMCACHED_SUCCESS ) { } else { // обработать ошибку } // 4. получить значение value2 = memcached_get (memc , key , strlen (key ), & length , & flags , & rc ); if (rc == MEMCACHED_SUCCESS ) { printf ("%s \n " , value2 ); free (value2 ); } else { // обработать ошибку } // 5. высвободить структуру memcached_free (memc ); return 0 ; }

Программа состоит из 5 основных операций и в особых комментариях не нуждается. Разве что можно отметить, что в пункте 2 можно добавлять много серверов, в случае использования распределенной системы.

Компилируем, возможно придется явно указать пути к библиотекам:

gcc -Wall -o mc mc.c -I/usr/local/include/libmemcached/ -lmemcached

Запускаем:

Видим требуемое значение - должно быть, заработало !

Для уточнения деталей, смотрим сообщения на сервере memcached:

<32 new auto-negotiating client connection 32: Client using the ascii protocol 32 STORED 32 sending key key >32 END <32 quit <32 connection closed.

В данном примере представлены следующие события: подключение клиента, установка пары ключ-значение, чтение данных по ключу и отключение клиента.

Посмотрим статистику на сервере:

memstat --servers localhost Listing 1 Server Server: localhost (11211) pid: 14534 uptime: 4659 time : 1247392973 version: 1.4.0 pointer_size: 32 rusage_user: 0.0 rusage_system: 0.0 curr_items: 1 total_items: 1 bytes: 58 curr_connections: 10 total_connections: 13 connection_structures: 11 cmd_get: 1 cmd_set: 1 get_hits: 1 get_misses: 0 evictions: 0 bytes_read: 58 bytes_written: 58 limit_maxbytes: 67108864 threads: 5

Следующие две строчки показывают, что в кэше появилось значение:

curr_items: 1 total_items: 1

Посмотрим на данное значение:

memcat --servers localhost key value

Итак, приложение, использующее memcached - готово.

PHP

Для начала установим PECL extension для PHP - memcached

pecl install memcached

На этом этапе возможно появление сообщения об ошибке вида:

ERROR: "phpize" failed

Это означает, что не установлен пакет php-dev или его аналог. Устанавливаем его и можно пробовать снова:

pecl install memcached install ok: channel://pecl.php.net/memcached-1.0.0 You should add "extension=memcached.so" to php.ini

Как нам и советуют, дописываем extension=memcached.so в php.ini и перезапускаем Apache.

Смотрим информацию об используемом PHP:

memcached support enabled Version 1.0.0 libmemcached version 0.31 Session support yes igbinary support no

Пример программы на PHP

Можно смело использовать обращения к memcached из PHP. Как обычно, рассмотрим пример:

addServer ("localhost" , 11211 ); $m -> set ("phpkey" , "phpvalue" ); var_dump ( $m -> get ("phpkey" )); ?>

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

string(8) "phpvalue"

Итак, PHP-приложение, использующее memcached - готово.

Кэширование данных сессий

Memcached можно использовать и как хранилище данных сессий для PHP. Такой подход часто используется в реальных приложениях. Давайте рассмотрим, что для этого надо сделать.

Вносим изменения в php.ini

;session.save_handler = files session.save_handler = memcached ;session.save_path = /var/lib/php5 session.save_path = localhost:11211

Параметр session.save_handler указывает, что теперь данные будут храниться в memcached. Второй параметр - session.save_path указывает сервер memcached (их может быть указано несколько, через запятую) на котором будут сохранятся данные.

Перезапускаем Apache - и готово!

Теперь надо проверить, что теперь данные сессии реально хранятся не на диске, а в memcached.

Рассмотрим работу несложного скрипта, заносящего что-нибудь в сессию:

Запускаем скрипт, он заносит данные в сессию, после чего смотрим на кэш

memdump --servers localhost key keyphp memc.sess.key.3ff8ccab14424082ff83a6dfbcf0941f

Итак - к нашим знакомым по предыдущим примерам ключам, добавился ключ с характерным именем memc.sess.key..

Хранение данных сессии перенесено в систему кэширования. Более подробную информацию по работе с memcached из PHP можно почитать .

Заключение

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

Понравилась статья? Поделитесь ей
Наверх