Web 應用中耗時任務(發郵件、生成報表、推播通知)不宜阻塞 HTTP 請求。Resque 架構(Job + Queue + Worker)提供標準化解決方案。本文詳細講解 php-resque 安裝、Job/Worker 編寫、佇列操作、狀態追蹤與生產部署要點。
| 角色 | 職責 | 實現形式 |
|---|---|---|
| Job(任務) | 具體業務邏輯(如發送郵件) | 一個 PHP Class,必須實現 perform() 方法 |
| Queue(佇列) | 任務緩衝區,先進先出 | Redis List(LPUSH/RPOP) |
| Worker(工作者) | 常駐進程,消費佇列並執行 Job | PHP CLI 腳本,支援多進程、多佇列、fork 子進程 |
pcntl、posix 擴充,Windows 不支援 fork)。apt-get install redis-server)。# 專案目錄 composer require chrisboulton/php-resque # 或手動 clone git clone https://github.com/chrisboulton/php-resque.git cd php-resque && composer install
// jobs/SendMailJob.php
class SendMailJob {
public function perform() {
// $this->args 為入佇列時傳遞的參數陣列
$to = $this->args['to'];
$subject = $this->args['subject'];
$body = $this->args['body'];
// 實際發送邏輯(可用 PHPMailer)
$mailer = new \PHPMailer\PHPMailer\PHPMailer();
// ... 設定 SMTP、內容 ...
$mailer->addAddress($to);
$mailer->Subject = $subject;
$mailer->Body = $body;
$mailer->send();
// 可選:輸出日誌到 STDOUT,Worker 會捕獲
fwrite(STDOUT, "[".date('Y-m-d H:i:s')."] Mail sent to {$to}\n");
}
// 可選:設置失敗重試次數
// public $maxRetries = 3;
// 可選:設置佇列名稱(預設 default)
// public $queue = 'emails';
}
require_once 'vendor/autoload.php';
use Resque\Resque;
Resque::setBackend('127.0.0.1:6379'); // Redis 連線
$jobId = Resque::enqueue('emails', 'SendMailJob', [
'to' => 'user@example.com',
'subject' => '歡迎註冊',
'body' => '感謝您的註冊,您的驗證碼是 123456',
], true); // true = 追蹤狀態
echo "Job 已入佇列,ID: {$jobId}";
# 單進程處理 emails 佇列 QUEUE=emails php vendor/bin/resque.php # 多進程(需安裝 php-resque-scheduler 或使用 supervisor 管理多個 worker) COUNT=5 QUEUE=emails php vendor/bin/resque.php # 處理所有佇列 QUEUE=* php vendor/bin/resque.php
Worker 會 fork 子進程執行 Job,主進程負責監控與信號處理。生產環境**必須使用 Supervisor/Systemd 管理 Worker 進程**,保證自動重啟、日誌輪轉、開機自啟。
# /etc/supervisor/conf.d/resque-worker.conf [program:resque-worker] command=php /var/www/project/vendor/bin/resque.php process_name=%(program_name)s_%(process_num)02d numprocs=5 directory=/var/www/project user=www-data autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/supervisor/resque-worker.log environment=QUEUE="emails",APP_ENV="production"
supervisorctl reread && supervisorctl update && supervisorctl status
// 獲取 Job 狀態
$status = Resque\Job\Status::get($jobId);
echo $status; // 1=waiting, 2=running, 3=failed, 4=complete
// 失敗任務查看
$failed = Resque\Failure\Failure::all(0, 20);
foreach ($failed as $job) {
echo "Job: {$job['payload']['class']}, Error: {$job['error']}\n";
}
// 重試失敗任務
Resque\Failure\Failure::retry($failedJobId);
php-resque-scheduler 擴充,支援 enqueueAt($timestamp, ...)、enqueueIn($seconds, ...)。QUEUE=high,default,low,高優先級佇列先被消費。perform() 拋出異常會被 Worker 捕獲、標記失敗、記錄堆疊、可選重試。perform() 開頭用 Redis SETNX 鎖定 key。php-resque-web(Sinatra/React 介面)即時查看佇列長度、Worker 狀態、失敗率。| 問題 | 解決 |
|---|---|
| Worker 頻繁崩潰 | 檢查 PHP memory_limit、Job 記憶體洩漏、Redis 連線超時重試 |
| 任務卡住不執行 | 確認 Worker 啟動的 QUEUE 參數與 enqueue 的佇列名一致 |
| 重複執行 | Job 內加冪等性 Key(Redis SETNX),或資料庫唯一索引 |
| 中文亂碼 | 確保 PHP 檔案 UTF-8、Redis 連線無編碼轉換、JSON 編碼加 JSON_UNESCAPED_UNICODE |