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

14 KiB

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 包引入

{
  "require": {
    "game-worker/kiri-crontab": "^v1.0"
  }
}

7.2 通过 Provider 注册到框架

// config/servers.php 中添加
'process' => [
    \Kiri\Crontab\CrontabProcess::class,
],

7.3 独立运行模式

php bin/crontab start
php bin/crontab stop
php bin/crontab restart

7.4 注解驱动注册方式

use Kiri\Crontab\Annotate\Crontab;
use Kiri\Crontab\TaskInterface;

#[Crontab(name: '清理日志', expression: 'daily:03:00')]
class CleanLogTask implements TaskInterface
{
    public function handle(): void
    {
        // 清理逻辑
    }
}

在配置中指定扫描路径:

// 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