Files
kiri-crontab/DESIGN.md
T
2026-06-28 17:31:59 +08:00

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