Предисловие
В наше время огромного колличества высоконагруженных проектов тема кеширования чего бы то не было как никогда актуальна. Кешируют как запросы из бд, отдельные блоки страницы, так и все страницы целиком.
Для себя я решил кешировать только результаты запросов к бд, т.к., имхо, загружать кеш огромными (или не очень) html блоками — в некотором роде расточительство ресурсов. (Это утверждение скорее всего справедливо только если вы не используете шаблонизатор). Ну и конечно же в качестве службы кеширования использую всем известный memcached.
Теперь давайте посмотрим — какие проблемы нас ждут при таком кешировании.
А проблема то всего одна, но зато не самая лицеприятная — поддерживание кеша всегда в актуальном состоянии.
Под катом — мое решение проблемы, позволяющее кешу жить вечно (если конечно он актуален и у вас не закончилась оперативка).
Подробнее
Придерживаясь модной нынче модели MVC все запросы к бд у нас будут проводится в моделях. В своих проектах для каждого раздела сайта у меня своя отдельная модель, а так же общая модель, данные из которой необходимы в каждом разделе.
Ключ записи кеша таким образом состоит из имени раздела, названия функции и параметров, передаваемых этой функции. Все это оборачивается в md5 для того, чтобы предотвратить слишком длинные стоки или передачу массива параметров как части ключа кеша.
Теперь при обновлении информации в базе данных в 90% случаев мы точно будем знать кеш каких функций нам нужно будет обнулить. Но так происходит не всегда. К примеру пользователь удалил последние 5 сообщений из своего личного кабинета, но, поскольку все сообщения разбиты скажем по 10 на страницу, обнулив кеш только последней страницы, все остальные страницы останутся закешированны в старом состоянии, а это уже не хорошо. Задача — обнулить кеш функции, зная всего 1 ее параметр (ид пользователя), но не зная второго (номер страницы).
Итак, мое решение
Вкратце — при каждом добавлении новой записи в кеш составные части ключа записываются в монго, давая тем самым возможность полностью воссоздать ключ кеша, зная только его часть. Не буду вникаться в подробности, просто пробежимся по коду.
class Cache extends Memcached { private $registry; public function __construct() { parent::__construct(); $this->addServer('localhost', 11211); $m = new Mongo(); $this->registry = $m->local->cache_registry; //выбираем коллекцию, где будут храниться записи ключей } public function set($name, $content) { //добавляем запись в кеш $this->registry->insert(array('id' => $name)); parent::set(md5(print_r($name, 1)), $content); } public function delete($name) { //простое удаление по ключу $this->registry->remove(array('id' => $name)); parent::delete(md5(print_r($name, 1))); } public function get($name) { return parent::get(md5(print_r($name, 1))); } public function smart_delete($params) { //удаление по части ключа $criteria = array(); $size = sizeof($params); for ($i = 0; $i < 2; $i++) { // перебираем название модели и названия функции $criteria['id.' . $i] = $params[$i]; } if ($size == 3) { //параметры функции идут вложенным массивом for ($i = 0; $i < sizeof($params[2]); $i++) { $criteria['id.2.' . $i] = $params[2][$i]; } } elseif ($size > 3) throw new Exception('Size of input parameters is out of expected range'); $cursor = $this->registry->find($criteria); while ($cursor->hasNext()) { // удаляем все найденые записи из базы и из кеша $data = $cursor->getNext(); $id = $data['_id']; parent::delete(md5(print_r($data['id'], 1))); $this->registry->remove(array('_id' => new MongoId($id))); } } }
И пример вызова, для того чтобы все окончательно встало на свои места:
$cache = new Cache(); $cache->smart_delete('user', 'messages', array(1));/*удаляем весь кеш функции messages из раздела user где первый параметр = 1*/
Как видите, все до неприличия просто и достаточно много профита:
- Удаление всех записей из определенного раздела
- Удаление всех записей раздела только по имени функции
- Удаление всех записей функции, зная только необходимый параметр и отбрасывая лишние.
Конечно, при том, что реализовано сейчас нельзя удалить запись, зная только второй параметр и не зная первый, что обязывает продумывать порядок параметров в момент создания функции. Или удалить записи одинаковых функций из разных разделов (мало ли зачем), но, думаю, если будет свободное время все это вполне реализуемо.
P.S. Всех с прошедшими праздниками!