PHP项目Symfony Scheduler任务

wen PHP项目 2

本文目录导读:

PHP项目Symfony Scheduler任务

  1. 什么是Symfony Scheduler
  2. 安装
  3. 基本配置
  4. 高级用法
  5. 使用Cron表达式
  6. 使用周期性时间间隔
  7. 运行调度器
  8. 完整示例
  9. 最佳实践
  10. 注意事项

我来详细介绍Symfony Scheduler组件的使用和配置。

什么是Symfony Scheduler

Symfony Scheduler是Symfony框架的任务调度组件,用于管理定时任务、周期性任务和一次性任务,它提供了比cron更灵活和强大的任务调度能力。

安装

composer require symfony/scheduler

基本配置

启用调度器

config/packages/scheduler.yaml 中配置:

# config/packages/scheduler.yaml
framework:
    scheduler:
        enabled: true

创建任务

创建任务类

// src/Scheduler/MyTask.php
namespace App\Scheduler;
use Symfony\Component\Scheduler\Attribute\AsCronTask;
use Symfony\Component\Scheduler\Attribute\AsPeriodicTask;
#[AsCronTask('*/5 * * * *')] // 每5分钟执行一次
#[AsPeriodicTask('10 minutes')] // 每10分钟执行一次
class MyTask
{
    public function __invoke(): void
    {
        // 执行任务逻辑
        $this->doSomething();
    }
    private function doSomething(): void
    {
        // 你的业务逻辑
        file_put_contents('/tmp/scheduler.log', 
            sprintf("Task executed at %s\n", date('Y-m-d H:i:s')), 
            FILE_APPEND
        );
    }
}

高级用法

使用Schedule对象

// src/Scheduler/TaskProvider.php
namespace App\Scheduler;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\ScheduleProviderInterface;
class TaskProvider implements ScheduleProviderInterface
{
    public function getSchedule(): Schedule
    {
        $schedule = new Schedule();
        // 添加周期性任务
        $schedule->add(
            RecurringMessage::every('5 minutes', new SendNewsletter())
        );
        // 添加Cron表达式任务
        $schedule->add(
            RecurringMessage::cron('0 0 * * *', new DailyReport())
        );
        // 添加一次性任务
        $schedule->add(
            RecurringMessage::at(new \DateTimeImmutable('2024-12-31 23:59:59'), new NewYearTask())
        );
        return $schedule;
    }
}

任务类型定义

// src/Message/SendNewsletter.php
namespace App\Message;
class SendNewsletter
{
    public function __construct(
        private string $newsletterType,
        private array $recipients = []
    ) {}
    public function getNewsletterType(): string
    {
        return $this->newsletterType;
    }
    public function getRecipients(): array
    {
        return $this->recipients;
    }
}

消息处理器

// src/MessageHandler/SendNewsletterHandler.php
namespace App\MessageHandler;
use App\Message\SendNewsletter;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class SendNewsletterHandler
{
    public function __invoke(SendNewsletter $message): void
    {
        // 处理邮件发送
        $type = $message->getNewsletterType();
        $recipients = $message->getRecipients();
        // 发送邮件逻辑
        foreach ($recipients as $recipient) {
            // 发送邮件
        }
    }
}

使用Cron表达式

// 常用的Cron表达式
#[AsCronTask('* * * * *')]        // 每分钟
#[AsCronTask('*/5 * * * *')]      // 每5分钟
#[AsCronTask('0 * * * *')]        // 每小时
#[AsCronTask('0 0 * * *')]        // 每天午夜
#[AsCronTask('0 0 * * 0')]        // 每周日
#[AsCronTask('0 0 1 * *')]        // 每月1号
#[AsCronTask('0 */2 * * *')]      // 每2小时
#[AsCronTask('0 0 * * 1-5')]      // 工作日午夜

使用周期性时间间隔

// 使用人类可读的时间格式
#[AsPeriodicTask('5 minutes')]     // 每5分钟
#[AsPeriodicTask('1 hour')]        // 每小时
#[AsPeriodicTask('1 day')]         // 每天
#[AsPeriodicTask('2 weeks')]       // 每2周
#[AsPeriodicTask('1 month')]       // 每月
// 使用DateInterval
#[AsPeriodicTask('P1D')]           // 每天
#[AsPeriodicTask('PT1H')]          // 每小时
#[AsPeriodicTask('PT30M')]         // 每30分钟

运行调度器

启动调度器消费者

# 在后台运行调度器
php bin/console scheduler:consume
# 指定调度器名称
php bin/console scheduler:consume default
# 使用进程管理器(Supervisor)配置

Supervisor配置

# /etc/supervisor/conf.d/symfony-scheduler.conf
[program:symfony-scheduler]
command=php /path/to/your/project/bin/console scheduler:consume --time-limit=3600
directory=/path/to/your/project
autostart=true
autorestart=true
stderr_logfile=/var/log/symfony/scheduler.err.log
stdout_logfile=/var/log/symfony/scheduler.out.log
numprocs=2
process_name=%(program_name)s_%(process_num)02d

完整示例

邮件通知任务

// src/Scheduler/EmailNotificationTask.php
namespace App\Scheduler;
use Symfony\Component\Scheduler\Attribute\AsPeriodicTask;
use Psr\Log\LoggerInterface;
#[AsPeriodicTask('1 hour')]
class EmailNotificationTask
{
    public function __construct(
        private LoggerInterface $logger
    ) {}
    public function __invoke(): void
    {
        try {
            // 检查待发送邮件
            $pendingEmails = $this->getPendingEmails();
            foreach ($pendingEmails as $email) {
                $this->sendEmail($email);
                $this->markAsSent($email);
            }
            $this->logger->info('Email notification task completed', [
                'count' => count($pendingEmails)
            ]);
        } catch (\Exception $e) {
            $this->logger->error('Email notification failed', [
                'error' => $e->getMessage()
            ]);
        }
    }
    private function getPendingEmails(): array
    {
        // 从数据库获取待发送邮件
        return [];
    }
    private function sendEmail(array $email): void
    {
        // 发送邮件逻辑
    }
    private function markAsSent(array $email): void
    {
        // 标记为已发送
    }
}

数据清理任务

// src/Scheduler/CleanupTask.php
namespace App\Scheduler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Scheduler\Attribute\AsCronTask;
#[AsCronTask('0 3 * * *')] // 每天凌晨3点执行
class CleanupTask
{
    public function __construct(
        private EntityManagerInterface $entityManager
    ) {}
    public function __invoke(): void
    {
        // 清理30天前的日志
        $this->cleanOldLogs();
        // 清理过期会话
        $this->cleanExpiredSessions();
        // 清理临时文件
        $this->cleanTempFiles();
    }
    private function cleanOldLogs(): void
    {
        $date = new \DateTime('-30 days');
        $this->entityManager
            ->createQuery('DELETE FROM App\Entity\Log l WHERE l.createdAt < :date')
            ->setParameter('date', $date)
            ->execute();
    }
    private function cleanExpiredSessions(): void
    {
        // 清理过期session
    }
    private function cleanTempFiles(): void
    {
        // 清理临时文件
        $tempDir = sys_get_temp_dir() . '/app';
        if (is_dir($tempDir)) {
            array_map('unlink', glob("$tempDir/*"));
        }
    }
}

最佳实践

错误处理

#[AsPeriodicTask('5 minutes')]
class RobustTask
{
    public function __invoke(): void
    {
        try {
            // 任务逻辑
        } catch (\Exception $e) {
            // 记录日志
            $this->logger->error('Task failed', [
                'exception' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            // 发送告警通知
            $this->notifyAdmin($e);
            // 重新抛出异常(可选)
            throw $e;
        }
    }
}

任务锁

use Symfony\Component\Lock\LockFactory;
#[AsPeriodicTask('10 minutes')]
class LockedTask
{
    public function __construct(
        private LockFactory $lockFactory
    ) {}
    public function __invoke(): void
    {
        $lock = $this->lockFactory->createLock('task-lock');
        if (!$lock->acquire()) {
            // 任务已在运行,跳过
            return;
        }
        try {
            // 执行任务
            $this->doWork();
        } finally {
            $lock->release();
        }
    }
}

监控和日志

#[AsPeriodicTask('1 hour')]
class MonitoredTask
{
    public function __invoke(): void
    {
        $startTime = microtime(true);
        // 执行任务
        $this->doWork();
        $duration = microtime(true) - $startTime;
        // 记录性能指标
        $this->logger->info('Task completed', [
            'duration' => $duration,
            'memory_usage' => memory_get_peak_usage(true)
        ]);
    }
}

注意事项

  1. 确保只有一个实例运行:使用锁机制防止并发执行
  2. 监控执行时间:避免任务重叠执行
  3. 处理异常:确保异常不会导致整个调度器崩溃
  4. 记录日志:便于问题排查和性能监控
  5. 使用Supervisor:保证调度器持续运行
  6. 考虑分布式环境:在多服务器环境中需要使用分布式锁

Symfony Scheduler为PHP项目提供了强大、灵活的任务调度能力,相比传统cron更加易于维护和扩展。

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