Queue และ Job ใน Laravel: สถาปัตยกรรมแบบ Asynchronous และคำถามสัมภาษณ์ 2026

เจาะลึกสถาปัตยกรรม queue และ job ของ Laravel ครอบคลุม dispatch job, batching, chaining, middleware, การจัดการ job ที่ล้มเหลว และการบริหาร queue worker พร้อมตัวอย่างจาก Laravel 12

แผนภาพสถาปัตยกรรมแบบ asynchronous ของ queue และ job ใน Laravel พร้อมโปรเซสของ worker และไปป์ไลน์การ dispatch job

Queue ของ Laravel ให้ API แบบรวมศูนย์สำหรับเลื่อนงานที่ใช้เวลานาน — การส่งอีเมล การประมวลผลไฟล์อัปโหลด การสร้างรายงาน — ไปยัง worker เบื้องหลัง แทนที่จะบังคับให้ผู้ใช้ต้องรอ แอปพลิเคชันจะส่ง job เข้า queue แล้วทำงานต่อไป กลไกนี้อยู่ที่หัวใจของแอปพลิเคชัน Laravel ที่ขยายขนาดได้ทุกตัว

ภาพรวมสถาปัตยกรรม Queue

Laravel รองรับ backend ของ queue หลายแบบ (Redis, Amazon SQS, ฐานข้อมูล, Beanstalkd) ผ่าน API ตัวเดียวที่ไม่ขึ้นกับ driver Job คือคลาส PHP ที่ถูก serialize และ implement interface ShouldQueue Worker จะดึง job ออกจาก queue ทำการ deserialize แล้วเรียกเมธอด handle() Job ที่ล้มเหลวจะถูกเก็บไว้ในตาราง failed_jobs เฉพาะเพื่อ retry หรือตรวจสอบ

การทำงานเบื้องหลังของ Job Dispatching ใน Laravel

เมื่อมีการเรียก dispatch() บนคลาส job Laravel จะ serialize อินสแตนซ์ของ job — รวมถึงคุณสมบัติ public — และส่ง payload ไปยัง connection ของ queue ที่กำหนดไว้ Payload ที่ถูก serialize จะมีชื่อคลาสแบบเต็ม คุณสมบัติที่ serialize ชื่อ queue เป้าหมาย และ metadata เช่นจำนวนครั้งที่อนุญาตให้ลองใหม่และ timeout

โปรเซส worker (php artisan queue:work) ทำงานเป็น daemon ที่อยู่ยาวนาน คอย poll backend ของ queue หา job ใหม่ เมื่อได้รับ payload แล้ว worker จะ deserialize job แก้ไข dependencies ผ่าน service container แล้วเรียก handle()

App/Jobs/ProcessInvoice.phpphp
namespace App\Jobs;

use App\Models\Order;
use App\Services\PdfGenerator;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessInvoice implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 60;
    public int $timeout = 120;

    public function __construct(
        public readonly Order $order
    ) {}

    public function handle(PdfGenerator $pdf): void
    {
        // Generate PDF invoice for the order
        $invoice = $pdf->generate($this->order);

        // Store the generated file
        $this->order->update([
            'invoice_path' => $invoice->path(),
            'invoiced_at' => now(),
        ]);
    }

    public function failed(\Throwable $e): void
    {
        // Notify the ops team when invoice generation fails
        logger()->error('Invoice generation failed', [
            'order_id' => $this->order->id,
            'error' => $e->getMessage(),
        ]);
    }
}

Trait SerializesModels จะเก็บเฉพาะ primary key และชื่อคลาสของโมเดล ไม่ใช่ทั้งโมเดล Eloquent เมื่อ worker ประมวลผล job มันจะดึงโมเดลล่าสุดจากฐานข้อมูล แนวทางนี้ป้องกันข้อมูลเก่าและทำให้ payload มีขนาดเล็ก

Job Batching สำหรับงานที่ทำขนาน

Job batching จัดกลุ่มหลาย job ไว้ใน batch เดียว ติดตามความคืบหน้ารวม และทริกเกอร์ callback เมื่อ job ทั้งหมดเสร็จ — หรือเมื่อมี job ใดล้มเหลว แพตเทิร์นนี้เหมาะกับการ import ข้อมูล การส่งแจ้งเตือนจำนวนมาก และการสร้างรายงานที่หลายหน่วยงานอิสระต้องเสร็จก่อนจะรันขั้นตอนสุดท้าย

App/Http/Controllers/ImportController.phpphp
use App\Jobs\ImportRow;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

public function import(Request $request)
{
    $rows = $this->parseCSV($request->file('data'));

    // Create a batch of import jobs, one per CSV row
    $batch = Bus::batch(
        collect($rows)->map(fn ($row) => new ImportRow($row))
    )
    ->then(function (Batch $batch) {
        // All jobs completed successfully
        Notification::send(
            auth()->user(),
            new ImportComplete($batch->totalJobs)
        );
    })
    ->catch(function (Batch $batch, \Throwable $e) {
        // First failure in the batch
        logger()->warning('Batch import partial failure', [
            'batch_id' => $batch->id,
            'failed' => $batch->failedJobs,
        ]);
    })
    ->finally(function (Batch $batch) {
        // Runs after all jobs finish (success or failure)
        Cache::forget("import_lock_{$batch->id}");
    })
    ->allowFailures()
    ->dispatch();

    return response()->json(['batch_id' => $batch->id]);
}

Laravel 12 เสริม payload ของ batch ด้วย metadata เพิ่มเติม รวมถึงเวลาที่รออยู่ใน queue และการระบุตัว worker เมธอด allowFailures() ป้องกันไม่ให้ job ที่ล้มเหลวเพียงตัวเดียวยกเลิก batch ทั้งหมด — สำคัญสำหรับการ import ขนาดใหญ่ที่ความสำเร็จบางส่วนเป็นที่ยอมรับได้

Job Chaining สำหรับเวิร์กโฟลว์ตามลำดับ

ในขณะที่ batching จัดการงานแบบขนาน chaining รับประกันการทำงานตามลำดับ Job แต่ละตัวใน chain จะรันต่อเมื่อ job ก่อนหน้าสำเร็จเท่านั้น หาก job ใดล้มเหลว ส่วนที่เหลือของ chain จะถูกยกเลิกและ callback catch จะทำงาน

App/Services/OrderWorkflow.phpphp
use App\Jobs\ValidatePayment;
use App\Jobs\ReserveInventory;
use App\Jobs\SendConfirmation;
use App\Jobs\GenerateShippingLabel;
use Illuminate\Support\Facades\Bus;

public function processOrder(Order $order): void
{
    // Each job runs only after the previous one succeeds
    Bus::chain([
        new ValidatePayment($order),
        new ReserveInventory($order),
        new GenerateShippingLabel($order),
        new SendConfirmation($order),
    ])
    ->onQueue('orders')
    ->catch(function (\Throwable $e) use ($order) {
        // Roll back the order if any step fails
        $order->update(['status' => 'failed']);
        logger()->error('Order chain failed', [
            'order_id' => $order->id,
            'step' => $e->getMessage(),
        ]);
    })
    ->dispatch();
}

Chaining โดดเด่นในเวิร์กโฟลว์เชิงโดเมนที่ลำดับขั้นตอนสำคัญ — การชำระเงินต้องผ่านการตรวจสอบก่อนจะจองสินค้าคงคลัง และฉลากการจัดส่งขึ้นกับสินค้าคงคลังที่ยืนยันแล้ว

พร้อมที่จะพิชิตการสัมภาษณ์ Laravel แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Queue Middleware สำหรับเรื่องตัดขวาง

Queue middleware ห่อหุ้มการทำงานของ job ด้วย logic ที่นำกลับมาใช้ใหม่ได้ เช่น rate limiting การกำจัดซ้ำ หรือ circuit breaker แทนที่จะฝังเรื่องเหล่านี้ไว้ในทุก job middleware ช่วยให้ job มุ่งเน้นที่ logic ทางธุรกิจ

App/Jobs/Middleware/RateLimitedJob.phpphp
namespace App\Jobs\Middleware;

use Closure;
use Illuminate\Support\Facades\RateLimiter;

class RateLimitedJob
{
    public function __construct(
        private string $key,
        private int $maxAttempts = 10,
        private int $decaySeconds = 60
    ) {}

    public function handle(object $job, Closure $next): void
    {
        // Release job back to queue if rate limit exceeded
        if (RateLimiter::tooManyAttempts($this->key, $this->maxAttempts)) {
            $job->release($this->decaySeconds);
            return;
        }

        RateLimiter::hit($this->key, $this->decaySeconds);

        $next($job);
    }
}

ใช้งาน middleware โดยกำหนดเมธอด middleware() บนคลาส job:

App/Jobs/CallExternalApi.phpphp
public function middleware(): array
{
    return [
        new RateLimitedJob(
            key: 'external-api',
            maxAttempts: 30,
            decaySeconds: 60
        ),
        // Prevent duplicate jobs from running concurrently
        (new WithoutOverlapping($this->apiResource->id))
            ->releaseAfter(300)
            ->expireAfter(600),
    ];
}

Middleware WithoutOverlapping ใช้ atomic lock เพื่อให้แน่ใจว่ามีเพียงอินสแตนซ์เดียวของ job (ระบุด้วย key) ที่รันในขณะหนึ่ง เมื่อรวมกับ rate limiting จึงป้องกันทั้งการประมวลผลซ้ำและการถูก throttle จาก API

การจัดการ Job ที่ล้มเหลวและกลยุทธ์ Retry

ระบบ queue ในโปรดักชันต้องการการจัดการความล้มเหลวที่แข็งแกร่ง Laravel เก็บ job ที่ล้มเหลวไว้ในตาราง failed_jobs พร้อม payload เต็ม exception trace และ queue/connection ที่ก่อให้เกิดความล้มเหลว เมธอด failed() บนคลาส job แต่ละตัวจะรันหลังจากความพยายาม retry ทั้งหมดถูกใช้หมดแล้ว

การกำหนดพฤติกรรม retry แยกต่อ job ทำให้ควบคุมได้ละเอียด:

App/Jobs/SyncExternalData.phpphp
class SyncExternalData implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 5;

    // Exponential backoff: 10s, 30s, 60s, 120s, 300s
    public function backoff(): array
    {
        return [10, 30, 60, 120, 300];
    }

    // Job-specific timeout
    public int $timeout = 180;

    // Maximum exceptions before marking as failed
    public int $maxExceptions = 3;

    public function retryUntil(): \DateTime
    {
        // Keep retrying for up to 24 hours
        return now()->addHours(24);
    }

    public function handle(): void
    {
        $response = Http::timeout(30)
            ->retry(2, 1000)
            ->get('https://api.vendor.com/data');

        if ($response->failed()) {
            // Release back to queue with delay for transient failures
            $this->release(60);
            return;
        }

        DataSync::process($response->json());
    }

    public function failed(\Throwable $e): void
    {
        Notification::route('slack', config('services.slack.ops_channel'))
            ->notify(new SyncFailed($e));
    }
}

ความแตกต่างระหว่าง $tries, $maxExceptions และ retryUntil() มีความสำคัญในการสัมภาษณ์ $tries นับทุกความพยายามรวมถึงการ release ด้วยตนเอง $maxExceptions นับเฉพาะ exception ที่ไม่ถูกจัดการ retryUntil() กำหนดกรอบเวลาโดยไม่ขึ้นกับจำนวนครั้ง

การจัดการ Queue Worker และ Deployment

Queue worker ในโปรดักชันต้องการการกำกับดูแลโปรเซส การรีสตาร์ตแบบนุ่มนวลขณะ deploy และการบริหารทรัพยากร Supervisor เป็นเครื่องมือมาตรฐานในการรักษาให้ worker ทำงานต่อเนื่อง

ini
; /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopwaitsecs=3600
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/worker.log
stopasgroup=true
killasgroup=true

ข้อพิจารณาสำคัญสำหรับการ deploy:

  • รีสตาร์ตแบบนุ่มนวล: php artisan queue:restart ส่งสัญญาณให้ worker จบ job ปัจจุบันก่อนรีสตาร์ต ป้องกันการเสียหายของ job ขณะ deploy
  • เวลา/job สูงสุด: --max-time=3600 และ --max-jobs=1000 ป้องกัน memory leak ด้วยการรีไซเคิลโปรเซส worker เป็นระยะ
  • ช่วงพัก (sleep): --sleep=3 ควบคุมเวลาที่ worker รอก่อน poll queue ที่ว่างอีกครั้ง ค่าน้อยลงเพิ่มการตอบสนอง แต่ก็เพิ่มภาระให้ฐานข้อมูล/Redis
  • หลาย queue: --queue=critical,default,low ประมวลผล queue ตามลำดับความสำคัญ Worker จะระบาย queue critical ให้หมดก่อนแตะ default

Laravel 12.37 แนะนำ connection ของ queue background ที่เลื่อน job ด้วย Concurrently::defer() driver นี้ serialize และรัน job ในโปรเซส PHP แยกต่างหาก — มีประโยชน์สำหรับ job เบาที่ไม่จำเป็นต้องใช้โครงสร้าง queue เต็มรูปแบบ

Unique Job และ Payload ที่เข้ารหัส

มีสองแพตเทิร์นที่พบบ่อยในโปรดักชันและการสัมภาษณ์: การรับประกันว่า job รันเพียงครั้งเดียวต่อ key ที่กำหนด และการปกป้องข้อมูลละเอียดอ่อนใน payload

App/Jobs/RebuildSearchIndex.phpphp
use Illuminate\Contracts\Queue\ShouldBeUnique;

class RebuildSearchIndex implements ShouldQueue, ShouldBeUnique
{
    // Lock duration in seconds
    public int $uniqueFor = 3600;

    public function __construct(
        public readonly string $indexName
    ) {}

    // Unique key scopes the lock to this specific index
    public function uniqueId(): string
    {
        return $this->indexName;
    }

    public function handle(): void
    {
        SearchIndex::rebuild($this->indexName);
    }
}

สำหรับ job ที่บรรจุข้อมูลละเอียดอ่อน (ข้อมูลรับรองผู้ใช้ โทเค็นการชำระเงิน) interface ShouldBeEncrypted จะเข้ารหัส payload ที่ serialize ทั้งหมดในขณะจัดเก็บ:

App/Jobs/ProcessPayment.phpphp
use Illuminate\Contracts\Queue\ShouldBeEncrypted;

class ProcessPayment implements ShouldQueue, ShouldBeEncrypted
{
    public function __construct(
        private string $paymentToken,
        private float $amount
    ) {}

    public function handle(PaymentGateway $gateway): void
    {
        $gateway->charge($this->paymentToken, $this->amount);
    }
}

Payload ถูกเข้ารหัสด้วย application key ก่อนจัดเก็บใน Redis หรือฐานข้อมูล Worker จะถอดรหัสโดยอัตโนมัติก่อน deserialize

คำถามสัมภาษณ์ทั่วไปเกี่ยวกับ Laravel Queue

การสัมภาษณ์ทางเทคนิคมักทดสอบความเข้าใจสถาปัตยกรรม queue ที่ลึกกว่าความรู้ระดับผิวของ API

เกิดอะไรขึ้นเมื่อ job ใน queue อ้างถึงโมเดล Eloquent ที่ถูกลบไปแล้ว? ด้วย SerializesModels worker พยายามดึงโมเดลตาม ID เมื่อประมวลผล job หากโมเดลไม่อยู่แล้ว Laravel จะโยน ModelNotFoundException เพื่อจัดการอย่างนุ่มนวล ตั้งคุณสมบัติ $deleteWhenMissingModels เป็น true — job จะถูกลบออกจาก queue อย่างเงียบ ๆ แทนที่จะล้มเหลว

ShouldBeUnique ต่างจาก middleware WithoutOverlapping อย่างไร? ShouldBeUnique ป้องกันการ dispatch job หากมี job ที่ใช้ unique key เดียวกันอยู่ใน queue แล้ว WithoutOverlapping อนุญาตให้ dispatch แต่ป้องกัน การทำงานพร้อมกัน — หาก job ที่มี key เดียวกันกำลังรันอยู่ อินสแตนซ์ใหม่จะถูกส่งกลับเข้า queue ทั้งสองแก้ปัญหาที่ต่างกันและสามารถใช้ร่วมกันได้

ควรเลือก retryUntil() แทน $tries เมื่อใด? retryUntil() เหมาะสำหรับ job ที่โต้ตอบกับบริการภายนอกที่เวลาฟื้นตัวคาดเดาไม่ได้ จำนวน retry คงที่ ($tries = 3) อาจหมดในช่วงสั้น ๆ ที่บริการล่ม retryUntil() กำหนดกรอบเวลา (เช่น 24 ชั่วโมง) และยังคง retry แบบ backoff ต่อไปจนกว่าบริการจะฟื้น หรือกรอบเวลาหมดลง

Queue priority ทำงานอย่างไรกับหลาย queue? การรัน queue:work --queue=critical,default,low สร้างระบบลำดับความสำคัญ Worker จะระบาย queue critical ให้หมดก่อนตรวจสอบ default และ default ก่อน low หมายความว่า job ลำดับความสำคัญต่ำอาจอดอยากในช่วงโหลดสูง สำหรับ SLA ที่เคร่งครัด worker เฉพาะทางต่อแต่ละ queue ให้การรับประกันที่ดีกว่า

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

บทสรุป

  • Queue ของ Laravel แยกชั้น backend ของ queue ออกจากกันด้วย API ที่ไม่ขึ้นกับ driver รองรับ Redis, SQS, ฐานข้อมูล และ connection background ใหม่ใน Laravel 12.37
  • Job batching จัดการงานขนานพร้อมการติดตามความคืบหน้ารวม ในขณะที่ chaining บังคับให้ทำงานตามลำดับสำหรับเวิร์กโฟลว์เชิงโดเมน
  • Queue middleware (rate limiting, WithoutOverlapping) ทำให้เรื่องตัดขวางอยู่นอก logic ทางธุรกิจของ job
  • การจัดการ job ที่ล้มเหลวรวม $tries, $maxExceptions, retryUntil() และ exponential backoff เพื่อกลยุทธ์ retry ที่ทนทาน
  • ShouldBeUnique ป้องกันการ dispatch ซ้ำ ShouldBeEncrypted ปกป้อง payload ละเอียดอ่อนในขณะจัดเก็บ
  • Worker โปรดักชันต้องใช้ Supervisor การรีสตาร์ตแบบนุ่มนวลขณะ deploy และการจัดการหน่วยความจำผ่าน --max-time และ --max-jobs
  • การเตรียมตัวสัมภาษณ์ควรครอบคลุมความแตกต่างระหว่างความเป็นเอกลักษณ์ตอน dispatch กับการล็อกตอนทำงาน พฤติกรรมการ serialize โมเดล และการอดอยากของ queue ตามลำดับความสำคัญ

สำหรับการฝึกฝน คำถามสัมภาษณ์ Laravel คลังคำถามของ SharpSkill ครอบคลุม queue, middleware และแพตเทิร์นของ Eloquent พร้อมคำอธิบายโดยละเอียด

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

แท็ก

#laravel
#queues
#jobs
#php
#async
#architecture

แชร์

บทความที่เกี่ยวข้อง

คู่มือเจาะลึก Laravel Middleware ครอบคลุม Authentication, Rate Limiting และ Custom Middleware

Laravel Middleware เจาะลึก: Authentication, Rate Limiting และ Custom Middleware ฉบับสมบูรณ์

คู่มือเจาะลึก Laravel Middleware ครอบคลุมการทำงานของ Authentication Middleware, Rate Limiting ด้วย Throttle, การสร้าง Custom Middleware และการลงทะเบียน Middleware ใน Laravel 12 พร้อมตัวอย่างโค้ดที่ใช้งานได้จริงในโปรดักชัน

รูปแบบและการเพิ่มประสิทธิภาพ Eloquent ORM สำหรับ Laravel

Eloquent ORM: รูปแบบและการเพิ่มประสิทธิภาพสำหรับ Laravel

เชี่ยวชาญ Eloquent ORM ด้วยรูปแบบขั้นสูงและเทคนิคการเพิ่มประสิทธิภาพ ทั้ง eager loading, query scope, accessor, mutator และประสิทธิภาพสำหรับแอป Laravel

คำถามสัมภาษณ์งาน Laravel และ PHP - คู่มือฉบับสมบูรณ์

25 คำถามสัมภาษณ์งาน Laravel และ PHP ยอดนิยมในปี 2026

รวม 25 คำถามสัมภาษณ์งาน Laravel และ PHP ที่พบบ่อยที่สุด ครอบคลุม Service Container, Eloquent ORM, middleware, queues และการ deploy ระบบ production พร้อมคำตอบและตัวอย่างโค้ดครบถ้วน