Назад к блогу
Backend

Redis: кэширование, сессии и очереди

Использование Redis для кэширования данных, хранения сессий, pub/sub, очередей задач и инвалидации кэша.

5 января 2026 г.
11 мин чтения
61 просмотров
MOLOTILO

MOLOTILO DIGITAL

Redis: кэширование, сессии и очереди

Что такое Redis

Redis — in-memory хранилище данных, используемое для кэширования, сессий, очередей и pub/sub. Скорость Redis измеряется микросекундами, что делает его идеальным для высоконагруженных приложений.

Подключение к Redis

import Redis from 'ioredis';

// Простое подключение
const redis = new Redis({
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT || '6379'),
  password: process.env.REDIS_PASSWORD,
  db: 0
});

// С retry стратегией
const redis = new Redis({
  host: 'localhost',
  port: 6379,
  retryStrategy: (times) => {
    const delay = Math.min(times * 50, 2000);
    return delay;
  },
  maxRetriesPerRequest: 3
});

// Проверка подключения
redis.on('connect', () => console.log('Redis connected'));
redis.on('error', (err) => console.error('Redis error:', err));

Кэширование данных

// Простой кэш
async function getUser(id: string): Promise<User> {
  const cacheKey = `user:${id}`;
  
  // Проверяем кэш
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }
  
  // Загружаем из БД
  const user = await prisma.user.findUnique({ where: { id } });
  
  // Сохраняем в кэш на 1 час
  await redis.setex(cacheKey, 3600, JSON.stringify(user));
  
  return user;
}

// Универсальная функция кэширования
async function cached<T>(
  key: string,
  ttl: number,
  fn: () => Promise<T>
): Promise<T> {
  const cached = await redis.get(key);
  if (cached) {
    return JSON.parse(cached);
  }
  
  const data = await fn();
  await redis.setex(key, ttl, JSON.stringify(data));
  return data;
}

// Использование
const products = await cached(
  'products:featured',
  300, // 5 минут
  () => prisma.product.findMany({ where: { featured: true } })
);

Инвалидация кэша

// Удаление конкретного ключа
async function updateUser(id: string, data: UpdateUserData) {
  const user = await prisma.user.update({
    where: { id },
    data
  });
  
  // Инвалидируем кэш
  await redis.del(`user:${id}`);
  
  return user;
}

// Удаление по паттерну
async function invalidateUserCache(userId: string) {
  const keys = await redis.keys(`user:${userId}:*`);
  if (keys.length > 0) {
    await redis.del(...keys);
  }
}

// Тегированный кэш
async function setWithTags(key: string, value: any, tags: string[], ttl: number) {
  const pipeline = redis.pipeline();
  
  pipeline.setex(key, ttl, JSON.stringify(value));
  
  for (const tag of tags) {
    pipeline.sadd(`tag:${tag}`, key);
    pipeline.expire(`tag:${tag}`, ttl);
  }
  
  await pipeline.exec();
}

async function invalidateByTag(tag: string) {
  const keys = await redis.smembers(`tag:${tag}`);
  if (keys.length > 0) {
    await redis.del(...keys, `tag:${tag}`);
  }
}

// Использование
await setWithTags(
  `product:${id}`,
  product,
  ['products', `category:${product.categoryId}`],
  3600
);

// Инвалидация всех продуктов категории
await invalidateByTag(`category:${categoryId}`);

Сессии

import session from 'express-session';
import RedisStore from 'connect-redis';

// Express сессии в Redis
app.use(session({
  store: new RedisStore({ client: redis }),
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000 // 24 часа
  }
}));

// Кастомное хранение сессий
class SessionManager {
  private prefix = 'session:';
  private ttl = 86400; // 24 часа

  async create(userId: string, data: SessionData): Promise<string> {
    const sessionId = crypto.randomUUID();
    const key = this.prefix + sessionId;
    
    await redis.setex(key, this.ttl, JSON.stringify({
      userId,
      ...data,
      createdAt: Date.now()
    }));
    
    return sessionId;
  }

  async get(sessionId: string): Promise<SessionData | null> {
    const data = await redis.get(this.prefix + sessionId);
    return data ? JSON.parse(data) : null;
  }

  async destroy(sessionId: string): Promise<void> {
    await redis.del(this.prefix + sessionId);
  }

  async refresh(sessionId: string): Promise<void> {
    await redis.expire(this.prefix + sessionId, this.ttl);
  }
}

Pub/Sub

// Publisher
const publisher = new Redis();

async function publishEvent(channel: string, data: any) {
  await publisher.publish(channel, JSON.stringify(data));
}

// Subscriber
const subscriber = new Redis();

subscriber.subscribe('orders', 'notifications');

subscriber.on('message', (channel, message) => {
  const data = JSON.parse(message);
  
  switch (channel) {
    case 'orders':
      handleNewOrder(data);
      break;
    case 'notifications':
      sendNotification(data);
      break;
  }
});

// Использование
await publishEvent('orders', {
  orderId: '123',
  userId: 'user-1',
  total: 1500
});

Очереди задач

import Bull from 'bull';

// Создание очереди
const emailQueue = new Bull('email', {
  redis: {
    host: 'localhost',
    port: 6379
  }
});

// Добавление задачи
await emailQueue.add('welcome', {
  to: 'user@example.com',
  subject: 'Welcome!',
  template: 'welcome'
}, {
  attempts: 3,
  backoff: {
    type: 'exponential',
    delay: 1000
  },
  removeOnComplete: true
});

// Обработка задач
emailQueue.process('welcome', async (job) => {
  const { to, subject, template } = job.data;
  await sendEmail(to, subject, template);
  return { sent: true };
});

// События
emailQueue.on('completed', (job, result) => {
  console.log(`Job ${job.id} completed`);
});

emailQueue.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err);
});

Rate Limiting

async function rateLimit(
  key: string,
  limit: number,
  window: number
): Promise<boolean> {
  const current = await redis.incr(key);
  
  if (current === 1) {
    await redis.expire(key, window);
  }
  
  return current <= limit;
}

// Middleware
async function rateLimitMiddleware(req, res, next) {
  const key = `ratelimit:${req.ip}`;
  const allowed = await rateLimit(key, 100, 60); // 100 запросов в минуту
  
  if (!allowed) {
    return res.status(429).json({ error: 'Too many requests' });
  }
  
  next();
}

Заключение

Redis — незаменимый инструмент для высоконагруженных приложений. Кэширование ускоряет ответы, сессии масштабируются, очереди разгружают основной процесс. Правильная инвалидация кэша — ключ к консистентности данных.

Redis — это не просто кэш. Это универсальный инструмент для pub/sub, очередей, rate limiting и многого другого.

RedisКэшированиеBackendPerformanceNode.js

Понравилась статья?

Подпишитесь на наш блог, чтобы не пропустить новые материалы