[Series Thực Chiến] Chinh phục Queue - Phần 3: Đưa Job vào Queue - Khi nào chạy ngay, khi nào đợi?
Chào anh em, rất vui được gặp lại mọi người trong series "Chinh phục Queue".
Ở bài trước, chúng ta đã học cách "đóng gói" một công việc vào trong một Job Class. Đó là một bước tiến lớn giúp code của bạn sạch sẽ và chuyên nghiệp hơn. Nhưng có một câu hỏi đặt ra: Làm sao để cái Object đó bay từ RAM của ứng dụng vào trong database hoặc Redis, rồi nằm chờ ở đó cho đến khi có người đến xử lý?
Hôm nay, chúng ta sẽ cùng giải mã quá trình "bắn" Job vào hàng đợi, cách điều khiển thời gian thực thi và lắng nghe những gì xảy ra với Job đó.
1. Cơ chế Dispatching: Phía sau bức màn
Khi bạn gọi lệnh Queue::push(new SendEmailJob()) (trong Laravel) hay queue.add(new SendEmailJob()) (trong Node.js), hệ thống không chỉ đơn giản là ném cái Object đó đi. Nó thực hiện một quá trình gọi là Serialization.
Vì Redis hay Database không hiểu được "Class" hay "Object" của ngôn ngữ lập trình, hệ thống sẽ chuyển toàn bộ dữ liệu của Job Class đó thành một chuỗi JSON.
Ví dụ: Cái Job SendWelcomeEmailJob của bạn sẽ được "biến hình" thành một bản ghi như thế này trong Queue:
{
"job": "App\\Jobs\\SendWelcomeEmailJob",
"data": {
"userEmail": "senior_dev@example.com",
"userName": "Anh Em Code"
},
"attempts": 0,
"maxTries": 3
}
Khi Worker lấy Job này ra, nó sẽ dựa vào tên Class để khởi tạo lại (Unserialize) đúng Object đó và gọi hàm handle().
2. Trì hoãn Jobs (Delayed Jobs) - Đừng làm ngay bây giờ!
Đây là tính năng "ăn tiền" nhất của Queue. Có những việc bạn không muốn thực hiện ngay lập tức.
Case study thực tế: * User vừa đăng ký tài khoản, bạn muốn gửi một email hướng dẫn sử dụng sau... 30 phút để họ không cảm thấy bị "spam" ngay tức khắc.
- User vừa hủy gói subcription, bạn muốn đợi 1 ngày sau mới gửi email khảo sát lý do.
Thay vì dùng sleep() (tối kỵ trong Backend vì nó gây block tiến trình), chúng ta dùng tính năng Delay.
// Demo với logic delay 30 phút
const job = new SendWelcomeEmailJob(user.email, user.name);
// Sử dụng phương thức delay (thời gian tính bằng giây)
await QueueDispatcher.dispatch(job, { delay: 1800 });
console.log("Job đã được lên lịch chạy sau 30 phút!");
Dưới nắp capo, hệ thống Queue sẽ lưu thêm một trường là available_at. Worker sẽ bỏ qua những Job có available_at lớn hơn thời gian hiện tại. Cực kỳ thông minh và tiết kiệm tài nguyên!
3. Job Events - Lắng nghe nhịp đập của hàng đợi
Một hệ thống Queue ổn định là hệ thống mà bạn phải biết được chuyện gì đang xảy ra với nó. Đừng để Job rơi vào "hố đen" rồi biến mất không dấu vết. Các Framework hiện đại cung cấp cho chúng ta các Hooks (Events) để can thiệp vào vòng đời của Job.
Có 3 sự kiện quan trọng nhất bạn cần nắm:
- Before: Chạy ngay trước khi Job bắt đầu xử lý (Thường dùng để log hoặc chuẩn bị môi trường).
- After (Success): Chạy khi Job hoàn thành thành công (Dùng để thông báo, cập nhật trạng thái đơn hàng).
- Failed: Chạy khi Job đã thử lại nhiều lần nhưng vẫn thất bại (Đây là lúc bạn cần bắn tin nhắn vào Slack/Telegram để báo cho team Dev vào "cứu giá").
// Ví dụ xử lý khi Job thất bại hoàn toàn
Queue.on('failed', (job, error) => {
console.error(`Cấp báo! Job ${job.name} đã tạch sau nhiều lần thử lại.`);
console.error(`Lý do: ${error.message}`);
// Gửi alert về Telegram cho dev
NotificationService.sendToTelegram(`[ALERT] Job ${job.name} fail! Check logs ngay!`);
});
Để giúp bạn hình dung rõ hơn về luồng đi của một Job từ lúc khởi tạo đến khi kết thúc, hãy xem mô phỏng tương tác dưới đây:
Để hiểu rõ hơn về cách Job di chuyển qua các trạng thái, bạn có thể tương tác với mô phỏng luồng dưới đây. Bạn sẽ thấy cách một Job được "đóng gói", trì hoãn và cách các sự kiện (Events) được kích hoạt như thế nào.
{
"component": "LlmGeneratedComponent",
"props": {
"height": "600px",
"prompt": "Hãy tạo một ứng dụng mô phỏng luồng hoạt động của Job trong Queue. Người dùng có thể nhấn nút 'Tạo Job', sau đó chọn 'Gửi ngay' hoặc 'Trì hoãn 5 giây'. Màn hình sẽ hiển thị các trạng thái: 1. Dispatching (Đang gửi), 2. In Queue (Trong hàng đợi - hiển thị countdown nếu trì hoãn), 3. Processing (Đang xử lý - kích hoạt Before Event), 4. Finished (Thành công - kích hoạt After Event). Hãy hiển thị một bảng log bên dưới để ghi lại các Event được kích hoạt (Before, After, Failed). Giao diện tối giản, trực quan theo phong cách dashboard của kỹ sư phần mềm."
}
}
Hé lộ bài tiếp theo...
Chúng ta đã biết cách đóng gói Job (Phần 2) và cách bắn Job vào hàng đợi (Phần 3). Nhưng nãy giờ, chúng ta chỉ đang nói về "đầu gửi". Ai sẽ là người đứng ra "nhận" và thực thi đống công việc đang nằm chờ trong Redis kia?
Nếu bạn chỉ Dispatch mà không có ai xử lý, hàng đợi sẽ phình to ra và làm sập server. Làm thế nào để cấu hình một con "Worker" khỏe mạnh, chạy ngầm 24/7 và biết cách tự hồi sinh khi gặp lỗi?
Tất cả sẽ có trong Phần 4: Thực thi Queue Listener - Những anh công nhân thầm lặng.
Đừng quên nhấn Upvote nếu bài viết giúp ích cho bạn nhé! Hẹn gặp lại anh em ở bài viết tiếp theo.
All rights reserved