定時任務三大主流方案對比
| 方案 | 原理 | 優點 | 缺點 | 適用場景 |
| Crontab + Shell | Linux 系統級調度 | 穩健、獨立於 PHP、支援分鐘級 | 依賴 Linux、分散式部署難管理 | 常規定時備份、報表、清理 |
| ignore_user_abort + set_time_limit | HTTP 請求觸發常駐腳本 | 無需系統權限、代碼控制邏輯 | 只能單實例、易超時、佔用 FPM 進程 | 簡單輪詢、輕量任務 |
| 純 PHP CLI + PCNTL (本文重點) | 常駐進程 + 信號驅動 | 毫秒級精度、進程隔離、支援持久化任務 | 需 CLI 環境、PCNTL 擴展、Linux 為主 | 高併發隊列消費、即時推送、遊戲邏輯循環 |
純 PHP Timer 核心原理
- 靜態三維數組存儲任務:
[$timestamp => [[$interval, $callback, $args, $persist], ...]]
- PCNTL 信號:每秒發送
SIGALRM 給當前進程
- 信號處理器捕獲信號 → 遍歷任務隊列 → 到期則
call_user_func_array 執行
- 自循環:執行完重新
pcntl_alarm(1) 設置下一秒鬧鐘
- 主循環:
while(true) { sleep(1); pcntl_signal_dispatch(); } 保持進程存活並分發信號
Timer 類完整實現
class Timer
{
public static $tasks = [];
public static $tick = 1; // 秒
public static function run(int $tick = null): void
{
if ($tick) self::$tick = $tick;
self::installHandler();
pcntl_alarm(1);
}
public static function installHandler(): void
{
pcntl_signal(SIGALRM, [self::class, 'signalHandler']);
}
public static function signalHandler(): void
{
self::executeDueTasks();
pcntl_alarm(self::$tick); // 重新裝填鬧鐘
}
public static function executeDueTasks(): void
{
if (empty(self::$tasks)) return;
$now = time();
foreach (self::$tasks as $timestamp => &$jobs) {
foreach ($jobs as $index => $job) {
if ($now >= $timestamp) {
// 執行回調
call_user_func_array($job['callback'], $job['args'] ?? []);
// 非持久化任務執行完移除
if (!$job['persist']) {
unset($jobs[$index]);
} else {
// 持久化:重新計算下次執行時間
$nextTime = $now + $job['interval'];
self::$tasks[$nextTime][] = $job;
unset($jobs[$index]);
}
}
}
if (empty($jobs)) unset(self::$tasks[$timestamp]);
}
}
public static function add(int $interval, callable $callback, array $args = [], bool $persist = false): void
{
$time = time() + $interval;
self::$tasks[$time][] = [
'callback' => $callback,
'args' => $args,
'interval' => $interval,
'persist' => $persist
];
}
public static function clear(): void
{
self::$tasks = [];
}
}
使用示例
require 'Timer.php';
class Heartbeat
{
public function beat(array $meta = []): void
{
echo "[" . date('H:i:s') . "] Heartbeat: " . json_encode($meta) . PHP_EOL;
}
}
Timer::clear();
// 每 1 秒執行一次,持久化
Timer::add(1, [new Heartbeat(), 'beat'], ['type' => 'sys'], true);
// 3 秒後執行一次,非持久化
Timer::add(3, [new Heartbeat(), 'beat'], ['type' => 'once'], false);
echo "Timer started at " . date('H:i:s') . PHP_EOL;
Timer::run();
// 主進程保持存活並分發信號
while (true) {
sleep(1);
pcntl_signal_dispatch();
}
生產環境部署關鍵點
- 進程守護:用 Supervisor、Systemd 或 Docker `restart: always` 確保進程掛掉自動拉起
- 多進程擴展:啟動多個 Worker 進程,配合 Redis/RabbitMQ 分發任務
- 優雅關閉:捕獲
SIGTERM 信號,完成當前任務後退出
- 日誌與監控:記錄執行耗時、異常堆棧,接入 Prometheus/Grafana
- 精度限制:PHP
pcntl_alarm 僅支援秒級,毫秒級需用 Swoole/Workerman/ReactPHP
現代替代方案推薦
| 框架/庫 | 特點 |
| Swoole / Hyperf | 協程、毫秒定時器、高併發、內建 Server |
| Workerman | 純 PHP、多進程、定時器、長連接 |
| Laravel Scheduler | 藝術家風格、一行代碼定義、底層仍靠 Crontab |
| Symfony Messenger + Messenger Consume | 消息總線、異步處理、可配合 Crontab 觸發 |
總結
理解 PCNTL + Signal + 靜態任務隊列 的純 PHP Timer 原理,能讓你在無法安裝擴展的受限環境裡實現可靠定時任務。但生產環境強烈建議升級到 Swoole/Hyperf/Workerman,獲得協程調度、毫秒精度、更豐富的生態支持。選對工具,讓定時任務成為系統穩健運行的「心跳」而非「定時炸彈」。