17 KiB
17 KiB
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):
return [
\Kiri\Crontab\Events\OnTaskFailed::class => [
[App\Listener\CrontabAlertListener::class, 'process'],
],
];
八、与 kiri-core 集成方式
8.1 作为 Composer 包引入
{
"require": {
"game-worker/kiri-crontab": "^v1.0"
}
}
8.2 通过 Provider 注册命令
CrontabProviders 自动将 sw:crontab 命令注册到 Console Application。
8.3 通过 Process 集成
// config/servers.php 中添加
'process' => [
\Kiri\Crontab\CrontabProcess::class,
],
CrontabProcess 继承 AbstractProcess,支持框架的生命周期管理(信号处理、优雅退出)。
8.4 独立运行模式
php bin/crontab start
php bin/crontab stop
php bin/crontab restart
php bin/crontab status
8.5 kiri-core 控制台模式
php kiri.php sw:crontab start|stop|restart|status
8.6 注解驱动注册方式
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