Giới thiệu
Trong các ứng dụng web hiện đại, những tác vụ nặng như gửi email hàng loạt, tạo báo cáo PDF, hoặc xử lý ảnh thường gây blocking cho luồng yêu cầu chính. Khi một request phải chờ cho những công việc này hoàn thành, thời gian phản hồi tăng lên và trải nghiệm người dùng giảm sút. Giải pháp phổ biến là di chuyển các tác vụ này sang hàng đợi công việc nền (background jobs) để server có thể trả về kết quả ngay lập tức, trong khi công việc được thực thi bất đồng bộ ở một tiến trình riêng.
Tại sao nên dùng hàng đợi (Queue)
Hàng đợi cung cấp các tính năng quan trọng:
- Retry tự động khi công việc thất bại.
- Backoff để giảm tải khi có lỗi tạm thời.
- Concurrency cho phép chạy nhiều job đồng thời trên nhiều worker.
- Persist dữ liệu job trong Redis, giúp phục hồi sau khi server khởi động lại.
Trong môi trường Node.js, BullMQ là thư viện mạnh mẽ, dựa trên Redis, hỗ trợ đầy đủ các tính năng trên và dễ tích hợp với Express.
Cài đặt BullMQ và Redis
Đầu tiên, cần cài đặt Redis trên máy hoặc sử dụng dịch vụ đám mây. Sau đó, cài các package Node.js:
npm install bullmq ioredis
Tạo một Queue đơn giản
Khởi tạo một queue và thêm một job mẫu:
const { Queue } = require('bullmq');
const emailQueue = new Queue('email', {
connection: { host: '127.0.0.1', port: 6379 }
});
await emailQueue.add('send-email', {
to: '[email protected]',
subject: 'Chào mừng',
body: 'Cảm ơn bạn đã đăng ký!'
});Job trên sẽ được lưu trong Redis và chờ worker tiêu thụ.
Viết Worker để xử lý công việc
Worker là tiến trình chịu trách nhiệm lấy job từ queue và thực thi logic nghiệp vụ. Dưới đây là một worker cơ bản, bao gồm cấu hình concurrency và retry:
const { Worker, QueueScheduler } = require('bullmq');
// Scheduler cần thiết để quản lý delayed jobs và retries
new QueueScheduler('email', { connection: { host: '127.0.0.1', port: 6379 } });
const worker = new Worker('email', async job => {
// Giả sử hàm sendMail đã được định nghĩa ở nơi khác
await sendMail(job.data);
}, {
connection: { host: '127.0.0.1', port: 6379 },
concurrency: 5, // Xử lý tối đa 5 job đồng thời
// Tùy chọn tự động retry khi job thất bại
settings: {
retryProcessDelay: 3000 // Đợi 3s trước khi thử lại
}
});
worker.on('completed', job => {
console.log(`Job ${job.id} hoàn thành`);
});
worker.on('failed', (job, err) => {
console.error(`Job ${job.id} thất bại: ${err.message}`);
});Thêm tùy chọn backoff và attempts
Khi thêm job, bạn có thể chỉ định số lần thử lại và chiến lược backoff:
await emailQueue.add('send-email', data, {
attempts: 3, // Tối đa 3 lần thử
backoff: { type: 'exponential', delay: 5000 } // Tăng dần thời gian chờ
});Mở rộng quy mô với nhiều worker
Đối với tải lớn, bạn có thể khởi chạy nhiều tiến trình worker trên các máy khác nhau, chỉ cần chúng cùng kết nối tới Redis. Redis sẽ tự động phân phối job dựa trên concurrency và priority (nếu được thiết lập).
Giám sát Queue bằng Bull Board
Để theo dõi trạng thái các job, Bull Board cung cấp giao diện web đơn giản:
npm install @taskforcesh/bull-board
Sau khi cài, tích hợp vào Express:
const { createBullBoard } = require('@taskforcesh/bull-board');
const { ExpressAdapter } = require('@taskforcesh/bull-board/express');
const serverAdapter = new ExpressAdapter();
createBullBoard({ queues: [new BullMQAdapter(emailQueue)], serverAdapter });
app.use('/admin/queues', serverAdapter.getRouter());Giao diện này cho phép xem số lượng job đang chờ, đang chạy, đã hoàn thành và thất bại, đồng thời có thể retry thủ công.
Best practices
- Đừng lưu dữ liệu lớn trong payload: Chỉ truyền ID và tham số cần thiết, dữ liệu thực tế nên truy vấn từ DB trong worker.
- Sử dụng
QueueSchedulerluôn luôn để hỗ trợ delayed jobs và retries. - Giới hạn thời gian job bằng
timeoutđể tránh worker bị kẹt. - Graceful shutdown: Khi dừng server, gọi
worker.close()vàemailQueue.close()để hoàn thành các job đang chạy.
Kết luận
Việc chuyển các tác vụ nặng sang BullMQ + Redis giúp giảm tải cho event loop, cải thiện thời gian phản hồi dưới 100ms và tăng khả năng mở rộng của ứng dụng Node.js. Khi đã nắm vững các khái niệm trên, bạn có thể xây dựng hệ thống microservice mạnh mẽ, hỗ trợ hàng ngàn job mỗi giây.
Để nâng cao kiến thức và thực hành trên các dự án thực tế, Tham khảo khóa học "Lập trình Back-End với NodeJS Express" tại đây.








