PHP项目事件调度如何定时执行数据库任务

wen PHP项目 26

本文目录导读:

PHP项目事件调度如何定时执行数据库任务

  1. 使用操作系统的Cron(最常用的方案)
  2. 使用消息队列(适合高并发场景)
  3. 使用PHP框架的内置调度器
  4. 使用独立调度库(适合非框架项目)
  5. 最佳实践建议
  6. 推荐方案

在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 组件化、灵活
  1. 对于大多数PHP项目,使用操作系统的Cron + PHP脚本是最简单可靠的方案
  2. 处理高并发任务时,考虑引入消息队列
  3. 框架项目优先使用框架内置的调度器
  4. 始终添加执行锁防止任务重叠
  5. 做好日志和告警,及时发现任务异常

选择哪种方案取决于你的项目规模、部署环境和团队技术栈,建议从最简单的方案开始,随着需求增长再逐步升级。

抱歉,评论功能暂时关闭!