Xây dựng một cronjob phân tán với BullMQ và Redis

2 min read

Xây dựng một cronjob phân tán với BullMQ và Redis

“Cronjob là trái tim của nhiều hệ thống backend – nhưng nếu không thiết kế phân tán, nó có thể trở thành nút thắt cổ chai.”


🧭 1. Vấn đề với cronjob truyền thống

Hầu hết backend đều có một số tác vụ chạy theo lịch (cron):

  • Gửi email hàng ngày
  • Quét đơn hàng bị quá hạn
  • Đồng bộ dữ liệu với hệ thống khác

Vấn đề:

  • Chạy trên 1 server → không scale
  • Triển khai đa node dễ bị chạy trùng job (job chạy 2 lần!)
  • Không có retry, theo dõi, hoặc log rõ ràng

“Cron truyền thống rất khó kiểm soát nếu bạn chạy app dạng cluster hoặc serverless.”


🚀 2. BullMQ là gì? Vì sao phù hợp?

BullMQ là thư viện hàng đầu trong Node.js để xử lý job async, dựa trên Redis.

Ưu điểm:

  • Hỗ trợ Queue, Retry, Delay, Rate limit
  • Có UI quản lý (bull-board)
  • Dễ dùng với NestJS, Express
  • Hỗ trợ repeatable jobs → dùng làm cronjob

Redis đóng vai trò hàng đợi trung tâm → giúp phân tán job giữa các worker.


🛠️ 3. Cài đặt BullMQ

npm install bullmq ioredis

Tạo một file queue đơn giản:

// jobs/cron.job.ts
import { Queue } from 'bullmq';
import { connection } from './redis';

export const cronQueue = new Queue('cron-job', { connection });

⏰ 4. Tạo cronjob phân tán bằng repeatable job

// schedule job chạy mỗi 1 phút
await cronQueue.add('daily-task', {}, {
  repeat: { cron: '*/1 * * * *' },
  removeOnComplete: true,
});

Trong worker:

// workers/cron.worker.ts
import { Worker } from 'bullmq';
import { connection } from './redis';

new Worker('cron-job', async job => {
  if (job.name === 'daily-task') {
    console.log('Đang chạy job theo lịch...');
    // Logic xử lý ở đây
  }
}, { connection });

Redis sẽ đảm bảo job chỉ chạy một lần, dù có nhiều worker cùng kết nối


Syntax của cronjob:

⚙️ 5. Cấu hình nâng cao

Tính năngCách dùng
Retry job lỗiattempts: 3 + backoff để retry khi job fail
Delay jobdelay: 60000 để trì hoãn job 1 phút
Rate limitinglimiter: { max: 10, duration: 1000 } để tránh job chạy quá nhanh
Prioritypriority: 1 để ưu tiên job quan trọng
Logging jobKết hợp log + Bull Board UI để theo dõi trạng thái job dễ dàng

🔒 6. Đảm bảo job không chạy trùng

Với BullMQ, job có thể bị lặp nếu:

  • Worker bị restart giữa chừng
  • Redis bị flush mất dữ liệu

Mẹo:

  • Dùng jobId cố định để tránh tạo trùng
  • Luôn cấu hình removeOnComplete: true
  • Dùng lock (hoặc TTL key Redis) cho các job quan trọng

📈 7. Monitoring với Bull Board

npm install @bull-board/api @bull-board/express

Tích hợp UI vào hệ thống:

import { ExpressAdapter } from '@bull-board/express';
import { createBullBoard } from '@bull-board/api';

const serverAdapter = new ExpressAdapter();
createBullBoard({
  queues: [new BullAdapter(cronQueue)],
  serverAdapter,
});

app.use('/admin/queues', serverAdapter.getRouter());

Bạn sẽ có UI để theo dõi queue, job đang chạy, đã hoàn thành hay lỗi


🧪 8. So sánh với các giải pháp cron khác

Công cụƯu điểmHạn chế
Node-CronĐơn giản, dễ dùngKhông phân tán, dễ chạy trùng
AgendaHỗ trợ MongoDBÍt phổ biến, ít plugin
BullMQRedis-based, hỗ trợ queue phân tánPhụ thuộc Redis
TemporalWorkflow rất mạnh, có retry, event…Cần thời gian học, nặng đô

🎯 Kết luận

BullMQ + Redis là lựa chọn lý tưởng để xây dựng cronjob phân tán trong Node.js:

  • Không lo chạy trùng
  • Có retry, delay, log
  • Dễ scale worker theo tải
  • Dễ monitor với UI Bull Board

“Một hệ thống bền vững là hệ thống có job chạy đúng lúc, đúng nơi, và không chạy… hai lần.”

Avatar photo

Dựng front-end bằng Clean Architecture

Khi nhắc đến Clean Architecture, nhiều người thường chỉ nghĩ đến backend – nơi cần cấu trúc rõ ràng để quản lý nghiệp vụ...
Avatar photo Toan Nguyen Thai
5 min read

Leave a Reply

Your email address will not be published. Required fields are marked *