Series Chinh phục Heroku | Bài 4: Worker Dyno & Redis - Giải phóng Web API với Background Jobs
Sự khác biệt giữa Web Dyno và Worker Dyno
- Web Dyno: Được Heroku cấp cho một cổng (Port) để lắng nghe các request HTTP từ bên ngoài (Internet).
- Worker Dyno: Chạy ngầm trong hệ thống, không mở cổng HTTP, không ai truy cập được từ bên ngoài. Nhiệm vụ duy nhất của nó là kết nối vào Database/Redis, chờ có việc thì xử lý.
Triển khai kiến trúc Queue với Node.js & Bull
Cài đặt Heroku Redis
Giống như cài Postgres ở bài trước, bạn chỉ cần gõ một lệnh để tích hợp Redis vào ứng dụng.
heroku addons:create heroku-redis:mini
Ngay lập tức, Heroku tạo một server Redis ảo và tự động gắn biến môi trường REDIS_URL vào Config Vars của bạn.
Viết code chia việc (Producer & Consumer)
Dùng thư viện Bull để tạo Queue
Trong dự án Node.js, cài đặt thư viện bull (công cụ quản lý Queue mạnh mẽ và phổ biến nhất của Node).
npm install bull
Tạo một file mới tên là worker.js. Đây sẽ là bộ não của Worker Dyno:
// file: worker.js
const Queue = require('bull');
// Kết nối vào Redis thông qua biến môi trường của Heroku
const ticketLogQueue = new Queue('ticket-logs', process.env.REDIS_URL);
console.log("Worker đang chờ xử lý log giao dịch...");
// Worker sẽ chực chờ ở đây. Khi có Job bay vào, nó sẽ chạy hàm này:
ticketLogQueue.process(async (job) => {
console.log(`Đang xử lý file log từ trạm: ${job.data.stationId}`);
// Giả lập tác vụ nặng (bóc tách dữ liệu mất 3 giây)
await new Promise(resolve => setTimeout(resolve, 3000));
console.log(`Hoàn thành file log của trạm: ${job.data.stationId}`);
return true;
});
Tiếp theo, sửa lại file API (index.js - Web Dyno) để ném việc vào Queue:
// file: index.js
const express = require('express');
const Queue = require('bull');
const app = express();
app.use(express.json());
const ticketLogQueue = new Queue('ticket-logs', process.env.REDIS_URL);
// API nhận log từ máy bán vé
app.post('/api/upload-log', async (req, res) => {
const { stationId, fileUrl } = req.body;
// Ném việc vào Queue và không cần chờ xử lý xong
await ticketLogQueue.add({ stationId, fileUrl });
// Phản hồi ngay lập tức cho máy bán vé
res.status(200).json({ message: "Đã nhận file, hệ thống đang xử lý ngầm." });
});
app.listen(process.env.PORT || 3000, () => console.log('Web API đã chạy'));
Cập nhật bản đồ Procfile
Mở file Procfile ra. Hiện tại nó chỉ có 1 dòng dành cho Web. Bây giờ hãy khai báo thêm cho Worker:
web: node index.js
worker: node worker.js
Dòng thứ 2 báo cho Heroku biết: "Nếu tôi bật Worker Dyno, hãy chạy file worker.js.
Deploy và kích hoạt Worker
Push code lên Heroku:
git add .
git commit -m "Thêm kiến trúc Redis Queue xử lý log"
git push heroku main
Lưu ý cực kỳ quan trọng: Theo mặc định, Heroku chỉ tự bật web dyno (số lượng = 1) và tắt worker dyno (số lượng = 0) để tiết kiệm tiền cho bạn. Bạn phải tự tay kích hoạt Worker bằng lệnh:
heroku ps:scale worker=1
Kiểm tra hệ thống thực chiến
Bây giờ, nếu bạn dùng Postman bắn API POST /api/upload-log, API sẽ phản hồi thành công trong vỏn vẹn vài mili-giây.
Đồng thời, nếu bạn gõ lệnh xem log:
heroku logs --tail
Bạn sẽ thấy 2 luồng log chạy song song. Luồng của app[web.1] báo đã nhận request, và luồng của app[worker.1] báo đang xử lý file log. Quá trình xử lý nặng nề đã hoàn toàn được tách rời khỏi API chính!
Tư duy Scale (Mở rộng): Ngày lễ Tết, hàng chục trạm Metro quá tải, Redis Queue của bạn phình to lên 50.000 jobs. Bạn chỉ cần gõ nhẹ lệnh heroku ps:scale worker=5. Ngay lập tức, 5 container Worker ảo sẽ nhảy vào "cắn" job từ Redis ra xử lý song song, tốc độ tăng gấp 5 lần. Qua lễ, bạn gõ worker=1 để giảm chi phí.
All rights reserved