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