PHP 是「請求-響應」模型,無事件循環、無線程、無信號處理。實現定時任務的本質是:由外部調度器在特定時間觸發 PHP CLI 執行腳本。根據環境約束(權限、架構、併發),方案演進路徑清晰。
# 1. 編輯 crontab crontab -e # 2. 添加任務(建議使用 flock 防重複) * * * * * flock -xn /tmp/lock.scheduler -c '/usr/bin/php /var/www/artisan schedule:run >> /dev/null 2>&1' # 3. 直接腳本調度 0 2 * * * flock -xn /tmp/lock.daily -c '/usr/bin/php /var/www/scripts/daily_report.php --env=prod >> /var/log/daily.log 2>&1'
sleep $((RANDOM\%60)))避免驚群。# 利用 Redis SETNX 實現分布式鎖(需 Redis 可用) * * * * * /usr/bin/php /var/www/scripts/distributed_task.php --lock-key=sync_orders --lock-ttl=300
// distributed_task.php 核心邏輯
$redis = new Redis(); $redis->connect('127.0.0.1', 6379);
$lockKey = "cron_lock:{$argv[1]}"; $ttl = $argv[2] ?? 300;
$lockVal = gethostname() . '_' . getmypid() . '_' . uniqid();
if (!$redis->set($lockKey, $lockVal, ['NX', 'EX' => $ttl])) {
exit("鎖佔用中,退出\n");
}
register_shutdown_function(function() use ($redis, $lockKey, $lockVal) {
$lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
$redis->eval($lua, 1, $lockKey, $lockVal);
});
// 核心業務邏輯
echo "獲得鎖,執行任務...\n";
:: cron.bat @echo off D:\php\php.exe -q D:\www\scripts\task.php >> D:\logs\task.log 2>&1
cmd.exe,參數:/c D:\scripts\cron.bat。// 原理:每次請求檢查是否有到期任務,有則異步執行
// 缺點:流量低時任務延遲;高流量時並發衝突;無法精確控制時間
// 改進:異步觸發(fsockopen/curl 非阻塞)
function triggerCronAsync(string $url): void {
$parsed = parse_url($url);
$host = $parsed['host'];
$path = $parsed['path'] . ($parsed['query'] ? '?' . $parsed['query'] : '');
$fp = fsockopen($host, 80, $errno, $errstr, 1); // 1 秒超時
if ($fp) {
$req = "GET $path HTTP/1.1\r\nHost: $host\r\nConnection: Close\r\n\r\n";
fwrite($fp, $req);
fclose($fp); // 立即關閉,不等待響應
}
}
// 業務代碼中
if (shouldRunCron()) {
triggerCronAsync('https://mysite.com/cron_endpoint.php');
}
| 服務 | 免費額度 | 特點 |
|---|---|---|
| cron-job.org | 無限任務,最小 1 分鐘 | Web 界面、日誌、郵件通知、支援 HTTP Basic Auth |
| EasyCron | 1 個任務/天 | 簡單易用、支援 POST/Headers/Cookie |
| GitHub Actions | 免費額度大 | Cron 語法、完整日誌、可運行腳本/容器 |
| GitLab CI/CD | 免費 | Pipeline 排程、支援手動觸發 |
| 雲函數定時觸發 | 各廠商免費額度 | SCF/FC/CloudRun 等,支援 HTTP 觸發 |
# GitHub Actions 範例 .github/workflows/cron.yml
name: Daily Cron
on:
schedule:
- cron: '0 2 * * *' # UTC 時間
workflow_dispatch: # 手動觸發
jobs:
run:
runs-on: ubuntu-latest
steps:
- name: Trigger PHP Cron
run: |
curl -fsS --max-time 30 \
-H "Authorization: Bearer ${{ secrets.CRON_TOKEN }}" \
"https://mysite.com/api/cron/daily"
// 核心缺陷:佔用 FPM 進程、內存洩漏、無法優雅停止、部署失效
// 僅適合:本地測試、極低流量個人項目、無任何其他選擇
ignore_user_abort(true);
set_time_limit(0);
$interval = 3600; // 1 小時
$nextRun = strtotime('tomorrow 02:00');
while (true) {
$wait = max(0, $nextRun - time());
sleep($wait);
// 檢查開關文件
if (!file_exists(__DIR__ . '/cron_switch')) break;
// 執行任務
require __DIR__ . '/cron_task.php';
$nextRun += 86400; // 下一天
}
apiVersion: batch/v1
kind: CronJob
metadata:
name: php-daily-report
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid # 禁止並行
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: php-task
image: my-php-cli:8.2
command: ["php", "/var/www/scripts/daily_report.php"]
env:
- name: APP_ENV
value: "production"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
# Dockerfile
FROM php:8.2-cli
RUN curl -fsSL https://github.com/aptible/supercronic/releases/download/v0.2.1/supercronic-linux-amd64 -o /usr/local/bin/supercronic \
&& chmod +x /usr/local/bin/supercronic
COPY crontab /etc/crontab
CMD ["supercronic", "/etc/crontab"]
# crontab
* * * * * php /var/www/artisan schedule:run
┌─────────────────────────────────────────────────────────────┐
│ 你有服務器 Root/管理員權限? │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
是 否
│ │
▼ ▼
┌───────────────┐ ┌───────────────────────┐
│ Linux/Unix? │ │ 虛擬主機/共享主機? │
└───────┬───────┘ └───────────┬───────────┘
│ │
┌────┴────┐ ┌──────┴──────┐
▼ ▼ ▼ ▼
Linux Windows 有流量? 無流量/極低
│ │ │ │
▼ ▼ ▼ ▼
系統 Cron 任務計劃 第三方 Cron GitHub Actions/
(首選) 程式 + bat 服務跳板 雲函數定時
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ 進階需求:秒級/毫秒級、高併發、分布式協調、可視化監控? │
└─────────────────────┬───────────────────────────────────────┘
│
┌───────┴───────┐
▼ ▼
是 否
│ │
▼ ▼
Swoole/Workerman/ 當前方案足夠
Hyperf/消息佇列/ 專注業務開發
K8s CronJob
timestamp、level、task_name、trace_id、duration_ms、status。PHP 定時任務的本質是外部調度 + CLI 執行。根據環境約束選型:
永遠不要在生產環境使用 ignore_user_abort() + sleep() 死循環。投資於正確的調度架構,將為後期維護節省無數工時。