0

[Series ioredis - Bài 3] Giao Tiếp Giữa Các Service Bằng Kiến Trúc Event-Driven (Redis Pub/Sub)

Chào các bạn! Trong hành trình xây dựng Backend, sẽ có lúc hệ thống của bạn phình to ra và được chia nhỏ thành nhiều service khác nhau (Microservices). Lúc này, các service cần phải "nói chuyện" với nhau.

Khi nhắc đến Event-Driven Architecture (Kiến trúc hướng sự kiện) hoặc Message Broker, chúng ta thường nghĩ ngay đến những cái tên hạng nặng như Kafka hay RabbitMQ. Tuy nhiên, nếu bạn cần một cơ chế phát - nhận thông điệp (publish/subscribe) siêu tốc, lightweight (nhẹ) và không yêu cầu thiết lập phức tạp, Redis Pub/Sub chính là "chân ái".

Hôm nay, chúng ta sẽ dùng ioredis để hiện thực hóa cơ chế Pub/Sub này.

1. Cơ chế Pub/Sub trong Redis hoạt động thế nào?

Pub/Sub (Publisher/Subscriber) hoạt động dựa trên khái niệm Channel (Kênh).

  • Publisher: Người gửi tin nhắn vào một channel cụ thể.
  • Subscriber: Người đăng ký lắng nghe một (hoặc nhiều) channel. Khi có tin nhắn mới, Subscriber sẽ nhận được ngay lập tức.

Đặc điểm cốt lõi: Redis Pub/Sub mang tính chất Fire-and-Forget (Bắn và Quên). Nếu một tin nhắn được gửi đi mà không có ai đang lắng nghe (không có subscriber nào online), tin nhắn đó sẽ bốc hơi mãi mãi. Nó không lưu trữ lại dữ liệu như Kafka.

2. Triển khai Pub/Sub với ioredis

Để thấy rõ cách hoạt động, hãy tưởng tượng chúng ta đang vận hành một hệ thống bán vé tự động AFC (Automatic Fare Collection) tại nhà ga. Khi hành khách quẹt vé qua cổng (Gate), cổng kiểm soát sẽ phát ra một sự kiện. Một service khác (như Dashboard giám sát thời gian thực) sẽ lắng nghe sự kiện này.

Chúng ta sẽ cần 2 kết nối Redis riêng biệt: một cho Subscriber và một cho Publisher.

Bước 1: Tạo Subscriber (Lắng nghe sự kiện)

// file: subscriber.js
const Redis = require("ioredis");

// Khởi tạo một client dành riêng cho việc subscribe
const subscriber = new Redis();

const CHANNEL_NAME = "afc:gate_events";

async function startSubscriber() {
  // Đăng ký lắng nghe channel
  await subscriber.subscribe(CHANNEL_NAME, (err, count) => {
    if (err) {
      console.error("Lỗi khi subscribe:", err);
      return;
    }
    console.log(`Đã subscribe thành công. Đang lắng nghe ${count} channel(s).`);
  });

  // Xử lý khi có tin nhắn mới tới
  subscriber.on("message", (channel, message) => {
    if (channel === CHANNEL_NAME) {
      const eventData = JSON.parse(message);
      console.log(`[Dashboard] 🚦 Nhận sự kiện từ cổng ga:`, eventData);
      // Thực hiện logic update giao diện, ghi log...
    }
  });
}

startSubscriber();

Bước 2: Tạo Publisher (Phát sự kiện)

// file: publisher.js
const Redis = require("ioredis");

// Khởi tạo một client khác để publish
const publisher = new Redis();
const CHANNEL_NAME = "afc:gate_events";

async function simulateGateEvent() {
  const passengerEvent = {
    station: "Ben Thanh",
    gateId: "GATE_05",
    cardId: "CARD_998877",
    action: "tap_in",
    timestamp: Date.now()
  };

  // Publish tin nhắn (phải chuyển object thành chuỗi JSON)
  const subscribersCount = await publisher.publish(CHANNEL_NAME, JSON.stringify(passengerEvent));
  
  console.log(`Đã phát sự kiện. Có ${subscribersCount} service đã nhận được tin nhắn.`);
  
  // Đóng kết nối
  publisher.quit();
}

// Giả lập một hành khách quẹt thẻ
setTimeout(simulateGateEvent, 2000);

Cách chạy thử nghiệm: Bạn hãy mở 2 tab Terminal. Tab 1 chạy node subscriber.js. Sau đó, mở Tab 2 chạy node publisher.js. Bạn sẽ thấy thông điệp được truyền đi ngay lập tức với độ trễ gần như bằng 0.

3. Những lưu ý "sống còn" khi dùng Pub/Sub bằng ioredis

  1. Client độc quyền (Exclusive Client): Khi một client ioredis đã gọi lệnh subscribe(), nó sẽ chuyển sang chế độ subscriber mode. Ở chế độ này, bạn không thể dùng client đó để gọi các lệnh Redis thông thường khác (như GET, SET). Đó là lý do chúng ta phải tạo 2 instance new Redis() riêng biệt cho Publisher và Subscriber.
  2. Pattern Subscription (PSUBSCRIBE): Nếu hệ thống ga có nhiều cổng (afc:gate:1, afc:gate:2), bạn có thể dùng lệnh psubscribe('afc:gate:*') để lắng nghe tất cả các kênh bắt đầu bằng tiền tố đó.

Tổng kết Bài 3

Qua bài này, chúng ta đã nắm được cách sử dụng Redis Pub/Sub để kết nối các phần khác nhau của hệ thống một cách lỏng lẻo (loosely coupled) nhưng cực kỳ nhanh chóng. Mặc dù không thay thế được các Message Broker hạng nặng trong những bài toán cần đảm bảo tính toàn vẹn và lưu trữ dữ liệu bền vững, Redis Pub/Sub vẫn là mảnh ghép hoàn hảo cho các tính năng realtime như chat, notification hay live dashboard.

Ở bài viết tới, chúng ta sẽ tiến sâu hơn vào kiến trúc hệ thống: Làm sao để ứng dụng Node.js của chúng ta vẫn sống sót khi server Redis đột ngột bốc cháy? Cùng chờ đón phần về Redis Sentinel và Cluster 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í