322 lines
14 KiB
Markdown
322 lines
14 KiB
Markdown
|
|
# kiri-crontab 任务调度系统 架构设计文档
|
||
|
|
|
||
|
|
## 一、功能概述
|
||
|
|
|
||
|
|
基于 Redis + Swoole 的定时任务调度系统,支持:
|
||
|
|
- 定时执行:指定具体时间点执行任务
|
||
|
|
- 间隔执行:每隔 N 秒/min/hour 重复执行
|
||
|
|
- cron 表达式:标准 5 字段 cron 表达式调度
|
||
|
|
- 一次性任务:执行后自动移除
|
||
|
|
- 任务暂停/恢复:通过 Redis 标记控制任务启停
|
||
|
|
- **注解驱动**: 使用 `#[Crontab]` 注解声明任务,支持自动扫描发现
|
||
|
|
|
||
|
|
## 二、核心组件
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────────────────────┐
|
||
|
|
│ kiri-crontab │
|
||
|
|
├─────────────────────────────────────────────────────────┤
|
||
|
|
│ │
|
||
|
|
│ ┌──────────────┐ ┌──────────────────┐ │
|
||
|
|
│ │CrontabCommand │───▶│ CrontabScheduler │ │
|
||
|
|
│ │ (控制台命令) │ │ (调度引擎) │ │
|
||
|
|
│ └──────────────┘ └────────┬─────────┘ │
|
||
|
|
│ │ │
|
||
|
|
│ ┌─────────▼─────────┐ │
|
||
|
|
│ │ TaskRegistry │ │
|
||
|
|
│ │ (任务注册中心) │ │
|
||
|
|
│ └─────────┬─────────┘ │
|
||
|
|
│ │ │
|
||
|
|
│ ┌─────────▼─────────┐ │
|
||
|
|
│ │ TaskInterface │ │
|
||
|
|
│ │ (任务接口) │ │
|
||
|
|
│ └──────────────────┘ │
|
||
|
|
│ │ │
|
||
|
|
│ ┌────────────────────────────▼──────────────────────┐ │
|
||
|
|
│ │ Redis │ │
|
||
|
|
│ │ ┌──────────────┐ ┌──────────────────────────┐ │ │
|
||
|
|
│ │ │ Sorted Set │ │ Hash (任务元数据) │ │ │
|
||
|
|
│ │ │ (调度队列) │ │ crontab:task:{key} │ │ │
|
||
|
|
│ │ │ crontab:queue │ └──────────────────────────┘ │ │
|
||
|
|
│ │ └──────────────┘ │ │
|
||
|
|
│ └────────────────────────────────────────────────────┘ │
|
||
|
|
│ │
|
||
|
|
└─────────────────────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
## 三、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: Swoole Worker ID + 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 列表
|
||
|
|
```
|
||
|
|
|
||
|
|
用于监控哪些任务正在运行中。
|
||
|
|
|
||
|
|
## 四、调度表达式
|
||
|
|
|
||
|
|
| 格式 | 示例 | 含义 |
|
||
|
|
|------|------|------|
|
||
|
|
| `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 │
|
||
|
|
│ 将任务加入 ZSET 调度队列 │
|
||
|
|
└────────────────┬─────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌──────────────────────┐
|
||
|
|
│ 主循环 (每 1 秒 tick) │◀────────────────────┐
|
||
|
|
└──────────┬───────────┘ │
|
||
|
|
│ │
|
||
|
|
▼ │
|
||
|
|
┌──────────────────────┐ │
|
||
|
|
│ 2. 获取主锁 │ │
|
||
|
|
│ SET NX EX 60s │── 失败 ──────────────┘
|
||
|
|
└──────────┬───────────┘
|
||
|
|
│ 成功
|
||
|
|
▼
|
||
|
|
┌──────────────────────┐
|
||
|
|
│ 3. ZRANGEBYSCORE │
|
||
|
|
│ score <= now │── 空 ───────────────┐
|
||
|
|
│ 获取到期任务列表 │ │
|
||
|
|
└──────────┬───────────┘ │
|
||
|
|
│ 有任务 │
|
||
|
|
▼ │
|
||
|
|
┌──────────────────────┐ │
|
||
|
|
│ 4. 遍历到期任务 │ │
|
||
|
|
│ - 获取任务锁 │ │
|
||
|
|
│ - 执行任务 handle() │ │
|
||
|
|
│ - 记录日志/更新状态 │◀─────────────────────┘
|
||
|
|
│ - 计算下次执行时间 │
|
||
|
|
│ - 更新 ZSET 分数 │
|
||
|
|
└──────────┬───────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌──────────────────────┐
|
||
|
|
│ 5. sleep(1) 等待下个 │
|
||
|
|
│ tick │
|
||
|
|
└──────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
## 六、任务生命周期
|
||
|
|
|
||
|
|
```
|
||
|
|
[注册] ──▶ [待调度] ──▶ [获取锁] ──▶ [执行中] ──▶ [完成]
|
||
|
|
▲ │ │
|
||
|
|
│ │ 锁获取失败 │
|
||
|
|
│ ▼ │
|
||
|
|
│ [跳过本轮] │
|
||
|
|
│ │
|
||
|
|
└───────────────────────────────────────┘
|
||
|
|
(等待下次调度)
|
||
|
|
|
||
|
|
状态流转:
|
||
|
|
active ──▶ paused ──▶ active (暂停/恢复)
|
||
|
|
active ──▶ disabled (禁用,从 ZSET 移除)
|
||
|
|
一次性任务执行完成后: 从 ZSET 和 Redis 中移除
|
||
|
|
```
|
||
|
|
|
||
|
|
## 七、与 kiri-core 集成方式
|
||
|
|
|
||
|
|
### 7.1 作为 Composer 包引入
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"require": {
|
||
|
|
"game-worker/kiri-crontab": "^v1.0"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.2 通过 Provider 注册到框架
|
||
|
|
```php
|
||
|
|
// config/servers.php 中添加
|
||
|
|
'process' => [
|
||
|
|
\Kiri\Crontab\CrontabProcess::class,
|
||
|
|
],
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.3 独立运行模式
|
||
|
|
```bash
|
||
|
|
php bin/crontab start
|
||
|
|
php bin/crontab stop
|
||
|
|
php bin/crontab restart
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.4 注解驱动注册方式
|
||
|
|
```php
|
||
|
|
use Kiri\Crontab\Annotate\Crontab;
|
||
|
|
use Kiri\Crontab\TaskInterface;
|
||
|
|
|
||
|
|
#[Crontab(name: '清理日志', expression: 'daily:03:00')]
|
||
|
|
class CleanLogTask implements TaskInterface
|
||
|
|
{
|
||
|
|
public function handle(): void
|
||
|
|
{
|
||
|
|
// 清理逻辑
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
在配置中指定扫描路径:
|
||
|
|
```php
|
||
|
|
// config/crontab.php
|
||
|
|
'scan_paths' => [
|
||
|
|
'app/Task',
|
||
|
|
'app/Crontab',
|
||
|
|
],
|
||
|
|
```
|
||
|
|
|
||
|
|
## 八、注解扫描流程
|
||
|
|
|
||
|
|
```
|
||
|
|
┌──────────────────────────────────────┐
|
||
|
|
│ CrontabScanner::scan($directory) │
|
||
|
|
└────────────────┬─────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌──────────────────────────────────────┐
|
||
|
|
│ 1. 递归遍历目录下所有 PHP 文件 │
|
||
|
|
│ 跳过 vendor/tests/cache/storage │
|
||
|
|
└────────────────┬─────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌──────────────────────────────────────┐
|
||
|
|
│ 2. require_once 加载文件 │
|
||
|
|
│ get_declared_classes() 获取新类 │
|
||
|
|
└────────────────┬─────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌──────────────────────────────────────┐
|
||
|
|
│ 3. 检查类是否 implements TaskInterface│
|
||
|
|
│ ──否──▶ 跳过 │
|
||
|
|
└────────────────┬─────────────────────┘
|
||
|
|
│ 是
|
||
|
|
▼
|
||
|
|
┌──────────────────────────────────────┐
|
||
|
|
│ 4. ReflectionClass::getAttributes( │
|
||
|
|
│ Crontab::class) 读取注解 │
|
||
|
|
│ ──无──▶ 跳过 │
|
||
|
|
└────────────────┬─────────────────────┘
|
||
|
|
│ 有
|
||
|
|
▼
|
||
|
|
┌──────────────────────────────────────┐
|
||
|
|
│ 5. $registry->register([ │
|
||
|
|
│ 'class' => $className, │
|
||
|
|
│ 'name' => $crontab->name, │
|
||
|
|
│ 'expression' => $crontab->expr, │
|
||
|
|
│ 'status' => $crontab->status, │
|
||
|
|
│ ]); │
|
||
|
|
└──────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
## 九、依赖关系
|
||
|
|
|
||
|
|
```
|
||
|
|
kiri-crontab
|
||
|
|
├── PHP >= 8.5
|
||
|
|
├── ext-swoole (协程/进程)
|
||
|
|
├── ext-redis (Redis 客户端)
|
||
|
|
├── psr/log (PSR-3 日志)
|
||
|
|
└── symfony/console (可选,如集成 kiri-core 则复用项目已有的)
|
||
|
|
```
|
||
|
|
|
||
|
|
## 十、目录结构
|
||
|
|
|
||
|
|
```
|
||
|
|
kiri-crontab/
|
||
|
|
├── composer.json
|
||
|
|
├── DESIGN.md # 本文档
|
||
|
|
├── README.md
|
||
|
|
├── bin/
|
||
|
|
│ └── crontab # 独立运行入口脚本
|
||
|
|
├── config/
|
||
|
|
│ └── crontab.php # 默认配置
|
||
|
|
├── src/
|
||
|
|
│ ├── TaskInterface.php # 任务接口
|
||
|
|
│ ├── TaskConfig.php # 任务配置值对象
|
||
|
|
│ ├── TaskRegistry.php # 任务注册中心
|
||
|
|
│ ├── CrontabScheduler.php # 核心调度引擎
|
||
|
|
│ ├── CrontabProcess.php # Swoole 进程适配器
|
||
|
|
│ ├── CrontabCommand.php # 控制台命令
|
||
|
|
│ ├── CrontabScanner.php # 注解任务扫描器
|
||
|
|
│ ├── CrontabProviders.php # kiri-core Provider 集成
|
||
|
|
│ ├── CronExpression.php # Cron 表达式解析器
|
||
|
|
│ ├── Annotate/
|
||
|
|
│ │ └── Crontab.php # #[Crontab] 注解类
|
||
|
|
│ └── Events/
|
||
|
|
│ ├── OnTaskBeforeExecute.php
|
||
|
|
│ ├── OnTaskExecuted.php
|
||
|
|
│ └── OnTaskFailed.php
|
||
|
|
└── tests/
|
||
|
|
└── CronExpressionTest.php
|
||
|
|
```
|