Кэширование — это один из способов оптимизации Web приложений. В любом приложении встречаются медленные операции (SQL запросы или запросы к внешним API), результаты которых можно сохранить на некоторое время. Это позволит выполнять меньше таких операций, а большинству пользователей показывать заранее сохраненные данные.
Наиболее популярная технология кеширования для Web приложений — Memcache.
Старайтесь избегать кэширования, пока в этом не будет прямой необходимости. Это простая техника, но это снижает гибкость приложения.
Не делайте лишнюю работу заранее, но закладывайте возможность использования кэширования в будущем:
Кэшировать нужно данные, которые медленно генерируются и часто запрашиваются. На практике это обычно:
Запросы к базе данных — наиболее распространенный пример. На основе Мemcache реализуется очень просто:
$q = mysql_query($sql); while ($row = mysql_fetch_assoc($q)) $list[] = $row; memcache_set('online_users', $list, 60*60); } return $list; } $list = get_online_users(); ...
Запрос на получение пользователей кэшируется на 1 час
Если Вы кэшируете данные, которые могут обновляться, необходимо очищать кэш после каждого обновления:
memcache_connect('localhost', 11211); function get_user($id) { if ( !$data = memcache_get('user' . $id) ) { $sql = 'SELECT * FROM users WHERE id= ' . intval($id); $q = mysql_query($sql); $data = mysql_fetch_assoc($q); memcache_set('user' . $id, $data, 60*60); } return $data; } function save_user($id, $data) { mysql_query('UPDATE users SET ... WHERE id = ' . intval($id)); memcache_delete('user' . $id); }
Кэширование списков
Допустим, Вы кэшируете данные каждого пользователя, как в примере, а также их списки (например, список online пользователей). При обновлении данных пользователя, Вы удаляете данные из кэша только для указанного пользователя. Но его данные могут также присутствовать в списке online пользователей, которые тоже лежат в кэше. Сбрасывать списки при каждом обновлении данных любого пользователя не эффективно. Поэтому обычно используют такой подход:
Реализация выглядит так:
$q = mysql_query($sql); while ($row = mysql_fetch_assoc($q)) $list[] = $row['id']; memcache_set('online_users', $list, 60*60); } return $list; } $list = get_online_users(); foreach ( $list as $id ) { $user = get_user($id); ... }
Получим список ID пользователей и для каждого из них получим актуальные данные
Для получения данных сразу нескольких объектов можно использовать Multiget.
Некоторые данные могут запрашиваться несколько раз в рамках одной страницы, например:
... Email: ... Моя страница ...
Каждый вызов get_user() будет получать данные из кэша. Если Memcache стоит на отдельном сервере, это вызовет большой сетевой трафик и задержки.
memcache_connect('localhost', 11211); function get_user($id) { global $app_cache; if ( $app_cache['user' . $id] ) return $app_cache['user' . $id]; if ( !$data = memcache_get('user' . $id) ) { $sql = 'SELECT * FROM users WHERE id= ' . intval($id); $q = mysql_query($sql); $data = mysql_fetch_assoc($q); memcache_set('user' . $id, $data, 60*60); $app_cache['user' . $id] = $data; } return $data; } function save_user($id, $data) { global $app_cache; mysql_query('UPDATE users SET ... WHERE id = ' . intval($id)); memcache_delete('user' . $id); unset($app_cache['user' . $id]); }
В реальных приложениях, имеет смысл иметь обертку для Memcache с дополнительным кэшом:
$data = memcache_get( $this->resource, $key ); $this->inner_cache[$key] = $data; return $data['value']; } public static function set( $key, $value, $ttl ) { memcache_set($key, $value, $ttl); $this->inner_cache[$key] = $value; } public static function del( $key ) { memcache_delete($key); unset($this->inner_cache[$key]); } }
$inner_cache хранит дополнительный кэш
Внимание. Использование этого подхода может приводить к утечкам памяти в случаях, когда идет работа с большим количеством данных в кэше. Например, в cron-задачах (допустим, мы перебираем всех пользователей для отправки рассылки). Тогда лучше добавить отключение внутреннего кэша:
$data = memcache_get( $this->resource, $key ); $this->inner_cache[$key] = $data; return $data['value']; } public static function set( $key, $value, $ttl ) { memcache_set($key, $value, $ttl); if ( self::$inner_cache_enabled ) $this->inner_cache[$key] = $value; } public static function del( $key ) { memcache_delete($key); unset($this->inner_cache[$key]); } } ... mem_cache::$inner_cache_enabled = false;
Отключаем внутренний кэш
При обновлении особенно тяжелых данных следует использовать не сброс кэша, а прямое обновление данных в нем:
memcache_connect('localhost', 11211); function get_rss($id) { if ( !$data = memcache_get('rss') ) { $data = file_get_contents('http://rss.com/rss'); memcache_set('rss', $data, 60*60); } return $data; } function update_rss_feed($id, $data) { # операции по обновлению внешних ресурсов $data = file_get_contents('http://rss.com/rss'); memcache_set('rss', $data, 60*60); }
Это позволит избежать дополнительной нагрузки при выполнении тяжелых выборок, когда ключ удаляется. Такую методику обычно используют в cron задачах, чтобы периодически обновлять результаты очень тяжелых выборок.
ttl (время жизни) — это время, после которого, данные будут удалены из кэша. В Memcache устанавливается в секундах:
memcache_set('rss', $data, 60*60);
Установка ttl на 1 час
Чаще всего ttl ставят от нескольких минут до нескольких дней. Не используйте значение 0 (бесконечное хранение), это может засорить память.
Любой кэш работает по принципу вытеснения если ему не хватает памяти. Т.е. если Memcache может использовать максимум 1G памяти, а Вы пытаетесь сохранить ключей на 2G, то половину из этих данных Memcache удалит. Для определения, какие именно ключи удалять, используется алгоритм LRU (Least Recently Used):
Представьте, что у Вас есть запрос, который выполняется 10 секунд. Вы сохраняете его в кэш на 1 час. Когда проходит это время, данные в кэше удаляются. В первые 10 секунд после этого Вы сталкиваетесь с ситуацией, когда несколько пользователей одновременно вызывают этот тяжелейший запрос. Это может привести к катастрофическим последствиям, т.к. в течение 10 секунд может быть несколько сотен или тысяч таких вызовов.
Иногда в кэше хранятся счетчики (например, количество пользователей). При добавлении новых пользователей, вместо сброса счетчика и повторной выборки, можно просто увеличить значение кэша на единицу. Но сделать это через приложение нельзя, т.к. это приведет к потере данных от двух одновременно выполненных запросов:
---$count = memcache_get('count'); $count++; memcache_set('count', $count);--- Memcache поддерживает две атомарные операции увеличения и уменьшения чисел: memcache_increment('count');
Увеличит счетчик на 1, функция memcache_decrement() уменьшает счетчик
Кэширование в приложениях на основе Memcache — это очень сильный инструмент. Не забывайте, что Memcache не гарантирует Вам сохранности данных. Это значит, что нельзя рассчитывать на то, что сохраненные на 60 минут данные будут находиться в кэше именно 60 минут.
Этот текст был написан несколько лет назад. С тех пор упомянутые здесь инструменты и софт могли получить обновления. Пожалуйста, проверяйте их актуальность.
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…