Series Docker & Redis cho Laravel #3: Hàng đợi Queue và "Mắt thần" Laravel Horizon (Trạm cuối)
1. Tư duy Bất đồng bộ (Asynchronous)
Thay vì bắt luồng chính của API phải đứng chờ kết quả từ các tác vụ nặng (gọi API bên thứ 3, gửi email, xuất file báo cáo Excel), chúng ta sẽ đóng gói tác vụ đó thành một Job và ném nó vào hàng đợi (Queue) của Redis.
API luồng chính coi như "xong việc" và lập tức trả về kết quả cho máy quẹt thẻ. Ở phía sau (Background), các Worker sẽ lôi Job ra xử lý một cách âm thầm.
use App\Jobs\SyncTransactionToHitachi;
public function processTapIn(Request $request)
{
$transaction = Transaction::create($request->all());
// ❌ Cách làm Junior: Gọi trực tiếp Service (Đợi 2 giây)
// $hitachiService->sync($transaction);
// ✅ Chuẩn Senior: Bắn Job vào Redis Queue và đi chơi (Mất 1 mili-giây)
SyncTransactionToHitachi::dispatch($transaction)->onQueue('high_priority');
// Mở cổng ngay lập tức!
return response()->json(['status' => 'success', 'open_gate' => true]);
}
2. Sự lợi hại của việc "Chia ngăn" Redis
Hãy nhớ lại ở Bài 1, chúng ta đã cấu hình file config/database.php để Cache nằm ở Database 1, còn Queue nằm ở Database 2.
Tại sao điều này lại sống còn? Nếu bạn dùng Database Queue (dùng chung MySQL để lưu hàng đợi), khi có 10.000 giao dịch chờ đồng bộ, MySQL sẽ bị khóa bảng (table lock) liên tục, kéo sập toàn bộ hệ thống. Nhưng nếu dùng Redis, tốc độ đọc/ghi (Push/Pop) của nó lên tới hàng trăm ngàn thao tác mỗi giây. Việc tách riêng Database 2 giúp đảm bảo rằng, dù bạn có clear toàn bộ Cache ở Database 1, thì 10.000 Job quan trọng đang xếp hàng ở Database 2 vẫn tuyệt đối an toàn và không bị bốc hơi.
3. Chiến lược Backoff: Xử lý khi đối tác "sập mạng"
Điều gì xảy ra nếu Job của bạn đang chạy ngầm thì server của Hitachi bảo trì hoặc rớt mạng? Nếu không cấu hình cẩn thận, Job sẽ bị đánh dấu là "Thất bại" (Failed) và dữ liệu giao dịch đó sẽ vĩnh viễn không được đồng bộ.
Một Senior Backend sẽ cấu hình cơ chế Exponential Backoff (Thử lại theo cấp số nhân) ngay bên trong Job:
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Exception;
class SyncTransactionToHitachi implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
protected $transaction;
// Cho phép Job thử lại tối đa 5 lần trước khi bỏ cuộc
public $tries = 5;
public function __construct($transaction)
{
$this->transaction = $transaction;
}
public function handle()
{
// Logic gọi API đồng bộ...
// Nếu HTTP status >= 500, ném ra Exception để Job biết là thất bại
}
/**
* Xác định thời gian chờ giữa các lần thử lại (Tính bằng giây)
* Lần 1 thất bại -> Chờ 10s. Lần 2 -> Chờ 30s. Lần 3 -> Chờ 60s...
*/
public function backoff()
{
return [10, 30, 60, 120, 300];
}
}
Nhờ cơ chế này, hệ thống của bạn có khả năng tự phục hồi (Resilience) cực kỳ mạnh mẽ trước những biến động hạ tầng mạng.
4. Laravel Horizon: "Mắt thần" quản lý Queue
Chạy lệnh php artisan queue:work ở background bằng Supervisor là cách làm truyền thống. Nhưng với hệ thống lớn, bạn làm sao biết được: Hiện tại có bao nhiêu Job đang xếp hàng? Mất bao lâu để xử lý một Job? Thằng Worker nào đang bị kẹt?
Đây là lúc chúng ta cài đặt Laravel Horizon - một Dashboard tuyệt đẹp được thiết kế riêng để giám sát Redis Queue.
composer require laravel/horizon
php artisan horizon:install
Sau khi cấu hình, Horizon mang lại những siêu năng lực:
- Giao diện Real-time: Truy cập /horizon, bạn sẽ thấy ngay nhịp đập của hệ thống, số lượng Job đang chạy, số lượng Job lỗi, bộ nhớ RAM tiêu thụ.
- Auto-scaling (Tự động mở rộng): Bạn có thể cấu hình Horizon tự động đẻ thêm Worker (nhân công) nếu hàng đợi dài ra. Ví dụ: Bình thường chỉ cần 1 Worker. Nhưng nếu hàng đợi high_priority vượt quá 1000 Job, Horizon tự động triệu hồi thêm 10 Worker nữa để "cày" cho nhanh, khi hết việc lại tự động giải tán để tiết kiệm CPU.
Cấu hình Auto-scaling trong config/horizon.php:
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high_priority', 'default'],
'balance' => 'auto', // Tự động cân bằng tải
'autoScalingStrategy' => 'time',
'minProcesses' => 1,
'maxProcesses' => 15, // Tối đa bung ra 15 process nếu nghẽn mạng
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
],
Tổng kết Series Docker & Redis
Trải qua 3 bài học thực chiến, chúng ta đã lột xác hoàn toàn cách sử dụng Redis trong hệ thống Laravel:
- Xây dựng môi trường Docker an toàn, không mất dữ liệu (AOF) và tối ưu đường truyền (PhpRedis).
- Xóa bỏ rủi ro quá tải Database nhờ Cache Tags, Atomic Locks và dẹp bỏ tình trạng Race Condition.
- Chuyển đổi kiến trúc sang dạng Bất đồng bộ (Async), dùng Horizon để vắt kiệt sức mạnh xử lý hàng đợi của Redis, đảm bảo hệ thống tự phục hồi kể cả khi đối tác gặp sự cố.
Với bộ công cụ này, hệ thống lõi của bạn dư sức duy trì thời gian phản hồi (Response Time) ở mức dưới 50ms, bất chấp lưu lượng giao dịch có lên tới hàng triệu request. Kiến trúc đã hoàn thiện, sẵn sàng đón nhận mọi thử thách!
All rights reserved