Web 請求的容忍延遲通常 < 200ms。若任務耗時超過 1-2 秒(如發送萬封郵件、生成大型報表、資料同步),必須移至背景離線執行,避免阻塞用戶介面、觸發逾時或資源耗盡。
KISS 原則:無需常駐多進程/多線程守護進程,利用系統內建 cron 定時啟動 PHP CLI 腳本,處理完即退出,天然免疫記憶體洩漏、死鎖、無限迴圈風險。
分 時 日 月 週 用戶 命令
0 23 * * * www /usr/bin/php /path/to/script.php # 每天 23:00
15 * * * * www /usr/bin/php /path/to/script.php # 每小時 15 分
*/5 9-18 * * 1-5 www /usr/bin/php /path/to/script.php # 工作日 9-18 點每 5 分鐘
MAILTO=admin@example.com # 錯誤郵件通知
Windows 使用「工作排程器」同樣可定時執行 php.exe script.php。
CREATE TABLE mailouts (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
from_address TEXT NOT NULL,
to_address TEXT NOT NULL,
subject TEXT NOT NULL,
content TEXT NOT NULL,
PRIMARY KEY (id)
);
Mailoutsclass Mailouts {
static function get_db() { return DB::connect('mysql://user:pass@localhost/db'); }
static function add($from, $to, $subject, $content) { /* INSERT */ }
static function delete($id) { /* DELETE */ }
static function get_all() { /* SELECT * */ }
}
Mailouts::add('noreply@site.com', 'user@domain.com', '主題', '內容');
mailout_send.phpforeach (Mailouts::get_all() as $msg) {
mail($msg[2], $msg[3], $msg[4], "From: $msg[1]");
Mailouts::delete($msg[0]); // 逐封刪除,防重發
}
由 cron 定時執行 mailout_send.php。
支援任意 PHP 函式延遲執行,參數以鍵值對儲存。
CREATE TABLE processing_items (id MEDIUMINT AUTO_INCREMENT, function TEXT, PRIMARY KEY(id));
CREATE TABLE processing_args (id MEDIUMINT AUTO_INCREMENT, item_id MEDIUMINT, key_name TEXT, value TEXT, PRIMARY KEY(id));
ProcessingItemsclass ProcessingItems {
static function add($function, array $args) { /* 寫入 items + args */ }
static function get_all() { /* JOIN 讀取 function + args 陣列 */ }
static function delete($id) { /* 刪除 items + args */ }
}
// 前端
ProcessingItems::add('sendReport', ['user_id' => 123, 'type' => 'monthly']);
// 背景處理器
foreach (ProcessingItems::get_all() as $item) {
call_user_func_array($item['function'], [$item['args']]);
ProcessingItems::delete($item['id']);
}
將任務序列化為檔案存放於目錄(如 batch_items/),每個檔案一個任務。
BatchFiles 類關鍵方法
static function add($function, $args) {
$path = BATCH_DIRECTORY . time() . '_' . uniqid();
$fh = fopen($path, 'w');
fwrite($fh, $function . "\n");
foreach ($args as $k=>$v) fwrite($fh, "$k:$v\n");
fclose($fh);
}
static function get_all() {
$rows = [];
if ($dh = opendir(BATCH_DIRECTORY)) {
while (($file = readdir($dh)) !== false) {
$path = BATCH_DIRECTORY . $file;
if (is_file($path)) {
$fh = fopen($path, 'r');
$item['id'] = $path;
$item['function'] = trim(fgets($fh));
$item['args'] = [];
while (($line = fgets($fh)) !== false) {
list($k, $v) = explode(':', trim($line), 2);
$item['args'][$k] = $v;
}
fclose($fh);
$rows[] = $item;
}
}
closedir($dh);
}
return $rows;
}
static function delete($id) { unlink($id); }
處理器邏輯與資料庫版本幾乎相同,僅替換類名。介面一致,便於未來遷移。
| 議題 | 建議 |
|---|---|
| 任務冪等性 | 設計任務可重複執行不產生副作用(如發送郵件前檢查狀態)。 |
| 失敗重試 | 增加 attempts、last_error 欄位,失敗不刪除,cron 重跑時重試,超過閾值移入死信表/目錄並告警。 |
| 優先級 | 資料庫表加 priority 欄位,ORDER BY priority DESC, id ASC。 |
| 併發控制 | 單一 cron 執行;若需多進程,用 flock() 或資料庫 SELECT ... FOR UPDATE SKIP LOCKED 協調。 |
| 監控 | 記錄處理耗時、成功/失敗計數,對接 Prometheus/Grafana 或簡單日誌告警。 |
| 現代替代方案 | 對於高併發、複雜流程,考慮 Redis + PHP Worker (Swoole/RoadRunner)、Laravel Queue (Redis/Database/SQS)、RabbitMQ/Kafka 等成熟佇列中介軟體。 |
從簡單的 cron + 檔案/資料庫佇列起步,隨業務增長平滑遷移至專業佇列系統。核心原則:**介面抽離、冪等設計、可觀測性、失敗自愈**。