Redis: кеширование
Redis — in-memory хранилище данных типа ключ-значение, используемое в Node.js-приложениях для кеширования результатов запросов, хранения сессий, rate-limiting и как брокер очередей.
Зачем нужно
Обращение к базе данных при каждом запросе замедляет API. Redis хранит данные в памяти, обеспечивая доступ за микросекунды. Кеширование популярных запросов (каталог товаров, профиль пользователя) снижает нагрузку на БД на порядки и уменьшает latency с десятков миллисекунд до долей.
Где используется
- Кеширование результатов дорогих SQL-запросов
- Хранение HTTP-сессий (альтернатива in-memory сессиям)
- Rate-limiting запросов (счётчики с TTL)
- Pub/Sub для real-time уведомлений
- Хранение временных данных: OTP-коды, токены сброса пароля
Основной контент
Установка (ioredis)
npm install ioredis
# ioredis — рекомендуемый клиент: поддерживает Cluster, Sentinel, pipeline
// db/redis.js
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD,
retryStrategy: (times) => Math.min(times * 50, 2000) // переподключение
});
redis.on('error', (err) => console.error('Redis error:', err));
redis.on('connect', () => console.log('Redis connected'));
module.exports = redis;
Базовые операции
const redis = require('./db/redis');
// Установить значение с TTL (seconds)
await redis.set('key', 'value', 'EX', 3600); // истечёт через 1 час
// Получить значение
const value = await redis.get('key'); // null если не найдено
// Хешировать объект
await redis.hset('user:1', { name: 'Alice', email: 'alice@example.com' });
const user = await redis.hgetall('user:1'); // { name: 'Alice', email: '...' }
// Удалить ключ
await redis.del('key');
// Проверить существование
const exists = await redis.exists('key'); // 0 или 1
// Время жизни ключа
const ttl = await redis.ttl('key'); // секунды, -1 = нет TTL, -2 = не существует
Паттерн Cache-Aside
// middleware/cache.js
const redis = require('../db/redis');
function cacheMiddleware(keyFn, ttl = 60) {
return async (req, res, next) => {
const key = keyFn(req);
try {
const cached = await redis.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
} catch (err) {
console.error('Cache read error:', err);
}
// Перехватываем res.json() для сохранения в кеш
const originalJson = res.json().bind(res);
res.json() = async (data) => {
try {
await redis.set(key, JSON.stringify(data), 'EX', ttl);
} catch (err) {
console.error('Cache write error:', err);
}
return originalJson(data);
};
next;
};
}
// Использование
router.get('/products',
cacheMiddleware((req) => `products:${JSON.stringify(req.query)}`, 300),
ProductController.getAll
);
Rate Limiting через Redis
// middleware/rateLimit.js
async function redisRateLimit(req, res, next) {
const key = `rate:${req.ip}`;
const limit = 100;
const window = 60; // секунд
const requests = await redis.incr(key);
if (requests === 1) {
await redis.expire(key, window); // установить TTL только при первом запросе
}
if (requests > limit) {
return res.status(429).json({ error: 'Too many requests' });
}
res.setHeader('X-RateLimit-Remaining', limit - requests);
next;
}
Хранение сессий
npm install connect-redis express-session
const session = require('express-session');
const { createClient } = require('redis');
const RedisStore = require('connect-redis').default;
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect;
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { maxAge: 86400000 } // 1 день
}));
Частые ошибки
- Не устанавливать TTL — ключи накапливаются и заполняют память; всегда указывать
EXилиPX - Кешировать ошибки — если запрос к БД вернул ошибку и она закешировалась, пользователи будут получать ошибку до истечения TTL; кешировать только успешные результаты
- Не обрабатывать недоступность Redis — если Redis упал, не отдавать ошибку пользователю; обернуть в try/catch и продолжать работу без кеша
- JSON.parse без проверки —
redis.getможет вернутьnull;JSON.parse(null)выбросит ошибку
Связанные темы
- _MOC Node.js
- Message Queue -- RabbitMQ, Bull
- Node.js -- безопасность (Helmet, rate-limit)
- PostgreSQL -- основы и pg