PHP 本身無多線程、無原生定時器,所有定時任務都需借助外部機制。本文系統梳理四大類實現方案:系統級 Crontab(最高效)、Windows 計劃任務、用戶訪問觸發(虛擬主機適用)、第三方平台跳板,並深度剖析各方案的原理、代碼實現、優劣勢與適用場景。
繞過 Web 服務器(Nginx/Apache),直接調用 PHP 解析器執行腳本,效率最高、資源佔用最低。
crontab -e 編輯定時任務表分 時 日 月 周 命令/usr/local/bin/php /path/to/script.php# 每 2 小時執行一次 00 */2 * * * /usr/local/bin/php /home/www/script.php # 每天凌晨 3:30 執行 30 3 * * * /usr/local/bin/php /home/www/daily_job.php >> /home/www/logs/daily.log 2>&1 # 每分鐘檢查一次(高頻任務慎用) * * * * * /usr/local/bin/php /home/www/check.php
max_execution_time)cron.bat:D:\php\php.exe -q D:\website\test.php
cron.batphp.ini 中 extension_dir 為絕對路徑max_execution_time)// 1. 忽略用戶斷開
ignore_user_abort(true);
// 2. 移除執行時間限制
set_time_limit(0);
// 3. 防重複執行鎖(文件鎖)
$lockFile = __DIR__ . '/cron.lock';
if (file_exists($lockFile) && (time() - filemtime($lockFile) < 3600)) exit();
// 4. 異步觸發核心:fsockopen 非阻塞請求
function async_request($url) {
$parsed = parse_url($url);
$host = $parsed['host'];
$port = $parsed['port'] ?? ($parsed['scheme'] === 'https' ? 443 : 80);
$path = $parsed['path'] . ($parsed['query'] ? '?' . $parsed['query'] : '');
$fp = fsockopen(($parsed['scheme'] === 'https' ? 'ssl://' : '') . $host, $port, $errno, $errstr, 1);
if (!$fp) return false;
stream_set_timeout($fp, 1);
$header = "GET $path HTTP/1.1\r\nHost: $host\r\nConnection: Close\r\n\r\n";
fwrite($fp, $header);
usleep(1000); // 關鍵:確保請求發出
fclose($fp);
return true;
}
| 模式 | 原理 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 死循環 + sleep | 腳本常駐內存,定時喚醒 | 實時性強 | 佔用進程、易崩潰、虛擬主機禁用 | 極少見,不推薦 |
| 用戶訪問掃描任務表 | 每次請求檢查是否有到期任務 | 無額外資源、實現簡單 | 執行時間不確定(依賴訪問量)、可能延遲 | 低頻、非實時任務(如緩存清理、統計更新) |
| 第三方跳板 | 阿里云/百度云/監控服務定時回調 | 時間精準、不佔用自身資源 | 依賴第三方、需公網可訪問 | 虛擬主機、無服務器權限環境 |
// tasks.json 任務表
[
{"name": "clean_cache", "url": "https://site.com/cron/clean_cache", "cron": "0 3 * * *"},
{"name": "daily_report", "url": "https://site.com/cron/daily_report", "cron": "30 3 * * *"}
]
// common.php 每次請求掛載
function check_cron_tasks() {
$tasks = json_decode(file_get_contents('tasks.json'), true);
$now = time();
foreach ($tasks as $k => $task) {
// 簡易 cron 表達式匹配(生產環境建議用 dragonmantank/cron-expression)
if (cron_match($task['cron'], $now)) {
$lastRun = get_last_run($task['name']);
if ($now - $lastRun >= 60) { // 至少間隔 60 秒防重複
async_request($task['url']);
set_last_run($task['name'], $now);
}
}
}
}
// 入口文件首行調用
check_cron_tasks();
on: schedule 定時運行 workflow,可 curl 回調// jumper.php 部署在有定時能力的平台
$targets = [
'https://mysite.com/cron/job1',
'https://mysite.com/cron/job2',
];
foreach ($targets as $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_TIMEOUT => 3,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => false,
]);
curl_exec($ch);
curl_close($ch);
}
// server.php
$server = new Swoole\Server('0.0.0.0', 9501);
$server->set(['worker_num' => 1, 'task_worker_num' => 4]);
$server->on('WorkerStart', function ($server, $workerId) {
if ($workerId === 0) {
// 每天 03:00 執行
$server->tick(86400000, function () use ($server) {
if (date('H:i') === '03:00') {
$server->task('daily_job');
}
});
// 每分鐘檢查
$server->tick(60000, function () use ($server) {
$server->task('minute_check');
});
}
});
$server->on('Task', function ($server, $taskId, $workerId, $data) {
if ($data === 'daily_job') { /* ... */ }
if ($data === 'minute_check') { /* ... */ }
});
$server->start();
fsockopen 異步觸發,避免阻塞用戶請求根據業務場景、基礎設施權限、實時性要求,選擇最適合的方案。現代雲原生架構下,雲函數 + 定時觸發器正成為虛擬主機/Serverless 環境的最佳實踐。