0

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

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í