本文目录导读:

在PHP项目中实现定时执行数据库任务,通常有几种主流方案,以下是针对不同场景的最佳实践:
使用操作系统的Cron(最常用的方案)
对于Linux服务器,这是最稳定、最推荐的方式。
实现步骤:
① 创建PHP脚本 (cron/tasks.php)
<?php
// 防止直接访问
define('CURRENT_TIME', date('Y-m-d H:i:s'));
// 执行数据库清理任务
function cleanupExpiredRecords($db) {
$stmt = $db->prepare("DELETE FROM sessions WHERE expires_at < NOW()");
return $stmt->execute();
}
// 执行数据同步任务
function syncData($db) {
try {
$db->beginTransaction();
// 同步待处理订单
$stmt = $db->prepare("
UPDATE orders
SET status = 'processed'
WHERE status = 'pending'
AND created_at < DATE_SUB(NOW(), INTERVAL 1 DAY)
");
$stmt->execute();
$db->commit();
logMessage('数据同步完成');
} catch (Exception $e) {
$db->rollBack();
logMessage('数据同步失败: ' . $e->getMessage());
}
}
// 日志记录
function logMessage($message) {
$logFile = __DIR__ . '/../logs/tasks.log';
file_put_contents($logFile,
'[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL,
FILE_APPEND | LOCK_EX
);
}
// 主入口
require_once __DIR__ . '/../config/database.php';
$db = getConnection();
// 根据参数执行不同任务
$task = $argv[1] ?? 'default';
switch ($task) {
case 'cleanup':
cleanupExpiredRecords($db);
break;
case 'sync':
syncData($db);
break;
default:
echo "可用任务: cleanup, sync\n";
}
② 设置Cron任务
# 编辑crontab crontab -e # 添加以下内容 # 每分钟执行清理 * * * * * /usr/bin/php /path/to/your/project/cron/tasks.php cleanup # 每小时执行数据同步 0 * * * * /usr/bin/php /path/to/your/project/cron/tasks.php sync # 每天凌晨3点执行完整备份 0 3 * * * /usr/bin/php /path/to/your/project/cron/tasks.php backup
③ 添加锁防止任务重叠
<?php
// cron/lock.php
$lockFile = __DIR__ . '/locks/' . md5($argv[1] ?? 'default') . '.lock';
// 尝试获取锁
if (!flock(fopen($lockFile, 'c'), LOCK_EX | LOCK_NB)) {
exit("任务正在执行中,跳过本次执行\n");
}
// 执行任务...
// 释放锁
flock($fp, LOCK_UN);
使用消息队列(适合高并发场景)
使用RabbitMQ实现定时任务
<?php
// 生产者: 添加定时任务
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
// 声明延迟交换机
$channel->exchange_declare(
'delayed_exchange',
'x-delayed-message',
false,
true,
false,
false,
false,
new AMQPTable(['x-delayed-type' => 'direct'])
);
// 创建消息
$data = json_encode([
'task' => 'cleanup_expired',
'params' => ['table' => 'sessions']
]);
$message = new AMQPMessage($data, [
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT
]);
// 设置延迟时间(30分钟后执行)
$message->set('application_headers', new AMQPTable([
'x-delay' => 1800000 // 毫秒
]));
$channel->basic_publish($message, 'delayed_exchange', 'task.queue');
$channel->close();
$connection->close();
使用PHP框架的内置调度器
Laravel的任务调度
<?php
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// 每分钟执行一次
$schedule->command('db:cleanup-expired')
->everyMinute()
->withoutOverlapping()
->runInBackground();
// 每天凌晨2点执行数据归档
$schedule->command('db:archive-data')
->dailyAt('02:00')
->onOneServer()
->environments(['production']);
// 自定义数据库任务
$schedule->call(function () {
DB::table('analytics')
->where('created_at', '<', now()->subDays(30))
->delete();
})->daily();
}
Symfony的任务调度
<?php
// config/packages/scheduler.php
use Symfony\Component\Scheduler\Attribute\AsCronTask;
#[AsCronTask('0 */2 * * *')] // 每2小时
class CleanupDatabaseTask extends AbstractTask
{
public function __construct(
private EntityManagerInterface $entityManager
) {}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// 清理过期数据
$query = $this->entityManager
->createQuery('DELETE FROM App\Entity\Session s WHERE s.expiresAt < :now')
->setParameter('now', new \DateTime());
$affected = $query->execute();
$output->writeln("清理了 $affected 条记录");
return Command::SUCCESS;
}
}
使用独立调度库(适合非框架项目)
使用 cron/cron 包
composer require dragonmantank/cron-expression
<?php
// scheduler.php
require_once 'vendor/autoload.php';
use Cron\CronExpression;
class Scheduler
{
private array $tasks = [];
public function add(string $cronExpression, callable $callback)
{
$this->tasks[] = [
'expression' => $cronExpression,
'callback' => $callback
];
}
public function run()
{
$now = new \DateTime();
foreach ($this->tasks as $task) {
$cron = new CronExpression($task['expression']);
if ($cron->isDue($now)) {
call_user_func($task['callback']);
}
}
}
}
// 使用方法
$scheduler = new Scheduler();
$scheduler->add('*/5 * * * *', function () {
// 每5分钟执行数据库清理
$db = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
$db->exec("DELETE FROM temp_data WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR)");
});
// 需要在循环中运行
while (true) {
$scheduler->run();
sleep(60);
}
最佳实践建议
错误处理
<?php
try {
// 执行任务
} catch (Throwable $e) {
// 记录错误并发送告警
error_log("任务执行失败: " . $e->getMessage());
// 发送邮件告警
mail('[email protected]', '任务失败', $e->getMessage());
}
监控日志
<?php
// 结构化日志
function logTask($taskName, $status, $details = []) {
$log = [
'timestamp' => date('c'),
'task' => $taskName,
'status' => $status,
'memory_usage' => memory_get_peak_usage(true),
'duration' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'],
'details' => $details
];
file_put_contents(
'logs/tasks.json',
json_encode($log) . PHP_EOL,
FILE_APPEND
);
}
性能优化
<?php
// 分批处理大数据
function batchProcess($db, $query, $batchSize = 1000) {
$offset = 0;
$total = 0;
do {
$stmt = $db->prepare("$query LIMIT $batchSize OFFSET $offset");
$stmt->execute();
$count = $stmt->rowCount();
if ($count > 0) {
$total += $count;
$offset += $batchSize;
}
} while ($count > 0);
return $total;
}
推荐方案
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 传统服务器 | Cron + PHP脚本 | 简单、稳定、零依赖 |
| 容器环境 | Kubernetes CronJob | 管理方便、可扩展 |
| 高并发微服务 | 消息队列 (RabbitMQ) | 削峰填谷、可靠 |
| Laravel项目 | Laravel Scheduler | 开箱即用、优雅 |
| Symfony项目 | Symfony Scheduler | 组件化、灵活 |
- 对于大多数PHP项目,使用操作系统的Cron + PHP脚本是最简单可靠的方案
- 处理高并发任务时,考虑引入消息队列
- 框架项目优先使用框架内置的调度器
- 始终添加执行锁防止任务重叠
- 做好日志和告警,及时发现任务异常
选择哪种方案取决于你的项目规模、部署环境和团队技术栈,建议从最简单的方案开始,随着需求增长再逐步升级。