0

Series Redis Sentinel & Node.js Thực chiến #3: Mã hóa "Kẻ gác đền" – Không bao giờ rớt kết nối

1. Cuộc chiến thư viện: redis vs ioredis

Trong hệ sinh thái Node.js, khi nhắc đến Redis, nhiều người sẽ cài ngay thư viện mặc định bằng lệnh npm install redis. Tuy nhiên, nếu bạn đang xây dựng một hệ thống Enterprise với kiến trúc Sentinel hoặc Cluster, lời khuyên xương máu từ các kỹ sư hệ thống là: Hãy chuyển sang dùng ioredis.

  • redis (đặc biệt là các bản v3 trở về trước) hỗ trợ Sentinel khá rườm rà và việc xử lý kết nối lại (reconnect) đôi khi không mượt mà.
  • ioredis được sinh ra với mục tiêu tối thượng là phục vụ môi trường Enterprise. Nó hỗ trợ Sentinel và Cluster out-of-the-box (tích hợp sẵn, cấu hình cực kỳ đơn giản), tự động xử lý Failover một cách hoàn hảo.

Cài đặt vũ khí vào project Node.js của bạn:

npm install ioredis

2. Định tuyến kết nối: Đừng bao giờ hardcode IP của Master

Trong kiến trúc Single Node (1 server), file cấu hình .env của bạn thường có dạng: REDIS_HOST=192.168.1.10.

Nhưng với Sentinel, địa chỉ của Master là Động (Dynamic). Hôm nay nó là 192.168.1.10, nhưng đêm nay server cháy, Sentinel sẽ tự động đổi Master sang con Replica có IP 192.168.1.11. Nếu bạn hardcode IP, Node.js sẽ lăn ra chết.

Thay vì kết nối vào Master, chúng ta sẽ cấu hình Node.js kết nối thẳng vào danh sách các Kẻ gác đền (Sentinels).

Dưới đây là đoạn code chuẩn mực khởi tạo kết nối:

const Redis = require('ioredis');

// Khai báo danh sách các Sentinel thay vì IP của Master
const redis = new Redis({
  sentinels: [
    { host: '127.0.0.1', port: 26379 }, // Sentinel 1
    { host: '127.0.0.1', port: 26380 }, // Sentinel 2
    { host: '127.0.0.1', port: 26381 }  // Sentinel 3
  ],
  name: 'mymaster', // Tên cụm Master đã định nghĩa trong file sentinel.conf
  
  // Cấu hình Reconnect chiến lược
  retryStrategy: (times) => {
    const delay = Math.min(times * 50, 2000);
    return delay;
  },
});

redis.on('connect', () => {
  console.log('✅ Đã kết nối thành công tới Redis thông qua Sentinel!');
});

redis.on('error', (err) => {
  console.error('❌ Lỗi kết nối Redis:', err.message);
});

Điều gì đang xảy ra bên dưới dòng code này?

  1. Thư viện ioredis sẽ gõ cửa Sentinel 1.
  2. Nó hỏi: "Chào anh bảo vệ, cụm mymaster hiện tại đang do con Redis nào làm Master vậy?"
  3. Sentinel trả về IP và Port của con Master đương nhiệm.
  4. ioredis lấy IP đó và tự động thiết lập kết nối để ứng dụng của bạn bắt đầu đọc/ghi dữ liệu.

3. Bài Test Thực Chiến: Đứt gãy và Phục hồi (Chaos Engineering in Code)

Để chứng minh hệ thống AFC của bạn thực sự "bất tử", chúng ta sẽ viết một đoạn script liên tục ghi dữ liệu vào Redis mỗi giây.

// Giả lập luồng tap-in (quẹt thẻ vào ga) của hệ thống AFC
let transactionCount = 0;

setInterval(async () => {
  transactionCount++;
  try {
    const key = `afc:tap_in:txn_${transactionCount}`;
    await redis.set(key, 'SUCCESS', 'EX', 60); // Ghi và set expire 60s
    console.log(`[Transaction ${transactionCount}] Ghi thành công vào Redis Master.`);
  } catch (error) {
    console.error(`[Transaction ${transactionCount}] THẤT BẠI - Hệ thống đang có sự cố!`);
  }
}, 1000);

Bây giờ, bạn hãy chạy file Node.js này. Trên Terminal, bạn sẽ thấy log báo ghi thành công liên tục:

[Transaction 1] Ghi thành công vào Redis Master.
[Transaction 2] Ghi thành công vào Redis Master.

Khoảnh khắc tử thần: Mở một Terminal khác, bạn gõ lệnh tắt thẳng con Master (như đã làm ở Bài 2):

docker stop redis-master

Hãy quay lại màn hình log của Node.js và quan sát điều kỳ diệu:

  1. Bạn sẽ thấy log quăng lỗi màu đỏ (khoảng 3-5 giây) do Master đã sập. Code quẹt thẻ sẽ tạm thời văng THẤT BẠI.
  2. Nhưng ngay sau lưng, cụm Sentinel đang "họp khẩn cấp" để bầu Master mới.
  3. Ngay khi Sentinel chốt xong IP của Master mới, nó bắn tín hiệu (Pub/Sub) cho thư viện ioredis.
  4. ioredis tự động ngắt kết nối cũ, nối thẳng sang con Master mới (trước đó là Replica).
  5. Màn hình Node.js tự động xanh trở lại: [Transaction 8] Ghi thành công vào Redis Master.

Toàn bộ quá trình từ lúc sập server đến lúc phục hồi diễn ra chỉ trong vài giây, hoàn toàn tự động, không cần Kỹ sư phải thức dậy SSH vào server sửa code hay restart lại ứng dụng Node.js!

4. Lưu ý sống còn cho Backend Engineer

Kiến trúc này đã bảo vệ bạn khỏi sự cố sập phần cứng, nhưng khi code thực tế, bạn phải xử lý bài toán "Khoảng hở 5 giây" (thời gian Sentinel bầu chọn Master mới).

Trong 5 giây đó, các hàm redis.set() sẽ bị block hoặc quăng lỗi. Tại các hệ thống giao dịch vé nhạy cảm, bạn phải bắt khối try...catch ở mọi thao tác Redis. Khi lỗi kết nối xảy ra trong khoảng hở này, ứng dụng phải có cơ chế Fallback:

  • Tạm thời ghi log ra ổ cứng máy chủ (File system).
  • Hoặc đẩy vào một Message Queue nội bộ.
  • Tránh tuyệt đối việc dội trực tiếp (bypass) hàng ngàn request đang bị kẹt thẳng vào Database chính như Vitess, vì điều đó sẽ gây ra hiệu ứng domino sập toàn bộ hệ thống.

Lời kết Series

Chúc mừng bạn! Qua 3 bài học ngắn gọn nhưng cực kỳ thực chiến, bạn đã rũ bỏ hoàn toàn kiến trúc Single Node mỏng manh.

Bằng việc kết hợp kiến thức DevOps (Docker, Sentinel) và tư duy Lập trình (Node.js, ioredis), bạn đã xây dựng thành công một cơ chế Cache và Rate-Limit đạt chuẩn Enterprise. Các trạm Metro giờ đây có thể yên tâm hoạt động 24/7. Cứ lưu lại đoạn code mẫu và cấu trúc Docker này, nó sẽ là "bảo bối" cứu cánh cho bạn ở bất kỳ dự án Backend chịu tải cao nào trong tương lai. Code cứng tay và hệ thống luôn vững vàng nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí