Добрый день!
В этой статье мы расскажем о том, как мы работаем с кешем в plus1.wapstart.ru, какие проблемы у нас возникали и как мы решали некоторые частные случаи.
Для начала о терминологии.
Под «кешем» в этой статье я буду понимать какое-то быстрое хранилище, которое может использоваться, в том числе, и для кеширования. При этом хранилище должно обладать стандартизированным интерфейсом.
Сервер/хранилище — это какое-нибудь приложение, которое может хранить данные и давать к ним доступ по интерфейсу, который описан ниже. Например, этим приложением может быть memcached.
Мы используем фреймворк onPHP. В нем есть абстрактный класс CachePeer, от которого должны наследоваться все реализации кешей. Интерфейс любой реализации сводится к следующим методам.
abstract public function get($key);
abstract public function delete($key);
abstract public function increment($key, $value);
abstract public function decrement($key, $value);
abstract protected function store(
$action, $key, $value, $expires = Cache::EXPIRES_MEDIUM
);
abstract public function append($key, $data);
В нашем мире существуют следующие реализации CachePeer (кликабельно)
На этой диаграмме представлены как реализации хранилищ (см. мост), так и разнообразные декораторы, решающие частные задачи.
В onphp есть поддержка работы c Redis; Memcached — целых две реализации: на сокетах и с использованием Memcache (http://php.net/Memcache); мы можем работать с SharedMemory. Если ничего из этого в инсталляции нет, то мы будем работать с памятью приложения.
Не смотря на все многообразие поддерживаемых технологий, мне не известно ни одного проекта на onphp, который бы использовал что-то отличное от Memcached.
Memcached правит этим миром. :)
У нас есть две реализации Memcache по следующим причинам:
- когда писался Memcached (на сокетах) extension Memcache еще не был написан.
да, мы знаем о конфликте имен с http://php.net/Memcached, в master это уже поправлено. - Реализация подключения на сокетах доступна вам даже тогда, когда вы не имеете доступа к настройкам php и не можете поставить необходимые расширения.
Мы практически повсеместно используем PeclMemcached, который подключается к серверу через расширение Memcache. При прочих равных он работает быстрее и к тому же поддерживает pconnect.
С альтернативной библиотекой (MemcacheD) у нас как-то не сложилось. Я пробовал писать под нее реализацию, но на тот момент (около двух лет назад) она была не очень стабильна.
О декораторах:
Когда соотношение проектов на одну кеширующую систему становится больше одного, то следует использовать WatermarkedPeer. Его смысл сводится к методу getActualWatermark(). Например, реализация get становится такой
public function get($key)
{
return $this->peer->get($this->getActualWatermark().$key);
}
Это позволяет избежать конфликта ключей. Данные из разных проектов/классов/ и т.д. будут записаны под разными ключами. В остальном — этот кеш является стандартным декоратором к какой-нибудь реализации хранилища.
Если вам необходимо разнести данные по множеству кеширующих систем, то можно либо воспользоваться кластером мемкеша из поставки php, либо взять один из наших аггрегатных кешей. У нас их несколько:
- AggregateCache определяет сервер, который будет работать с текущим ключем исходя из значения mt_rand от него с заранее переопределенным mt_srand. Звучит диковато, но на самом деле все очень просто.
- SimpleAggregateCache — вообще простой как кирпич. Нужный сервер определяется исходя из остатка от деления числового представления ключа на число серверов.
- CyclicAggregateCache — реализацию мы подсмотрели у last.fm. Она весьма изящна. Берем окружность, на нее наносим «точки монтирования» серверов.
Причем число точек для каждого сервера будет пропорционально его весу. При получении запроса ключ, так же маппим в какую-то точку на окружности. Его будет обрабатывать тот сервер, чья точка имеет меньшее расстояние к точке ключа.
Плюс этого подхода в том, что при добавлении сервера в пул, девалидируется лишь часть значений, а не все. Так же при выводе сервера из пула, пропадают только те значения, которые на нем хранились, при этом остальные сервера более-менее равномерно забирают его нагрузку. Об идее алгоритма подробнее можно почитать здесь или здесь.
На этом более-менее стандартная часть заканчивается. Большинству приложений должно хватить этого набора реализаций для создания нормальной системы кеширования.
Дальше начинаются частности.
- DebugCachePeer — название у него до такой степени самодокументируемо, что описывать его я смысла не вижу.
- ReadOnlyPeer — бывают такие кеши, из которых можно только читать, но нельзя в них писать. Например они могут записываться из какого-то другого места, или вовсе быть реализованы по-другому, например как наша рыба. Для этих хранилищь целесообразно использовать ReadOnlyPeer, т.к. он на стороне приложения будет гарантировать то, что данные будут только читаться, но не записываться/обновляться.
- CascadeCache — у вас есть локальный быстрый ненагруженный кеш, например на сокете. И есть какой-то удаленный кеш, который при этом еще и преднаполняется. Если для вашего приложения допустимо использование слегка устаревших данных, то можно использовать CascadeCache. Он будет делать читающие операции из локального кеша, при этом, если данные в локальном кеше отсутствуют, они будут запрошены в удаленном кеше.
Для «негативных» результатов (null) можно использовать одну из двух стратегий — они будут либо сохранены в локальном кеше, либо проигнорированы. - MultiCachePeer — у вас есть преднаполнятор кешей. При этом, желательно чтобы он мог наполнять десяток «локальных» кешей на десяти серверах.
Другими словами, мы хотим писать данные в одном место, а читать их, в общем случае, из другого. При этом, на каждом сервере с приложениями должна быть одна и таже конфигурация — для удобства деплоя. Для этого можно использовать MultiCachePeer примерно с таким конфигом:MultiCachePeer::create( PeclMemcached::create('localhost', 11211), array( PeclMemcached::create('meinherzbrennt', 11211), PeclMemcached::create('links234', 11211), PeclMemcached::create('sonne', 11211), PeclMemcached::create('ichwill', 11211), PeclMemcached::create('feuerfrei', 11211), PeclMemcached::create('mutter', 11211), PeclMemcached::create('spieluhr', 11211) ) );
- SequentialCache — представьте, что у вас есть хранилище, которое иногда падает или просто недоступно. Например, оно может иногда выводиться на обслуживание, перезапускаться и т.д. При этом, данные приложение хочет получать всегда. Для покрытия этой ситуации можно использовать SequentialCache, примерно с таким конфигом:
$cache = new SequentialCache( PeclMemcached::create('master', 11211, 0.1), //третий параметр конструктора - это таймаут. array( PeclMemcached::create('backup', 11211, 0.1), ) )
Т.к. практически все реализации используют паттерн декоратор, то их можно вполне успешно комбинировать.
Например, допустима такая конструкция:
$swordfish =
ReadOnlyPeer::create(
new SequentialCache(
PeclMemcached::create('localhost', 9898, 0.1),
array(
PeclMemcached::create('backup', 9898, 0.1),
)
)
);
Или даже такая:
$swordfish =
CascadeCache::create(
PeclMemcached::create('unix:///var/run/memcached_sock/memcached.sock', 0),
ReadOnlyPeer::create(
new SequentialCache(
PeclMemcached::create('localhost', 9898, 0.1),
array(
PeclMemcached::create('backup', 9898, 0.1),
)
)
),
CascadeCache::NEGATIVE_CACHE_OFF
);
В этом случае данные будут сначала искаться в локальном мемкеше, доступном по unix-socket, в случае если их там нет, то будет запрошен «мемкеш» localhost:9898. А в случае, если он недоступен, то backup:9898. При этом, приложение знает, что из кешей на портах 9898 можно только читать, но не писать.
На этом возможности кешей из onphp не заканчиваются. Можно делать совершенно разные конфигурации, которые будут покрывать ваши задачи. CachePeer из onphp — это круто.
ps. Когда-то давно здесь говорили о цикле статей про onphp. Начало положено этим постом. В будущем мы затронем другие темы, связанные с фреймворком и его применением в plus1.wapstart.ru.
pps. Пользуясь случаем, сообщаю, что мы ищем людей:
hantim.ru/jobs/11163-veduschiy-qa-menedzher-rukovoditel-otdela-testirovaniya
hantim.ru/jobs/11111-veduschiy-php-razrabotchik-team-leader
Автор: dovg