Quay lại blog
data9 phút đọc

Redis Caching Patterns cho Ứng dụng Node.js

Caching production với Redis — cache-aside, write-through, session management, rate limiting với sorted sets, pub/sub và chiến lược invalidation.

V
Bởi Ventra Rocket
·Đăng ngày 5 tháng 1, 2026
#Redis#Caching#Node.js#Performance#Backend

Redis là caching layer phổ biến nhất cho Node.js, với sorted sets, pub/sub và atomic operations.

Khởi tạo

import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

1. Cache-Aside Pattern

class ProductService {
  private readonly TTL = 3600;

  async getProduct(id: string): Promise<Product> {
    const key = `product:${id}`;
    const cached = await redis.get(key);
    if (cached) return JSON.parse(cached);

    const product = await db.products.findUnique({ where: { id } });
    if (!product) throw new Error("Không tìm thấy");

    await redis.setEx(key, this.TTL, JSON.stringify(product));
    return product;
  }

  async updateProduct(id: string, data: Partial<Product>) {
    const product = await db.products.update({ where: { id }, data });
    await redis.del(`product:${id}`);  // Xóa cache khi cập nhật
    return product;
  }
}

2. Ngăn chặn Cache Stampede

async function getWithLock<T>(key: string, ttl: number, fn: () => Promise<T>) {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const lockKey = `lock:${key}`;
  const acquired = await redis.set(lockKey, "1", { EX: 10, NX: true });

  if (acquired) {
    try {
      const data = await fn();
      await redis.setEx(key, ttl, JSON.stringify(data));
      return data;
    } finally {
      await redis.del(lockKey);
    }
  }
  await new Promise((r) => setTimeout(r, 100));
  return getWithLock(key, ttl, fn);  // Retry
}

3. Quản lý Session

const TTL = 86400;  // 24 giờ

async function createSession(userId: string) {
  const id = crypto.randomUUID();
  await redis.setEx(`session:${id}`, TTL, JSON.stringify({ userId }));
  return id;
}

async function getSession(id: string) {
  const raw = await redis.get(`session:${id}`);
  if (!raw) return null;
  await redis.expire(`session:${id}`, TTL);  // Sliding expiration
  return JSON.parse(raw);
}

4. Rate Limiting với Sorted Set

async function checkRateLimit(id: string, limit: number, windowSec: number) {
  const key = `ratelimit:${id}`;
  const now = Date.now();
  const windowStart = now - windowSec * 1000;

  const pipeline = redis.multi();
  pipeline.zRemRangeByScore(key, "-inf", windowStart);
  pipeline.zAdd(key, { score: now, value: `${now}` });
  pipeline.zCard(key);
  pipeline.expire(key, windowSec);

  const results = await pipeline.exec();
  const count = results[2] as number;
  return { allowed: count <= limit, remaining: Math.max(0, limit - count) };
}

5. Pub/Sub

const pub = redis.duplicate();
const sub = redis.duplicate();
await Promise.all([pub.connect(), sub.connect()]);

await pub.publish("inventory:updated", JSON.stringify({ productId: "123", qty: 50 }));

await sub.subscribe("inventory:updated", (msg) => {
  const event = JSON.parse(msg);
  // Cập nhật WebSocket clients
});

Kết luận

Redis phát huy tối đa khi: cache-aside cho DB offloading, sorted sets cho rate limiting, atomic operations cho distributed locks. Luôn đo cache hit rates và lên kế hoạch invalidation trước.

Bài viết liên quan

Redis Caching Patterns cho Ứng dụng Node.js | Ventra Rocket