網站卡住了?別再讓使用者等到天荒地老!Laravel 排程與背景任務 (Scheduler & Queue) 終極指南

2025/09/15 | Laravel技術分享

網站卡住了?別再讓使用者等到天荒地老!Laravel 排程與背景任務 (Scheduler & Queue) 終極指南

嗨,我是浪花科技的 Eric。身為一個整天跟程式碼為伍的工程師,我最怕聽到的不是 bug,而是客戶說:「欸,Eric,為什麼我點了送出之後,網站要轉圈圈轉個天荒地老?」這句話的殺傷力,大概跟泡麵裡沒有調理包一樣讓人心碎。

使用者體驗是王道,這句話我們都聽到膩了。但使用者按下按鈕後,如果你的網站需要處理一些複雜或耗時的任務——比如寄送上千封電子報、處理上傳的影片、生成複雜的月底報表——然後就讓使用者盯著那個讀取中的小圓圈,直到懷疑人生…那很抱歉,你的使用者體驗已經不及格了。他們不僅會關掉分頁,還可能再也不會回來。

這就是我們今天要聊的主題:如何用 Laravel 的兩大神器——排程 (Scheduler)佇列 (Queue),來優雅地處理這些背景任務,把順暢的體驗還給使用者,也把寧靜的夜晚還給開發者。

為什麼我們需要「非同步」處理?同步執行的惡夢

在深入 Laravel 之前,我們先來聊聊什麼是「同步 (Synchronous)」執行。簡單來說,就是一步一步來。使用者提交一個請求,伺服器接收到,開始處理,處理完畢,然後回傳結果。整個過程,使用者的瀏覽器都在原地等待。如果處理的任務很簡單,例如存一筆留言,那可能幾十毫秒就搞定了,使用者幾乎無感。

但如果任務是這樣的:

  • 使用者註冊成功後,系統要發送一封歡迎信。
  • 使用者下單後,系統要產生 PDF 訂單、通知倉儲、呼叫金流 API、發送訂單確認信。
  • 使用者上傳了一張大頭貼,系統要進行裁切、壓縮、加上浮水印、存到 S3。

這些任務,每一個都可能需要好幾秒。如果全部擠在同一個請求裡「同步」處理,使用者就得等。等待,是轉換率最大的殺手。身為工程師,我們不能容忍這種事情發生。我們需要的是「非同步 (Asynchronous)」處理,也就是把這些耗時的任務「丟到背景」去執行,然後立刻告訴使用者:「好的,收到你的請求了,你可以繼續逛了,我處理完會通知你。」

這就是 Laravel Queue(佇列) 發揮作用的地方。

Laravel Queue:你的任務分身,解放主程式的超能力

想像一下,你去一家超忙的速食店點餐。你點完餐,櫃檯人員不是在你面前從頭到尾把你的漢堡、薯條、飲料全部做完才服務下一位。他會給你一張號碼牌,然後把你的訂單傳到後方廚房。你就可以先到旁邊找位子坐,滑滑手機,等著叫號取餐。櫃檯(你的網站主程式)可以繼續服務下一位客人,而廚房(背景處理程序)則專心準備你的餐點。

Laravel Queue 就是這個系統。那些耗時的任務,我們稱為「Job(任務)」。當需要執行時,我們不直接執行它,而是把它「dispatch(分派)」到一個「Queue(佇列)」中。然後,會有一個或多個「Worker(工人)」在背景持續監看這個佇列,一有新的任務進來,就拿起來執行。

Queue 的基本設定與實戰

在 Laravel 中使用 Queue 非常直觀。首先,你需要在 .env 檔案中設定你的佇列驅動 (Queue Driver)。


QUEUE_CONNECTION=database

Laravel 支援多種 Driver,常見的有:

  • sync: 同步執行,根本沒有丟到背景。這只適合在本地開發時用來快速除錯。拜託,千萬別在正式環境用這個,你會被同事追殺。
  • database: 將任務資訊存在資料庫的 jobs 表中。設定簡單,適合中小型專案。
  • redis: 使用 Redis 作為佇列。速度飛快,是目前最主流、最推薦的選擇,效能屌打 database driver。
  • SQS: Amazon 的雲端佇列服務,擴展性極強,適合大型企業級應用。

如果選擇 database,你需要先建立對應的資料表:


php artisan queue:table
php artisan migrate

建立你的第一個 Job

假設我們要建立一個寄送註冊成功信的 Job,只需要一行指令:


php artisan make:job SendWelcomeEmail

這會在 app/Jobs/SendWelcomeEmail.php 建立一個新的類別。我們來看看它的結構:


<?php

namespace App\Jobs;

use App\Mail\WelcomeMail;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;

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

    protected $user;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        Mail::to($this->user->email)->send(new WelcomeMail($this->user));
    }
}

看到重點了嗎?我們把寄信的邏輯都封裝在 handle() 方法裡。建構子 __construct() 則用來接收必要的資料,例如這裡的 User 模型。

分派 (Dispatch) 任務

在你的 Controller 裡,原本同步寄信的程式碼可能是這樣:


// 舊的、會卡住的寫法
Mail::to($user->email)->send(new WelcomeMail($user));

現在,我們改成把它分派到佇列:


// 新的、非同步的寫法
use App\Jobs\SendWelcomeEmail;

SendWelcomeEmail::dispatch($user);

就這樣!一行程式碼,你的註冊流程瞬間從 5 秒變 0.1 秒。使用者幾乎感覺不到延遲,而寄信這個任務已經被丟到背景,等待 Worker 處理了。這就是工程師的魔法。

Laravel Scheduler:你的定時小鬧鐘,永不失約

Queue 解決了「即時性」的耗時任務,但如果有些任務是需要「週期性」執行的呢?例如:

  • 每天凌晨三點備份資料庫。
  • 每週一早上九點寄送上週銷售報表。
  • 每小時清理一次過期的快取檔案。

傳統上,我們會使用 Linux 的 Cron Job 來處理。你需要在伺服器上編輯 crontab 檔案,為每個任務新增一條規則。任務一多,管理起來就非常混亂,而且這些設定還無法納入 Git 版本控制,團隊協作時很容易出問題。

Laravel Scheduler 就是為了解決這個痛點而生的。它讓你用非常優雅、語意化的 PHP 程式碼來定義所有排程任務,並且只需要在伺服器上設定「一條」Cron Job 規則。

設定 Scheduler

首先,在你的伺服器 crontab 中加入這一行:


* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

這條規則的意思是:「每分鐘」都去執行 Laravel 的 schedule:run 指令。沒錯,就這一條,不管你未來有多少排程任務,都不用再碰 crontab 了。Laravel 會根據你在程式碼中的定義,判斷在「當下這一分鐘」有哪些任務需要被執行。

定義排程任務

所有的排程都定義在 app/Console/Kernel.php 檔案的 schedule 方法裡。


<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // 每小時執行一次快取清理指令
        $schedule->command('cache:clear')->hourly();

        // 每天凌晨三點執行資料庫備份任務 (假設你有一個 BackupDatabase 的指令)
        $schedule->command('db:backup')->dailyAt('03:00');

        // 每週一早上九點寄送報表
        $schedule->job(new SendWeeklyReport)->weeklyOn(1, '9:00');

        // 每五分鐘檢查一次網站狀態
        $schedule->call(function () {
            // 你的檢查邏輯
        })->everyFiveMinutes();
    }

    // ...
}

看看這語法,是不是像在寫英文一樣自然?hourly(), dailyAt('03:00'), weeklyOn(1, '9:00')… 這就是框架的威力。所有的排程邏輯都集中在一個檔案裡,可以跟著專案一起做版本控制,清晰明瞭,再也不用 ssh 到伺服器上猜測到底設定了哪些 Cron Job。

終極組合:Supervisor – 確保你的背景工人永不罷工

好了,現在我們知道如何把任務丟到 Queue,也知道 Scheduler 會定時執行。但還有一個關鍵問題:誰來執行 Queue 裡的任務?是 Worker。

你可以在終端機手動執行 php artisan queue:work 來啟動一個 Worker,但只要你關掉終端機,這個 Worker 就停止了。在正式環境中,我們需要一個程序監控工具,來確保我們的 Worker 永遠在背景執行,就算不小心掛了,也能自動重啟。

這個工具,就是 Supervisor。它是一個在 Linux 系統上廣泛使用的程序控制器。你只需要寫一個簡單的設定檔,告訴 Supervisor 要監控哪個指令。

一個典型的 Supervisor 設定檔 (例如 /etc/supervisor/conf.d/laravel-worker.conf) 可能長這樣:


[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path-to-your-project/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=your-user
numprocs=8
redirect_stderr=true
stdout_logfile=/path-to-your-project/storage/logs/worker.log
stopwaitsecs=3600

這個設定檔告訴 Supervisor:

  • 啟動 8 個 (numprocs=8) Worker 程序。
  • 如果任何一個程序掛了,自動重啟 (autorestart=true)。
  • 執行的指令是 php artisan queue:work ...
  • 將所有的日誌輸出到指定的檔案。

設定好之後,只需要重啟 Supervisor,它就會像個盡責的工頭,24 小時幫你盯著你的 Worker 們,確保所有背景任務都能被順利處理。

結論:從「卡頓」到「絲滑」,只是一個架構思維的轉變

從同步到非同步,從手動執行到自動排程,Laravel 的 Scheduler 和 Queue 提供了一套完整且優雅的解決方案,讓我們能夠打造出反應更迅速、架構更健壯的應用程式。

下次當你遇到一個可能耗時超過一秒的操作時,問問自己:「這個任務真的需要讓使用者在原地等待嗎?」答案通常是否定的。把它打包成一個 Job,丟進 Queue,然後給使用者一個即時的回應。這個小小的改變,將會對你的網站效能和使用者體驗帶來巨大的提升。

這不只是寫程式的技巧,更是一種對使用者負責的態度。別再讓你的使用者等到天荒地老了,今天就開始擁抱 Laravel強大的背景任務處理能力吧!

延伸閱讀

如果你對於導入 Laravel、優化網站效能,或是任何複雜的後端架構設計有任何疑問,浪花科技的團隊擁有豐富的實戰經驗。我們不只寫程式,我們打造穩定、高效、可擴展的商業解決方案。歡迎點擊這裡,填寫表單與我們聯繫,讓我們的資深工程師團隊為你的專案注入新的活力!

常見問題 (FAQ)

Q1: Laravel Scheduler 和傳統 Cron Job 有什麼不同?

傳統 Cron Job 需要直接在伺服器上為每個任務設定一條規則,管理不易且無法納入版本控制。Laravel Scheduler 則讓你在 PHP 程式碼中以語意化的方式定義所有排程,只需要在伺服器設定一條「每分鐘」執行的通用 Cron Job 即可。這讓排程任務的管理變得集中、清晰,且能跟著專案一起進行版本控制,大大提升了開發和維護的效率。

Q2: 我應該選擇哪種 Queue Driver (佇列驅動)?

選擇取決於你的專案規模和需求。對於本地開發,`sync` 很方便。對於中小型專案或想快速開始的專案,`database` 是一個可靠的選擇。然而,對於絕大多數的正式環境,`redis` 是速度和效能上的首選,它比 database driver 快得多。如果你的應用是建構在 AWS 上且有極高的擴展性需求,那麼 `SQS` 會是更適合的企業級解決方案。

Q3: 為什麼我需要用 Supervisor 來管理 Queue Worker?

`php artisan queue:work` 是一個長時運行的程序,如果你只是手動在終端機執行,一旦連線中斷或伺服器重啟,Worker 就會停止運作,所有背景任務也將停擺。Supervisor 是一個程序監控工具,它可以確保你的 Worker 程序永遠在背景運行,如果因任何原因崩潰,Supervisor 會自動將它重啟,保證了你背景任務處理系統的穩定性和可靠性。

 
立即諮詢,索取免費1年網站保固