你的 Laravel 網站還在同步等回應?解鎖 Scheduler 與 Queue,打造非同步火箭,使用者體驗原地起飛!
嗨,我是浪花科技的 Eric。身為一個天天跟程式碼打交道的工程師,我最受不了的事情之一,就是看到網頁上的那個小圈圈一直在轉、一直在轉…轉到天荒地老,轉到使用者都跑光了。你點了一個按鈕,想說送出個表單、上傳個影片,結果整個瀏覽器就卡在那邊動彈不得,這種體驗簡直是災難。
很多時候,這不是你伺服器不夠力,而是你的應用程式架構從根本上就「太老實了」。它堅持要「同步」完成所有事情——使用者一提出請求,它就得當場把所有事情做完才能回報。這就像你去手搖飲店點餐,店員跟你說:「先生請稍等,我要先去後面倉庫搬茶葉、煮珍珠、搖完你的飲料後,才能幫下一位點餐。」聽起來就很荒謬,對吧?
今天,我就要來跟你聊聊 Laravel 世界裡的兩大神器:排程 (Scheduler) 與 隊列 (Queue)。這兩個東西,就是幫你把手搖飲店流程最佳化的關鍵,讓你學會「非同步」處理任務,把那些耗時的工作丟到背景去執行,瞬間釋放你的網站,讓使用者體驗原地起飛。
為什麼「非同步」是現代網站的必修課?
在我們深入程式碼之前,先得建立一個核心觀念:不是所有任務都該讓使用者「線上等」。很多任務的本質就不是即時性的。我們來看看哪些情況下,同步處理會變成一場惡夢:
- 發送電子郵件: 使用者註冊後發送歡迎信,這件事很重要,但使用者需要立刻看到「信已寄出」的畫面嗎?不需要。他只需要知道「註冊成功」就好。但串接外部 SMTP 服務可能會有延遲,如果同步處理,使用者就得盯著轉圈圈的畫面,直到郵件伺服器回應為止。
- 影像/檔案處理: 使用者上傳了一部高畫質影片,後端需要進行轉檔、壓縮、加上浮水印等操作。這些都是 CPU 密集型任務,動輒數十秒甚至數分鐘。讓使用者在瀏覽器前乾等,99% 的人會直接關掉分頁。
- 呼叫外部 API: 你需要串接一個第三方的數據服務,但對方 API 的回應速度很不穩定。如果同步等待,你的網站效能就會被這個「豬隊友」給拖垮。
- 產生報表: 管理員需要一份上個月的銷售報表,這需要撈取大量資料庫數據並進行運算。如果同步執行,可能會導致請求超時 (Timeout),報表沒產生出來,還得到一個錯誤頁面。
看到問題了嗎?這些任務的共通點就是「耗時」且「不需立即回饋」。而 Laravel 排程與背景任務 (Scheduler / Queue) 的核心價值,就是把這些任務從主流程中抽離,丟到背景默默執行,讓你的網站能以最快的速度回應使用者:「好的,你的請求我收到了,處理完會通知你。」這才是現代化、高效率的應用程式該有的樣子。
Laravel Scheduler:網站的全自動管家
我們先來談談比較單純的 Scheduler。你可以把它想像成一個非常聰明且準時的管家,你只要預先告訴他「每天半夜三點,去把資料庫裡過期的快取清一清」或「每個星期一早上九點,寄送上週的營運報告給老闆」,他就會風雨無阻地自動執行。
囉嗦一句,以前我們搞這種排程任務,都得登入 Linux 主機,手動編輯一個叫 `crontab` 的檔案,語法又臭又長,而且跟專案程式碼是完全分離的,管理起來超級痛苦。Laravel Scheduler 把這一切都整合進你的專案裡,用優雅的 PHP 語法就能定義,簡直是工程師的福音。
第一步:設定 Cron Job (唯一需要碰伺服器的一次)
要讓 Scheduler 運作,你 딱 需要在你的伺服器上設定一條 Cron Job。就這一條,以後就再也不用碰它了。打開你的 crontab 編輯器 (`crontab -e`),然後加入下面這行:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
這行的意思是:「每分鐘」都去檢查你的 Laravel 專案,看看有沒有「剛好這個時間點」需要執行的任務。所有的調度邏輯,都由 Laravel 自己來判斷,我們不用再寫複雜的 cron 表達式了。
第二步:建立你的第一個排程指令
假設我們要建立一個每天刪除舊日誌檔案的指令。首先,用 Artisan 建立一個 Command:
php artisan make:command PruneOldLogs
然後打開 `app/Console/Commands/PruneOldLogs.php`,修改 `handle` 方法,把你要執行的邏輯寫進去:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
class PruneOldLogs extends Command
{
protected $signature = 'logs:prune';
protected $description = 'Prune old log files from storage';
public function handle()
{
$files = File::glob(storage_path('logs/*.log'));
foreach ($files as $file) {
// 假設我們要刪除七天前的日誌
if (filemtime($file) < now()->subDays(7)->getTimestamp()) {
File::delete($file);
$this->info('Deleted: ' . $file);
}
}
$this->info('Old logs pruned successfully!');
return 0;
}
}
第三步:在 Kernel 中註冊與排程
最後,打開 `app/Console/Kernel.php`,在 `schedule` 方法中註冊你的指令並設定執行的頻率。
use App\Console\Commands\PruneOldLogs;
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')->hourly();
$schedule->command('logs:prune')->dailyAt('03:00'); // 每天凌晨三點執行
}
就這樣!夠簡單吧?Laravel 提供了各種語意化的排程方法,例如 `hourly()`、`daily()`、`mondays()`、`everyFifteenMinutes()` 等等,可讀性超高,再也不用去查 cron 語法了。
Laravel Queue:處理耗時任務的非同步神器
如果說 Scheduler 是定時炸彈,那 Queue 就是處理突發事件的特種部隊。當使用者觸發一個耗時任務時,我們不是讓 PHP 當場處理,而是把這個「任務」打包成一個 Job,然後把它丟進一個「隊列 (Queue)」中,再馬上跟使用者說「OK,收到了!」。
接著,會有一個或多個在背景默默運行的「工人 (Worker)」程序,不斷地從隊列中取出任務來執行。這樣一來,前端的使用者完全感受不到延遲。
第一步:設定 Queue Driver
Laravel 支援多種 Queue driver,像是 database、Redis、Amazon SQS 等。對於初學者或中小型專案,`database` driver 是最簡單的起點。你只需要修改 `.env` 檔:
QUEUE_CONNECTION=database
接著,產生隊列需要的資料表:
php artisan queue:table php artisan migrate
這樣 Laravel 就會建立一個 `jobs` 資料表和 `failed_jobs` 資料表,用來存放你的任務。
第二步:建立你的第一個 Job
假設我們要在使用者註冊後,發送一封歡迎郵件。首先,建立一個 Job:
php artisan make:job SendWelcomeEmail
這會產生 `app/Jobs/SendWelcomeEmail.php` 檔案。我們需要修改它,讓它可以接收 User 物件,並在 `handle` 方法中執行寄信邏輯。
namespace App\Jobs;
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;
use App\Mail\WelcomeMail; // 假設你已經建立了一個 Mailable
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));
}
}
注意到 `implements ShouldQueue` 了嗎?這就是告訴 Laravel:「這個 Job 應該被推送到隊列中,而不是同步執行」。
第三步:在你的 Controller 中分派 Job
現在,在你處理使用者註冊的 Controller 方法中,你不再是直接呼叫 `Mail::send()`,而是分派 (dispatch) 這個 Job。
use App\Jobs\SendWelcomeEmail;
public function register(Request $request)
{
// ... 驗證與建立使用者的程式碼 ...
$user = User::create([...]);
// 把寄信任務丟到隊列中
SendWelcomeEmail::dispatch($user);
return response()->json(['message' => '註冊成功!歡迎信將稍後寄出。']);
}
看到差別了嗎?`dispatch` 這個動作幾乎是瞬間完成的,你的 API 可以立刻回傳成功訊息給前端,使用者體驗絲般滑順。
第四步:啟動 Queue Worker
任務是丟進資料庫了,但還需要有人去執行它。這就是 Worker 的工作。在你的終端機中執行:
php artisan queue:work
這個指令會啟動一個常駐程序,它會不斷地去 `jobs` 資料表檢查有沒有新任務,有的話就取出來執行。不過,這裡有個大坑!很多新手會直接在 SSH 視窗執行這個指令,然後一關掉視窗,Worker 就跟著停了。在正式環境中,你必須使用一個程序監控工具,如 Supervisor,來確保 `queue:work` 指令能夠一直在背景運行,掛掉了也能自動重啟。 這點非常、非常重要,不然你的隊列任務將會無限堆積,永遠不會被執行。
終極組合技:Scheduler + Queue
當你同時掌握了 Scheduler 和 Queue,你就可以玩出更進階的應用。例如,你可以用 Scheduler 每天半夜去觸發一個 Job,而這個 Job 的內容是去撈取一整天的訂單資料,然後產生一份複雜的報表並存到 S3。這樣既實現了自動化,又避免了產生報表的耗時操作影響到其他背景任務。
在 `Kernel.php` 中可以這樣寫:
use App\Jobs\GenerateDailySalesReport;
protected function schedule(Schedule $schedule)
{
// 每天半夜兩點,把「產生銷售報表」這個任務丟到隊列中
$schedule->job(new GenerateDailySalesReport)->dailyAt('02:00');
}
這種架構讓你的應用程式變得極度強大且有彈性,能夠從容應對各種複雜的業務需求。
結語:別讓你的網站活在石器時代
我知道,對於習慣 WordPress 或傳統 PHP 開發模式的朋友來說,Laravel 排程與背景任務 (Scheduler / Queue) 的概念一開始可能有點抽象。你需要設定 Cron Job、需要管理 Worker 程序,聽起來比單純寫 PHP 腳本要麻煩。
但相信我,這絕對是值得的。一旦你跨過了這個門檻,你就能打造出反應更迅速、架構更穩健、更能應對高併發場景的現代化應用程式。使用者不再需要為了一個耗時操作而枯燥等待,伺服器的資源也能被更有效地利用。這不僅是技術上的提升,更是思維上的躍進。
如果你對如何將這些強大的非同步機制導入你的專案,或是對如何建構更高效能的網站架構感興趣,別只是停留在想的階段。浪花科技擁有豐富的 Laravel 與 WordPress 整合開發經驗,我們能幫助你把想法化為現實。
👉 立即聯繫浪花科技,讓我們聊聊如何為你的網站裝上非同步的火箭引擎!
推薦閱讀
- Laravel 效能卡關?Redis 就是你的神兵利器!從快取到隊列,資深工程師帶你榨乾系統效能
- 別再手寫 SQL 了!Laravel Eloquent ORM 終極指南:從新手入門到效能優化,一次搞懂 Active Record 的黑魔法
- 你的 Controller 還在身兼多職?導入 Service Layer,打造可維護、高彈性的 Laravel Admin 後台架構!
常見問題 (FAQ)
Q1: Laravel Scheduler 和傳統的 Cron Job 到底有什麼不同?
傳統的 Cron Job 是直接在伺服器上設定要執行的指令和時間,設定分散且難以跟隨專案版本控管。Laravel Scheduler 則是將這些排程任務的「定義」寫在你的 PHP 程式碼中 (app/Console/Kernel.php),你只需要在伺服器上設定一條每分鐘執行 `php artisan schedule:run` 的通用 Cron Job 即可。這樣做的好處是:1. 排程邏輯跟著專案走,方便版控與協作。 2. 提供非常語意化、可讀性高的語法 (如 `daily()`, `mondays()`),不用再記憶複雜的 cron 表達式。 3. 可以輕鬆地與 Laravel 的其他功能(如 Queue)整合。
Q2: 我剛開始用 Queue,應該選擇哪種 driver (資料庫、Redis)?
對於初學者或中小型專案,我強烈建議從 `database` driver 開始。它的設定最簡單,只需要執行 `php artisan queue:table` 和 `migrate`,不需要額外安裝或設定其他服務。這讓你可以快速上手,專注在 Job 的邏輯開發上。當你的網站流量變大,隊列任務非常頻繁時,`database` driver 可能會因為頻繁的讀寫而成為效能瓶頸,這時候再考慮轉換到效能更好的 `redis` driver 也不遲。Redis 是基於記憶體的儲存,速度飛快,是正式環境中處理大量隊列任務的首選。
Q3: 我在終端機執行 `php artisan queue:work`,但關掉視窗後它就停止了,怎麼辦?
這是新手最常犯的錯誤!`php artisan queue:work` 是一個需要常駐執行的前景程序。你不能只是在 SSH 登入後手動執行它,因為當你登出或連線中斷,這個程序就會被終止。在正式的生產環境中,你必須使用一個「程序監控工具」來管理你的 worker。最常見的選擇是 **Supervisor**。你需要在伺服器上安裝並設定 Supervisor,告訴它去執行 `php artisan queue:work` 指令,並確保當這個程序意外掛掉時,Supervisor 會自動將它重新啟動,這樣才能保證你的背景任務能夠 7×24 小時不間斷地被處理。






