# kiri-crontab 任务调度系统 架构设计文档 ## 一、功能概述 基于 Redis + Swoole 的定时任务调度系统,支持: - 定时执行:指定具体时间点执行任务 - 间隔执行:每隔 N 秒/min/hour 重复执行 - cron 表达式:标准 5 字段 cron 表达式调度 - 一次性任务:执行后自动移除 - 任务暂停/恢复:通过 Redis 标记控制任务启停 - **注解驱动**: 使用 `#[Crontab]` 注解声明任务,启动时自动扫描发现 - **事件驱动**: 通过 kiri-core PSR-14 事件系统分发任务生命周期事件 - **幽灵任务清理**: 启动时自动清理 Redis 中已从配置移除的任务 ## 二、核心组件 ``` ┌─────────────────────────────────────────────────────────┐ │ kiri-crontab │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │CrontabCommand │───▶│ CrontabProcess │ │ │ │ (Symfony命令) │ │ (继承AbstractProcess) │ │ └──────────────┘ └────────┬─────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ CrontabScheduler │ │ │ │ (继承Component) │ │ │ └────────┬─────────┘ │ │ │ │ │ ┌─────────▼─────────┐ │ │ │ TaskRegistry │ │ │ │ (任务注册中心) │ │ │ └─────────┬─────────┘ │ │ │ │ │ ┌─────────▼─────────┐ │ │ │ TaskInterface │ │ │ │ (任务接口) │ │ │ └──────────────────┘ │ │ │ │ │ ┌────────────────────────────▼──────────────────────┐ │ │ │ Redis │ │ │ │ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ │ │ Sorted Set │ │ Hash (任务元数据) │ │ │ │ │ │ (调度队列) │ │ crontab:task:{key} │ │ │ │ │ │ crontab:queue │ └──────────────────────────┘ │ │ │ │ └──────────────┘ │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ kiri-core 事件系统 (PSR-14) │ │ │ │ OnTaskBeforeExecute → OnTaskExecuted → OnTaskFailed│ │ │ └────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ ``` ## 三、Redis 数据结构设计 ### 3.1 调度队列 (Sorted Set) ``` Key: crontab:queue Type: ZSET Score: 下次执行时间戳 (Unix timestamp) Member: 任务标识符 (task key) ``` ZSET 按时间戳排序,调度器只需 `ZRANGEBYSCORE` 获取到期任务。 ### 3.2 任务元数据 (Hash) ``` Key: crontab:task:{taskKey} Type: Hash Fields: class - 任务处理类完整路径 (如 App\Task\CleanLogTask) name - 任务显示名称 expression - 调度表达式 (every:60 | cron:*/5 * * * * | daily:03:00 | at:1234567890) next_run - 下次执行时间戳 (秒) last_run - 上次执行时间戳 (秒) status - 状态: active / paused / disabled interval - 执行间隔描述 (可读) created_at - 创建时间戳 ``` ### 3.3 任务执行锁 ``` Key: crontab:lock:task:{taskKey} Type: String Value: Timestamp TTL: 任务超时时间 (默认 300s) ``` 防止同一任务重复执行(前次未完成则跳过本次)。 ### 3.4 调度器主锁 ``` Key: crontab:lock:master Type: String Value: Worker PID TTL: 60s (并发执行期间由独立协程定期续期) ``` 防止多实例同时调度,用 SET NX EX + Lua 脚本实现。 ### 3.5 任务执行状态集合 ``` Key: crontab:running Type: SET Member: 当前正在执行的任务 key 列表 ``` 用于监控哪些任务正在运行中。 ### 3.6 任务移除标记 ``` Key: crontab:removal:{taskKey} Type: String Value: "1" TTL: 60s ``` 当任务内部调用 cancelCurrentTask() 时设置此标记,执行完成后检查并清理。 ## 四、调度表达式 | 格式 | 示例 | 含义 | |------|------|------| | `every:{秒}` | `every:60` | 每 60 秒执行 | | `every:{秒}s` | `every:30s` | 每 30 秒执行 | | `every:{分}m` | `every:5m` | 每 5 分钟执行 | | `every:{时}h` | `every:1h` | 每 1 小时执行 | | `daily:{HH:MM}` | `daily:03:00` | 每天凌晨 3 点 | | `hourly:{MM}` | `hourly:30` | 每小时第 30 分 | | `cron:{表达式}` | `cron:*/5 * * * *` | 标准 5 字段 cron | | `at:{时间戳}` | `at:1719590400` | 指定时间戳一次性 | ## 五、调度流程 ``` ┌──────────────────────────────────────┐ │ 调度器启动 (process) │ └────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────┐ │ 1. 扫描 TaskRegistry 注册的所有任务 │ │ 将未注册到 Redis 的任务写入 Hash │ │ 清理 Redis 中已移除的幽灵任务 │ │ 将任务加入 ZSET 调度队列 │ └────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────┐ │ 主循环 (每 1 秒 tick) │◀────────────────────┐ └──────────┬───────────┘ │ │ │ ▼ │ ┌──────────────────────┐ │ │ 2. 获取主锁 │ │ │ SET NX EX 60s │── 失败 ──────────────┘ └──────────┬───────────┘ │ 成功 ▼ ┌──────────────────────┐ │ 3. ZRANGEBYSCORE │ │ score <= now │── 空 ───────────────┐ │ 获取到期任务列表 │ │ └──────────┬───────────┘ │ │ 有任务 │ ▼ │ ┌──────────────────────────────────────┐ │ │ 4. 遍历到期任务 (协程并发) │ │ │ - 启动主锁续期协程 │ │ │ - 通过 Channel 控制最大并发数 │ │ │ - 获取任务锁 │ │ │ - 分发 OnTaskBeforeExecute 事件 │ │ │ - 执行任务 handle() │ │ │ - 分发 OnTaskExecuted/OnTaskFailed │ │ │ - 计算下次执行时间 │ │ │ - 更新 ZSET 分数 │ │ │ - 停止锁续期协程 │ │ └──────────────────────────────────────┘ │ │ │ ▼ │ ┌──────────────────────┐ │ │ 5. sleep(1) 等待下个 │◀─────────────────────┘ │ tick │ └──────────────────────┘ ``` ## 六、任务生命周期 ``` [注册] ──▶ [待调度] ──▶ [获取锁] ──▶ [执行中] ──▶ [完成] ▲ │ │ │ │ 锁获取失败 │ │ ▼ │ │ [跳过本轮] │ │ │ └───────────────────────────────────────┘ (等待下次调度) 状态流转: active ──▶ paused ──▶ active (暂停/恢复) active ──▶ disabled (禁用,从 ZSET 移除) 一次性任务执行完成后: 从 ZSET 和 Redis 中移除 任务从配置中移除: 启动时自动清理 (幽灵任务清理) ``` ## 七、事件系统 调度器通过 kiri-core PSR-14 事件系统在任务执行的各个阶段分发事件: | 事件 | 触发时机 | 参数 | |------|---------|------| | `OnTaskBeforeExecute` | 任务执行前 | taskKey, className, taskName | | `OnTaskExecuted` | 任务执行成功 | taskKey, className, taskName, duration, nextRun | | `OnTaskFailed` | 任务执行失败 | taskKey, className, taskName, error, duration, nextRun | 监听器注册方式(config/events.php): ```php return [ \Kiri\Crontab\Events\OnTaskFailed::class => [ [App\Listener\CrontabAlertListener::class, 'process'], ], ]; ``` ## 八、与 kiri-core 集成方式 ### 8.1 作为 Composer 包引入 ```json { "require": { "game-worker/kiri-crontab": "^v1.0" } } ``` ### 8.2 通过 Provider 注册命令 CrontabProviders 自动将 `sw:crontab` 命令注册到 Console Application。 ### 8.3 通过 Process 集成 ```php // config/servers.php 中添加 'process' => [ \Kiri\Crontab\CrontabProcess::class, ], ``` CrontabProcess 继承 AbstractProcess,支持框架的生命周期管理(信号处理、优雅退出)。 ### 8.4 独立运行模式 ```bash php bin/crontab start php bin/crontab stop php bin/crontab restart php bin/crontab status ``` ### 8.5 kiri-core 控制台模式 ```bash php kiri.php sw:crontab start|stop|restart|status ``` ### 8.6 注解驱动注册方式 ```php use Kiri\Crontab\Annotate\Crontab; use Kiri\Crontab\TaskInterface; #[Crontab(name: '清理日志', expression: 'daily:03:00')] class CleanLogTask implements TaskInterface { public function handle(): void { // 清理逻辑 } } ``` 任务类实现 TaskInterface 并使用 `#[Crontab]` 注解后,CrontabProcess 启动时自动通过 `get_declared_classes()` 发现并注册。 ## 九、注解扫描流程 ``` ┌──────────────────────────────────────┐ │ CrontabProcess::discoverAnnotationTasks() │ └────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────┐ │ 1. 遍历 get_declared_classes() │ │ 遍历所有已加载的 PHP 类 │ └────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────┐ │ 2. 检查类是否 implements TaskInterface│ │ ──否──▶ 跳过 │ └────────────────┬─────────────────────┘ │ 是 ▼ ┌──────────────────────────────────────┐ │ 3. ReflectionClass::getAttributes( │ │ Crontab::class) 读取注解 │ │ ──无──▶ 跳过 │ └────────────────┬─────────────────────┘ │ 有 ▼ ┌──────────────────────────────────────┐ │ 4. 调用 buildExpression() 生成表达式 │ │ 验证表达式非空 │ │ ──空──▶ 跳过 │ └────────────────┬─────────────────────┘ │ 有效 ▼ ┌──────────────────────────────────────┐ │ 5. TaskRegistry::register([ │ │ 'class' => $className, │ │ 'name' => $crontab->name, │ │ 'expression' => $expr, │ │ 'status' => $crontab->status│ │ ]); │ └──────────────────────────────────────┘ ``` ## 十、依赖关系 ``` kiri-crontab ├── PHP >= 8.5 ├── ext-swoole (协程/进程) ├── ext-redis (Redis 客户端) ├── psr/log (PSR-3 日志) ├── psr/event-dispatcher (PSR-14 事件) └── symfony/console (命令行集成) ``` 与 kiri-core 框架的关系: ``` kiri-crontab (外挂包) ├── 继承 Kiri\Abstracts\Component (CrontabScheduler) ├── 继承 Kiri\Abstracts\Providers (CrontabProviders) ├── 继承 Kiri\Server\Processes\AbstractProcess (CrontabProcess) ├── 使用 Kiri\Events\EventDispatch (事件分发) └── 集成 Symfony\Component\Console\Command\Command ``` ## 十一、目录结构 ``` kiri-crontab/ ├── composer.json ├── DESIGN.md # 本文档 ├── README.md ├── bin/ │ └── crontab # 独立运行入口脚本 ├── config/ │ └── crontab.php # 默认配置 ├── src/ │ ├── TaskInterface.php # 任务接口 │ ├── TaskConfig.php # 任务配置值对象 │ ├── TaskRegistry.php # 任务注册中心 │ ├── CrontabScheduler.php # 核心调度引擎 (继承 Component) │ ├── CrontabProcess.php # Swoole 进程适配器 (继承 AbstractProcess) │ ├── CrontabCommand.php # Symfony 控制台命令 │ ├── CrontabProviders.php # kiri-core Provider 集成 │ ├── CronExpression.php # Cron 表达式解析器 │ ├── functions.php # 全局辅助函数 │ ├── Annotate/ │ │ └── Crontab.php # #[Crontab] 注解类 │ └── Events/ │ ├── OnTaskBeforeExecute.php │ ├── OnTaskExecuted.php │ └── OnTaskFailed.php └── tests/ └── CronExpressionTest.php ```