|
|||||||
Кэширование данных
Время создания: 05.02.2018 10:01
Текстовые метки: highload cache
Раздел: Highload
Запись: Velonski/mytetra-database/master/base/15178068724jd8vcyuxo/text.html на raw.githubusercontent.com
|
|||||||
|
|||||||
Кэширование — это один из способов оптимизации Web приложений. В любом приложении встречаются медленные операции (SQL запросы или запросы к внешним API), результаты которых можно сохранить на некоторое время. Это позволит выполнять меньше таких операций, а большинству пользователей показывать заранее сохраненные данные. Наиболее популярная технология кеширования для Web приложений — Memcache . Когда нужно кэшировать Старайтесь избегать кэширования, пока в этом не будет прямой необходимости. Это простая техника, но это снижает гибкость приложения. Не делайте лишнюю работу заранее, но закладывайте возможность использования кэширования в будущем:
Что кэшировать? Кэшировать нужно данные, которые медленно генерируются и часто запрашиваются. На практике это обычно:
Кэширование выборок из баз данных Запросы к базе данных — наиболее распространенный пример. На основе Мemcache реализуется очень просто: <? memcache_connect('localhost', 11211); function get_online_users() { if ( !$list = memcache_get('online_users') ) { $sql = 'SELECT * FROM users WHERE last_visit > UNIX_TIMESTAMP() - 60*10'; $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 пользователей, которые тоже лежат в кэше. Сбрасывать списки при каждом обновлении данных любого пользователя не эффективно. Поэтому обычно используют такой подход:
Реализация выглядит так: <? memcache_connect('localhost', 11211); function get_online_users() { if ( !$list = memcache_get('online_users') ) { $sql = 'SELECT id FROM users WHERE last_visit > UNIX_TIMESTAMP() - 60*10'; $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 . Повторные запросы Некоторые данные могут запрашиваться несколько раз в рамках одной страницы, например: <html> <body> <h1><?=htmlspecialchars( get_user( $_SESSION['id'] )['name'] )?></h1> ... Email: <?=get_user( $_SESSION['id'] )['email']?> ... <a href="/<?=get_user( $_SESSION['id'] )['nick']?>">Моя страница</a> ... Каждый вызов 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 с дополнительным кэшом: <? class mem_cache { private $inner_cache = []; public static function get( $key ) { if ( array_key_exists($key, $this->inner_cache) ) return $this->inner_cache[$key]; $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-задачах (допустим, мы перебираем всех пользователей для отправки рассылки). Тогда лучше добавить отключение внутреннего кэша: <? class mem_cache { private $inner_cache = []; public static $inner_cache_enabled = true; public static function get( $key ) { if ( self::$inner_cache_enabled && array_key_exists($key, $this->inner_cache) ) return $this->inner_cache[$key]; $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): Memcache постарается удалить прежде всего те данные, которые запрашивались очень давно (т.е. менее популярные удалит, а более популярные оставит). Кэширование очень медленных запросов Представьте, что у Вас есть запрос, который выполняется 10 секунд. Вы сохраняете его в кэш на 1 час. Когда проходит это время, данные в кэше удаляются. В первые 10 секунд после этого Вы сталкиваетесь с ситуацией, когда несколько пользователей одновременно вызывают этот тяжелейший запрос. Это может привести к катастрофическим последствиям, т.к. в течение 10 секунд может быть несколько сотен или тысяч таких вызовов. Чтобы этого избежать, необходимо использовать специальную методику дублирования . Атомарные операции Иногда в кэше хранятся счетчики (например, количество пользователей). При добавлении новых пользователей, вместо сброса счетчика и повторной выборки, можно просто увеличить значение кэша на единицу. Но сделать это через приложение нельзя, т.к. это приведет к потере данных от двух одновременно выполненных запросов: <? $count = memcache_get('count'); $count++; memcache_set('count', $count); Memcache поддерживает две атомарные операции увеличения и уменьшения чисел: <? memcache_increment('count'); # Увеличит счетчик на 1, функция memcache_decrement() уменьшает счетчик Самое важное Кэширование в приложениях на основе Memcache — это очень сильный инструмент. Не забывайте, что Memcache не гарантирует Вам сохранности данных. Это значит, что нельзя рассчитывать на то, что сохраненные на 60 минут данные будут находиться в кэше именно 60 минут. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|