← Về danh sách bài học Bài 2/20

📨 Bài 2: Message Queue Là Gì?

⏱️ Thời gian đọc: 20 phút | 📚 Độ khó: Cơ bản

🎯 Sau bài học này, bạn sẽ:

1. Message Queue Là Gì?

Message Queue (Hàng đợi tin nhắn) là một cơ chế cho phép các ứng dụng hoặc service giao tiếp với nhau một cách bất đồng bộ (asynchronous) thông qua việc gửi và nhận các message (tin nhắn).

Hãy tưởng tượng Message Queue giống như một hòm thư: người gửi (Producer) bỏ thư vào hòm, và người nhận (Consumer) lấy thư ra khi có thể. Người gửi không cần đợi người nhận đọc thư.

KHÔNG CÓ Message Queue (Đồng bộ):
┌──────────┐  HTTP Request   ┌──────────┐
│ Service A │───────────────▶│ Service B │
│ (đợi...) │◀───────────────│ (xử lý)  │
└──────────┘  HTTP Response  └──────────┘
→ Service A bị BLOCK cho đến khi B xử lý xong!

CÓ Message Queue (Bất đồng bộ):
┌──────────┐   Gửi msg   ┌─────────┐   Nhận msg   ┌──────────┐
│ Service A │────────────▶│  Queue  │─────────────▶│ Service B │
│ (tiếp tục│              │ 📨📨📨  │              │ (xử lý)  │
│  làm việc)│              └─────────┘              └──────────┘
└──────────┘
→ Service A gửi xong là TIẾP TỤC làm việc khác!

2. Tại Sao Cần Message Queue?

📌 Vấn đề 1: Tight Coupling (Phụ thuộc chặt)

Khi Service A gọi trực tiếp Service B, nếu B bị down → A cũng bị ảnh hưởng. Message Queue giúp giảm phụ thuộc (decouple) — A chỉ cần gửi message vào queue, không cần biết B có đang hoạt động hay không.

📌 Vấn đề 2: Traffic Spike (Đột biến lưu lượng)

Khi có flash sale, hàng triệu request đến cùng lúc. Nếu xử lý trực tiếp, server sẽ quá tải. Message Queue đóng vai trò buffer, giúp consumer xử lý từ từ theo tốc độ của mình.

📌 Vấn đề 3: Long-running Tasks

Các tác vụ như gửi email, resize hình ảnh, xử lý video mất nhiều thời gian. Thay vì bắt user đợi, đẩy vào queue và xử lý ở background.

💡 Ví dụ thực tế:
Khi bạn đặt hàng trên Shopee:
1. API nhận order → trả về "Đặt hàng thành công" (nhanh)
2. Gửi message vào Queue: "Xử lý đơn hàng #123"
3. Background service: Trừ kho, tính phí ship, gửi email xác nhận...
→ User không cần đợi tất cả bước hoàn thành!

3. Các Khái Niệm Cốt Lõi

Khái niệm Mô tả Ví dụ
Message Đơn vị dữ liệu được truyền qua queue JSON: {"orderId": 123, "action": "process"}
Producer Ứng dụng gửi message vào queue Order Service gửi message "đơn hàng mới"
Consumer Ứng dụng nhận và xử lý message từ queue Email Service nhận message và gửi email
Queue Hàng đợi lưu trữ message theo FIFO order-queue, email-queue
Broker Server quản lý queue (middleware) RabbitMQ, Kafka, Redis
ACK Consumer xác nhận đã xử lý xong message Gửi ACK → broker xóa message khỏi queue

4. Synchronous vs Asynchronous

🔄 Synchronous (Đồng bộ)

// Đồng bộ: Client phải ĐỢI mỗi bước hoàn thành
app.post('/order', async (req, res) => {
    // Bước 1: Lưu đơn hàng (200ms)
    const order = await saveOrder(req.body);

    // Bước 2: Trừ kho (300ms)
    await updateInventory(order.items);

    // Bước 3: Tính phí ship (500ms)
    await calculateShipping(order);

    // Bước 4: Gửi email (2000ms)
    await sendConfirmationEmail(order);

    // Tổng: ~3000ms → User phải đợi 3 giây!
    res.json({ status: 'success', orderId: order.id });
});

⚡ Asynchronous với Message Queue

// Bất đồng bộ: Chỉ lưu order + gửi vào queue
app.post('/order', async (req, res) => {
    // Bước 1: Lưu đơn hàng (200ms)
    const order = await saveOrder(req.body);

    // Bước 2: Gửi message vào queue (5ms)
    await messageQueue.publish('order-processing', {
        orderId: order.id,
        items: order.items,
        userId: req.user.id
    });

    // Tổng: ~205ms → User nhận response ngay!
    res.json({ status: 'success', orderId: order.id });
});

// Consumer 1: Xử lý kho (chạy background)
messageQueue.consume('order-processing', async (msg) => {
    await updateInventory(msg.items);
    await messageQueue.publish('shipping-calc', msg);
});

// Consumer 2: Tính ship (chạy background)
messageQueue.consume('shipping-calc', async (msg) => {
    await calculateShipping(msg);
    await messageQueue.publish('send-email', msg);
});

// Consumer 3: Gửi email (chạy background)
messageQueue.consume('send-email', async (msg) => {
    await sendConfirmationEmail(msg);
});
⚠️ Trade-off:
Message Queue giúp response nhanh hơn nhưng tăng complexity (phức tạp). Bạn cần xử lý: message bị lỗi, message trùng lặp, đảm bảo thứ tự, monitoring, v.v.

5. Use Cases Thực Tế

📧 1. Gửi Email/SMS thông báo

Khi user đăng ký → Gửi message "welcome email" vào queue → Email service xử lý background.

🖼️ 2. Xử lý hình ảnh/video

User upload ảnh → Gửi vào queue → Worker resize, nén, tạo thumbnail ở background.

📊 3. Analytics & Logging

Mỗi page view, click → Gửi event vào Kafka → Analytics service tổng hợp realtime.

💳 4. Xử lý thanh toán

Request thanh toán → Queue → Payment service xử lý tuần tự, tránh race condition.

🔄 5. Đồng bộ dữ liệu giữa services

Khi user update profile → Publish event → Tất cả services liên quan subscribe và cập nhật.

📌 Quy tắc chung:
Nếu task mất hơn 100ms và user không cần kết quả ngay → nên dùng Message Queue!

6. Hello World: Pseudo Message Queue

Để hiểu nguyên lý, hãy xây dựng một Message Queue đơn giản nhất bằng JavaScript:

// Pseudo Message Queue (để hiểu nguyên lý)
class SimpleQueue {
    constructor() {
        this.queues = {};    // Lưu trữ messages
        this.consumers = {}; // Lưu trữ consumers
    }

    // Producer gửi message
    publish(queueName, message) {
        if (!this.queues[queueName]) {
            this.queues[queueName] = [];
        }
        this.queues[queueName].push(message);
        console.log(`📨 Sent to [${queueName}]:`, message);
        this._processQueue(queueName);
    }

    // Consumer đăng ký nhận message
    subscribe(queueName, handler) {
        this.consumers[queueName] = handler;
        console.log(`👂 Subscribed to [${queueName}]`);
    }

    // Xử lý queue
    async _processQueue(queueName) {
        const handler = this.consumers[queueName];
        if (!handler) return;

        while (this.queues[queueName]?.length > 0) {
            const msg = this.queues[queueName].shift(); // FIFO
            try {
                await handler(msg);
                console.log(`✅ ACK [${queueName}]`);
            } catch (err) {
                console.log(`❌ NACK [${queueName}]:`, err.message);
                // Trong thực tế: retry hoặc đưa vào dead letter queue
            }
        }
    }
}

// Sử dụng
const mq = new SimpleQueue();

// Consumer đăng ký
mq.subscribe('email-queue', async (msg) => {
    console.log(`📧 Sending email to: ${msg.to}`);
    // Giả lập gửi email
    await new Promise(r => setTimeout(r, 1000));
});

// Producer gửi messages
mq.publish('email-queue', { to: 'user@gmail.com', subject: 'Welcome!' });
mq.publish('email-queue', { to: 'admin@gmail.com', subject: 'New user' });
💡 Lưu ý: Đây chỉ là pseudo code để hiểu nguyên lý. Trong thực tế, bạn cần dùng broker chuyên dụng (RabbitMQ, Kafka) để đảm bảo: message persistence, clustering, retry, monitoring, v.v.

📝 Tóm Tắt Bài Học