+1

[Series Thực Chiến] Chinh phục Queue - Phần 1: Tại sao API của bạn lại chậm, và Queue là vị cứu tinh như thế nào?

Chào anh em,

Nếu bạn đã từng code một tính năng "Tạo đơn hàng", "Đăng ký user" hay "Xuất file báo cáo" và nhận ra rằng API của mình thi thoảng lại... quay đều quay đều mất vài giây, thì bài viết này là dành cho bạn.

Khi hệ thống bắt đầu phình to, lượng request tăng lên, cái cảnh người dùng phải ngồi nhìn vòng loading mòn mỏi chỉ vì hệ thống đang bận rộn đi gửi email xác nhận hay gọi sang một API của bên thứ 3 là một trải nghiệm cực kỳ tệ. Hôm nay, chúng ta sẽ mở đầu series "Chinh phục Queue" để giải quyết triệt để bài toán này.

1. Vấn đề của sự "Đồng bộ" (Synchronous)

Hãy nhìn vào đoạn code quen thuộc này khi tạo một user mới:

// Ví dụ với Node.js / Express
app.post('/api/register', async (req, res) => {
    try {
        // 1. Lưu user vào Database (mất 50ms)
        const user = await Database.createUser(req.body);

        // 2. Gửi email Welcome (mất 2000ms - 3000ms) ⚠️ TỘI ĐỒ LÀ ĐÂY
        await EmailService.sendWelcomeEmail(user.email);

        // 3. Trả về response
        return res.status(200).json({ message: "Đăng ký thành công!" });
    } catch (error) {
        return res.status(500).json({ error: "Có lỗi xảy ra" });
    }
});

Chuyện gì đang xảy ra?

Người dùng phải đợi hệ thống làm xong việc gửi email thì mới nhận được thông báo "Đăng ký thành công". Việc lưu vào DB rất nhanh, nhưng việc gọi qua SMTP server để gửi mail thì lại phụ thuộc vào mạng lưới, server bên kia... Nếu server mail bị lag, API của bạn cũng "treo" theo. Đỉnh điểm là khi có 1000 người đăng ký cùng lúc, server của bạn sẽ cạn kiệt connection và sập.

2. Queue (Hàng đợi) là gì?

Nói một cách dân dã, Queue giống như khi bạn đi mua vé tàu điện hoặc vé xem phim. Ai đến trước mua trước (FIFO - First In, First Out). Nhưng trong kiến trúc hệ thống, Queue đóng vai trò là một vùng đệm (buffer) cực kỳ quan trọng.

Thay vì bắt API phải làm hết mọi việc ngay lập tức, API chỉ làm những việc quan trọng nhất (ví dụ: Lưu DB), những việc râu ria, tốn thời gian (gửi mail, nén ảnh, push notification) sẽ được vứt vào một cái hộp tên là Queue. Và ở một nơi khác, sẽ có những anh công nhân (gọi là Worker) cứ từ từ lấy từng công việc trong hộp ra mà làm.

3. Hệ thống "lột xác" như thế nào khi có Queue? (Asynchronous)

Hãy đập đi xây lại đoạn code phía trên:

app.post('/api/register', async (req, res) => {
    try {
        // 1. Lưu user vào DB (50ms)
        const user = await Database.createUser(req.body);

        // 2. Vứt nhiệm vụ "Gửi email" vào Queue và... kệ nó! (chỉ mất 2ms)
        await Queue.push('SEND_WELCOME_EMAIL', { email: user.email, name: user.name });

        // 3. Trả về kết quả ngay lập tức cho user!
        return res.status(200).json({ message: "Đăng ký thành công! Bạn hãy kiểm tra hộp thư nhé." });
    } catch (error) {
        return res.status(500).json({ error: "Có lỗi xảy ra" });
    }
});

Lợi ích khổng lồ mang lại:

  • Tăng tốc độ phản hồi API: Người dùng không còn phải chờ đợi. Trải nghiệm mượt mà hơn hẳn.
  • Decoupling (Tách biệt hệ thống): Thằng xử lý API và thằng gửi Mail giờ đây sống độc lập. Gửi mail có lỗi cũng không làm sập tiến trình đăng ký của user.
  • Dễ dàng Scale: Black Friday, lượng người đăng ký tăng gấp 100 lần? Không sao, API vẫn trả kết quả cực nhanh. Queue sẽ dài ra một chút, và ta chỉ cần bật thêm vài chục con Worker lên để "cắn" dần đống job trong Queue là xong. Không một ai bị rớt kết nối.

4. Những hệ thống Queue phổ biến hiện nay

Tùy vào độ phức tạp, bạn có thể implement Queue bằng nhiều cách:

  • Redis (BullMQ, Laravel Horizon): Cực kỳ phổ biến, tốc độ bàn thờ nhờ chạy in-memory. Thích hợp cho 80% các bài toán thông thường.
  • RabbitMQ / ActiveMQ: Chuẩn chỉ cho các hệ thống Enterprise, nhiều tính năng routing phức tạp.
  • Kafka / Amazon SQS: Dành cho những hệ thống cực lớn, phân tán, xử lý hàng triệu message mỗi giây (chúng ta sẽ có bài deep dive về Kafka sau nhé).

Hé lộ bài tiếp theo...

Đến đây, chắc hẳn anh em đã thấy Queue là một phần không thể thiếu trong các hệ thống Backend chịu tải cao.

Nhưng hãy nhìn lại đoạn code Queue.push('SEND_WELCOME_EMAIL', { email: user.email }). Trong thực tế, công việc không chỉ đơn giản là ném một cái tên và một cục data vào hàng đợi. Sẽ thế nào nếu việc gửi mail bị lỗi mạng và ta muốn tự động thử lại (retry) 3 lần? Sẽ thế nào nếu ta muốn delay công việc này sau 10 phút mới chạy?

Lúc này, chúng ta không thể code chay bằng mảng hay object thông thường được nữa. Chúng ta cần một cấu trúc mạnh mẽ hơn, chuẩn hóa hơn để bọc các task này lại. Đó chính là khái niệm Job Classes.

Làm thế nào để tạo và thiết kế một Job Class chuẩn chỉnh? Hẹn anh em ở Phần 2: Job Classes - "Đóng gói" công việc chuyên nghiệp như một Senior. Nhớ follow để nhận thông báo bài viết mới nhất nhé! Chúc anh em code không bug!


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í