TopDev AI Robot TopDev
#Backend & Microservices#Cơ sở dữ liệu & Tối ưu#Kiến trúc & System Design

Giải thích hiện tượng Thundering Herd Problem và giải pháp Cache Stampede với Mutex

Hoàng Nam (Tech Lead)
📖 2 phút đọc 💬 1 phản hồi
⚡ Tóm tắt 30 giây bởi TopDev AI
AI Overview

Khoảnh khắc chiếc chìa khóa Cache bị hết hạn Cache Expiration Hãy tưởng tượng trang chủ của bạn có một danh sách tin tức hot được lưu trong Redis với thời gian sống TTL là 10 phút. Trang chủ đang phụ…

☰ Mục lục bài viết (5 phần) Mở / Đóng mục lục

Khoảnh khắc chiếc chìa khóa Cache bị hết hạn (Cache Expiration)#

Hãy tưởng tượng trang chủ của bạn có một danh sách tin tức hot được lưu trong Redis với thời gian sống (TTL) là 10 phút. Trang chủ đang phục vụ 5.000 requests mỗi giây.

Vào đúng giây thứ 600, key trong Redis hết hạn và bị xóa. Trong đúng một giây đó:

  • 5.000 requests cùng ập tới và cùng thấy cache bị miss!
  • 5.000 requests cùng lúc lao thẳng xuống Database chính để chạy cùng một câu truy vấn SELECT nặng nề.
  • Database lập tức sập nguồn do quá tải CPU và nghẽn connection pool.

Hiện tượng này được gọi là Cache Stampede hoặc Thundering Herd Problem (Đàn bò giẫm đạp).

Giải pháp 1: Khóa phân tán (Distributed Mutex)#

Chỉ cho phép duy nhất một request đầu tiên được phép truy vấn Database để tái tạo cache, các request còn lại bắt buộc phải chờ hoặc đọc tạm cache cũ:

TYPESCRIPT
async function getNewsWithLock(key: string) {
  let data = await redis.get(key);
  if (data) return JSON.parse(data);

  // Thử lấy khóa với cờ NX (Not Exists)
  const lockKey = `lock:${key}`;
  const hasLock = await redis.set(lockKey, '1', 'EX', 5, 'NX');

  if (hasLock) {
    try {
      // Chỉ duy nhất tiến trình này được query database!
      const freshData = await db.query('SELECT * FROM hot_news');
      await redis.set(key, JSON.stringify(freshData), 'EX', 600);
      return freshData;
    } finally {
      await redis.del(lockKey);
    }
  } else {
    // Các request khác chờ 50ms rồi thử đọc lại từ Redis
    await sleep(50);
    return getNewsWithLock(key);
  }
}

Giải pháp 2: Thư viện Singleflight (Kỳ diệu trong Golang)#

Thư viện golang.org/x/sync/singleflight triệt tiêu hoàn toàn bài toán này: Nếu có 1.000 goroutine cùng gọi hàm lấy dữ liệu của cùng 1 key, singleflight chỉ thực thi hàm đó đúng 1 lần duy nhất và chia sẻ chung kết quả đó cho cả 1.000 caller!

Giải pháp 3: Tự động làm mới trước khi hết hạn (Probabilistic Early Expiration - XFetch)#

Sử dụng thuật toán xác suất để một vài request chủ động tính toán lại cache từ trước khi TTL chạm mốc 0.

Góc thảo luận#

Hệ thống caching của bạn đã có cơ chế phòng chống Cache Stampede chưa? Bạn chuộng dùng giải pháp Mutex hay Singleflight?


Xem thêm nội dung liên quan:

Chia sẻ thảo luận này:

Thảo luận

1 phản hồi

Ý kiến từ cộng đồng kỹ sư TopDev
💬

Tham gia thảo luận

Cộng đồng mở · Đăng nhập 1-chạm qua Google
  1. Thu Trang (Backend Engineer)
    #1

    Singleflight trong Go thực sự là một kiệt tác kỹ thuật! Code chưa tới 100 dòng nhưng giải quyết triệt để 100% hiện tượng Thundering Herd mà không cần phải can thiệp khóa phức tạp trên Redis.

Bài liên quan trong #Backend & Microservices

1
TopDev AI Companion
AI
✓ Đã sao chép link