0

Series Docker & Beanstalkd cho Laravel #2: Vòng đời Sinh tử của Job và "Mắt thần" Console

1. Khái niệm TUBE và 4 Trạng thái Sinh tử

Trong thế giới của Redis hay Kafka, chúng ta gọi các kênh phân loại là Queue hay Topic. Trong Beanstalkd, chúng được gọi là Tube (Đường ống). Bạn có thể tạo ra ống hitachi_sync_tube riêng, và ống send_email_tube riêng.

Bên trong mỗi Tube, một Job sẽ luân chuyển qua 4 trạng thái sinh tử:

  1. Ready (Sẵn sàng): Job vừa được đẩy vào ống, đang nằm chờ Worker đến bốc đi xử lý.
  2. Reserved (Đang bị giữ): Một Worker đã bốc Job này lên và đang xử lý. Lúc này, Job bị "khóa" (ẩn đi), các Worker khác không thể nhìn thấy để tránh việc 2 người cùng trừ tiền 1 khách hàng (Race condition).
  3. Delayed (Trì hoãn): Job được yêu cầu lùi lại một thời gian rồi mới xử lý (Ví dụ: Gửi email khảo sát sau 2 tiếng hành khách quẹt thẻ). Sau thời gian Delay, nó sẽ tự động biến thành trạng thái Ready.
  4. Buried (Chôn cất): Trạng thái kinh điển của Beanstalkd! Khi một Job bị lỗi quá nhiều lần, thay vì xóa bỏ nó (làm mất dữ liệu) hoặc ném lại vào hàng chờ (làm kẹt đường ống), Beanstalkd sẽ "chôn" nó sang một bên. Nó nằm đó một cách an toàn, chờ bạn vào kiểm tra code, fix lỗi xong rồi "đào lên" (Kick) để chạy lại.

2. Laravel thao túng vòng đời Job như thế nào?

Thật tuyệt vời là Laravel đã bọc toàn bộ những trạng thái phức tạp này thành những hàm cực kỳ dễ sử dụng trong Class Job.

Giả sử chúng ta có một Job SyncTransactionJob. Hãy xem cách một Senior thao túng vòng đời của nó:

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Exception;
use Illuminate\Support\Facades\Http;

class SyncTransactionJob implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    protected $transaction;

    // Cho phép Job chạy tối đa 3 lần trước khi bị đưa vào trạng thái BURIED
    public $tries = 3; 

    public function __construct($transaction)
    {
        $this->transaction = $transaction;
    }

    public function handle()
    {
        // Khi hàm này chạy, Job đang ở trạng thái RESERVED trong Beanstalkd

        $response = Http::post('https://api.hitachi.partner/sync', [
            'data' => $this->transaction
        ]);

        if ($response->serverError()) {
            // Đối tác đang bảo trì, trả về lỗi 500!
            // Chuẩn Senior: Thay vì ném Exception vỡ mặt, chúng ta chủ động đưa Job về trạng thái DELAYED.
            // Bắt Job phải "ngủ" trong 60 giây, sau 60 giây Beanstalkd sẽ tự động đẩy nó về READY để chạy lại lần 2.
            $this->release(60); 
            return;
        }

        if ($response->successful()) {
            // Hoàn thành xuất sắc! Laravel sẽ tự động gửi lệnh DELETE tới Beanstalkd để xóa vĩnh viễn Job này.
            return;
        }
    }
    
    /**
     * Hàm này được kích hoạt khi Job thất bại đủ 3 lần ($tries = 3).
     * Laravel sẽ bắt lấy nó, và gửi lệnh BURIED tới Beanstalkd. 
     * Giao dịch không bị mất, nó nằm chờ bạn trong hầm mộ.
     */
    public function failed(Exception $exception)
    {
        \Log::error("Giao dịch {$this->transaction->id} đã bị BURIED do lỗi: " . $exception->getMessage());
    }
}

3. Lắp "Mắt thần" Beanstalkd Console vào Docker

Để quản lý các Job bị chôn (Buried) hoặc theo dõi lưu lượng các ống Tube đang bị nghẽn, việc gõ lệnh CLI (Command Line) sẽ rất mệt mỏi. Chúng ta cần một giao diện trực quan.

Hãy mở file docker-compose.yml và bổ sung thêm Service Dashboard siêu nhẹ này vào:

# ... (Container beanstalkd đã có ở Bài 1)

  # Mắt thần giám sát Queue
  beanstalkd-console:
    image: schickling/beanstalkd-console
    container_name: afc_beanstalkd_console
    restart: unless-stopped
    ports:
      # Mở giao diện trên port 2080 của máy bạn
      - "2080:2080"
    environment:
      # Tự động trỏ kết nối vào Container chứa Beanstalkd thông qua tên mạng nội bộ
      - BEANSTALKD_PORT_11300_TCP_ADDR=beanstalkd
      - BEANSTALKD_PORT_11300_TCP_PORT=11300
    depends_on:
      - beanstalkd
    networks:
      - afc_network

Sau khi chạy docker-compose up -d, hãy mở trình duyệt lên và truy cập vào http://localhost:2080.

4. Trải nghiệm Vận hành thực tế (DevOps Experience)

Giao diện Beanstalkd Console hiện ra, và đây là những gì bạn có thể làm:

  • Tổng quan: Bạn sẽ thấy danh sách tất cả các Tube (như default, hitachi_sync, email_queue).
  • Phân tích Tube: Click vào một Tube, bạn sẽ thấy chính xác có bao nhiêu Job đang Ready, bao nhiêu đang Delayed, bao nhiêu đang bị Buried.
  • Giải cứu dữ liệu (Kick): Giả sử sáng nay hệ thống Hitachi bị sập, bạn vào Console và tá hỏa thấy có 500 Job đang nằm ở mục Buried. Đừng hoảng sợ!
  1. Bạn kiểm tra lại code, phát hiện ra lỗi do thay đổi cấu trúc API.
  2. Bạn fix code và deploy lại lên container Laravel.
  3. Bạn quay lại giao diện Console, ấn nút "Kick 500 jobs".
  4. Bùm! Toàn bộ 500 Job bị chôn lập tức được "sống dậy" chuyển sang trạng thái Ready, Worker lại bốc chúng lên xử lý bằng code mới. Không một đồng tiền nào của khách hàng bị thất thoát!

Với hệ thống State Machine chặt chẽ và Dashboard trực quan, Beanstalkd thực sự là một vũ khí hạng nặng, giải phóng áp lực lưu trữ Queue cho Redis, đồng thời đảm bảo tính vẹn toàn dữ liệu (Data Integrity) ở mức độ cao nhất cho kiến trúc phân tán.

Ở Bài 3 (trạm cuối của series Beanstalkd), bạn muốn chúng ta thiết lập Supervisor trong Docker để tự động duy trì một đội quân 10 Worker ngầm cày cuốc ngày đêm không biết mệt, hay muốn học kỹ thuật cấu hình Laravel Horizon (phiên bản tùy biến) để theo dõi các ống Tube này một cách hiện đại hơn nữa?


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.