Ответ
Кеширование — это стратегия хранения копии данных или результатов вычислений во временном, быстром хранилище (кеше) для последующего быстрого доступа, что позволяет избежать повторного выполнения дорогостоящих операций (запросов к БД, сложных вычислений, внешних API-вызовов).
Основные уровни и типы кеширования в веб-приложении:
-
Кеширование на стороне клиента (браузер):
- HTTP-кеширование: Использование заголовков
Cache-Control,ETag,Last-Modifiedдля кеширования статических ресурсов (CSS, JS, изображения) и даже динамических ответов API. - LocalStorage / SessionStorage: Для хранения данных приложения на стороне клиента.
- HTTP-кеширование: Использование заголовков
-
Кеширование на стороне сервера (Server-side):
- Кеш приложения (In-memory): Хранение данных в оперативной памяти процесса (например, PHP-массив,
apcu_store()). Быстро, но не разделяется между процессами/серверами. - Распределённый кеш (Distributed Cache): Использование таких систем, как Redis или Memcached. Данные хранятся в отдельном сервисе, доступном всем экземплярам приложения. Это решает проблемы масштабирования и разделения кеша.
- Кеширование базы данных: Встроенные механизмы СУБД (например, кеш запросов в MySQL).
- Опкод-кеш (OPcache): Для скомпилированного PHP-байткода, ускоряет выполнение скриптов.
- Кеш приложения (In-memory): Хранение данных в оперативной памяти процесса (например, PHP-массив,
-
Прокси-кеширование и CDN:
- Varnish, Nginx cache: Кеширование целых HTML-страниц перед веб-сервером.
- CDN (Cloudflare, AWS CloudFront): Доставка статического и динамического контента с географически близких серверов.
Пример реализации кеширования результатов тяжёлого запроса с использованием Redis в PHP:
function getTopProducts(int $categoryId, int $limit = 10): array {
$redis = new Redis();
$redis->connect('redis-host', 6379);
$cacheKey = "top_products:cat_{$categoryId}:limit_{$limit}";
$ttl = 300; // Время жизни кеша — 5 минут
// Попытка получить данные из кеша
$cachedData = $redis->get($cacheKey);
if ($cachedData !== false) {
return json_decode($cachedData, true); // Возвращаем из кеша
}
// Данных в кеше нет — выполняем дорогой запрос к БД
$db = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$stmt = $db->prepare("SELECT * FROM products WHERE category_id = ? ORDER BY sales DESC LIMIT ?");
$stmt->execute([$categoryId, $limit]);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Сохраняем результат в Redis перед возвратом
$redis->setex($cacheKey, $ttl, json_encode($products));
return $products;
}
Критические аспекты:
- Инвалидация кеша: Определение стратегии обновления кеша при изменении данных (TTL, ручная очистка, cache tagging).
- Сквозное кеширование (Cache-Aside / Lazy Loading): Паттерн, использованный в примере выше — приложение сначала проверяет кеш, и только при промахе загружает данные из источника.
- Согласованность данных: Кеш может содержать устаревшие (stale) данные, что необходимо учитывать в бизнес-логике.
Ответ 18+ 🔞
Давай разберём, что за зверь такой — кеширование, чтобы не было потом, как в том анекдоте, где сервер спрашивает базу: «Ну чё, опять?», а она ему: «Да похуй, ищи в кеше».
Представь, что твоё веб-приложение — это я, когда жена просит сходить в гараж за банкой варенья. Если бы у меня был кеш, я бы эту банку уже держал на кухне, под рукой, и не пришлось бы каждый раз, блядь, обуваться и идти через весь двор. Вот и кеш — это такое быстрое хранилище под рукой, чтобы не ходить каждый раз в медленное (базу данных, внешний API или на тот свет за результатами сложных вычислений).
Так где же эту банку с вареньем можно припрятать? Уровни кеширования:
-
Прямо в кармане (на клиенте). Браузер — он хитрая жопа, может многое сам запомнить. Картинки, скрипты — если правильно настроить заголовки (
Cache-Controlи прочую муть), он их не будет таскать с сервера каждый раз. А ещё можно в егоLocalStorageкакую-нибудь ерунду запихнуть — данные формы, настройки. Удобно, но доверия к нему, ебать, ноль, потому что пользователь в любой момент может всё почистить. -
В серверной, под столом (на стороне сервера). Тут вариантов — овердохуища.
- В памяти процесса (In-memory). Самый быстрый способ — запихнуть данные в обычный массив PHP. Но есть проблема: если у тебя несколько рабочих процессов или серверов, у каждого будет своя, отдельная память. Один процесс записал, другой нихуя не видит. Как два мужика в одном гараже, но с разными ключами.
- В отдельном хранилище (Redis/Memcached). Вот это уже серьёзно. Заводишь отдельный сервис — Redis. Он теперь как общий сейф в прихожей. Все процессы со всех серверов приходят и кладут/берут данные оттуда. Масштабируется на ура. Почти всегда это и есть правильный выбор для шустрого кеша.
- Кеш самой базы данных. База данных — тоже не дура, она частые запросы сама пытается запомнить. Но надеяться на это — всё равно что надеяться, что жена сама догадается, где ты носки спрятал. Ненадёжно.
-
На подступах к крепости (Прокси и CDN). Это когда ты ставишь перед своим основным сервером здоровенную бандуху типа Varnish или подключаешь CDN. Они могут кешировать целые готовые HTML-страницы и отдавать их, даже не беспокоя твоё основное приложение. Это, блядь, уровень «я тебе всю полку с вареньем сразу в комнату принесу».
А теперь смотри, как это выглядит в коде на PHP с Redis. Чистая практика, ёпта.
function getTopProducts(int $categoryId, int $limit = 10): array {
$redis = new Redis();
$redis->connect('redis-host', 6379);
// Придумываем ключ. Важно, чтобы он был уникальным для этого запроса.
$cacheKey = "top_products:cat_{$categoryId}:limit_{$limit}";
$ttl = 300; // Жить этому кешу 5 минут. Потом — на свалку истории.
// Пробуем найти банку в кеше (в сейфе).
$cachedData = $redis->get($cacheKey);
if ($cachedData !== false) {
// Ура, нашли! Возвращаем, не ходя в гараж (в базу).
return json_decode($cachedData, true);
}
// Вот тут пиздец. В кеше пусто. Придётся идти в самую дальнюю, холодную БД.
$db = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$stmt = $db->prepare("SELECT * FROM products WHERE category_id = ? ORDER BY sales DESC LIMIT ?");
$stmt->execute([$categoryId, $limit]);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Но мы не дураки! Прежде чем вернуть результат, запихнём его в Redis.
// В следующий раз он уже будет там.
$redis->setex($cacheKey, $ttl, json_encode($products));
return $products;
}
На чём споткнуться можно, или подводные ебушки-воробушки:
- Инвалидация. Это самое сложное. Вот данные в базе изменились (цену на товар поменяли), а в кеше лежит старая версия. Как сказать кешу, что он говно? Можно жить по TTL (как в примере — через 5 минут само протухнет). Можно вручную удалять ключ при изменении данных. В общем, без стратегии тут — пиши пропало.
- Паттерн «Cache-Aside» (или Lazy Loading). Это ровно то, что мы сделали в коде выше. Приложение само управляет кешем: «Сначала посмотри там, нет — сходи туда, принеси — положи сюда». Классика, работает на ура.
- Согласованность. Надо чётко понимать: кеш — это всегда возможно устаревшие данные. Если тебе нужна стопроцентная свежесть на миллисекунду (например, баланс счёта), то кешировать это — идти по охуенно тонкому льду. А если топ товаров — да похуй, пусть пять минут полежит старый список, никто не умрёт.
Вот и вся магия. Главное — не превратить приложение в мартышку, которая только и делает, что бегает между Redis и БД, забывая, что у неё вообще бизнес-логика есть.