Files
ulthon_admin/.agents/skills/ulthon-timer/SKILL.md
augushong 6dceb028b3 docs(timer): 更新 SKILL.md 和 PROJECT.md 多节点协调文档
T13: E2E single-node regression - all PASS
T14: E2E multi-node coordination - 7/7 PASS
T15: Document multi-node features in ulthon-timer SKILL.md and PROJECT.md
2026-05-26 18:33:43 +08:00

194 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: "ulthon-timer"
description: "内置秒级定时器php think timer的使用与扩展规范用于新增/调整定时任务site/call、并发分片、TimerController 防刷、timer.mode normal/parallel。"
---
# timer内置秒级定时器
## 核心机制(你要记住的 3 件事)
1) 任务配置统一从 `app_file_path('common/command/timer/config.php')` 读取
2) 每个配置会按 `concurrency` 自动展开成多份任务实例,并自动注入 `concurrency_id` / `concurrency_count`
3) “是否该执行”主要由定时器侧的 Cache 节流控制(可选叠加控制器侧的防刷保护)
相关实现入口:
- 系统入口(优先从 app 层理解):
- 定时器命令入口:`app/common/command/Timer.php`(实现继承自 `extend/base/common/command/TimerBase.php`
- 任务实例服务入口:`app/common/service/TimerService.php`(实现继承自 `extend/base/common/service/TimerServiceBase.php`
- site 任务控制器基类入口:`app/common/controller/TimerController.php`(实现继承自 `extend/base/common/controller/TimerControllerBase.php`
- 实现文件(需要深入机制时再看 Base
- [Timer.php](../../../app/common/command/Timer.php) / [TimerBase.php](../../../extend/base/common/command/TimerBase.php)
- [TimerService.php](../../../app/common/service/TimerService.php) / [TimerServiceBase.php](../../../extend/base/common/service/TimerServiceBase.php)
- [TimerController.php](../../../app/common/controller/TimerController.php) / [TimerControllerBase.php](../../../extend/base/common/controller/TimerControllerBase.php)
- 运行模式配置:[timer.php](../../../config/timer.php)
## 新增定时任务(默认规则)
无特殊情况下,新增定时任务应当通过本机制实现:在 `timer/config.php` 注册任务 + 以 `site``call` 的方式实现目标逻辑。
### 1) 选择任务类型
- `site`:通过 HTTP 访问一个控制器地址(默认优先使用)。即使任务需要“长时间运行”,也尽量设计成 `site` 模式(分片/分批/可重入),以复用框架的页面/接口同体、鉴权、日志、事务、限流等能力。
- `call`:直接执行一个 PHP callable不推荐除非万不得已。仅在确实不适合走 HTTP 上下文、且不希望暴露为控制器入口时使用。
长时间运行任务的推荐写法(仍用 `site`
- 设计为“可重入”的短任务:每次 `do()` 只处理一小批数据,处理进度写入缓存/表,下次继续
- 结合 `concurrency` 做分片:按 `$this->concurrencyId` 划分数据范围,多个实例并行推进
- 结合 `frequency` 控制节奏:用调度频率限制整体吞吐,避免单次占用过久
### 2) 编写任务目标target
#### A. site 类型HTTP 任务)
实现方式建议:
- 框架使用者(做业务):仅在 `app/tools/controller/timer/` 新增控制器 `*.php` 即可;不需要、也不应该在 `extend/base/` 增加 `*Base.php`
- 框架作者(维护内核):在 `extend/base/tools/controller/timer/` 新增 `*Base.php` 作为默认实现,同时在 `app/tools/controller/timer/` 增加同名入口类 `*.php` 继承 Base系统唯一调用入口
- 控制器建议继承 `app\common\controller\TimerController`(以获得并发参数校验与可选的 `$frequency` 防刷);执行入口一般提供 `do()`
示例(已有实现可参考):[ClearLog.php](../../../app/tools/controller/timer/ClearLog.php)、[ClearLogBase.php](../../../extend/base/tools/controller/timer/ClearLogBase.php)
并发与防刷建议:
- 如需并发分片处理,在控制器中设置:
- `protected $concurrency = N;`
- 使用 `$this->concurrencyId` 做分片编号0 ~ N-1
- 如希望防止外部重复请求(不仅是定时器自身节流),在控制器中设置:
- `protected $frequency = 秒数;`
- 这会启用 `TimerControllerBase::protectVisit()` 基于 URL 的防刷限制
#### B. call 类型(函数任务)
目标形态:
- `target``call_user_func` 可执行的 callable例如`[SomeService::class, 'method']`、闭包等
- 由于 Base/App 双层机制的入口要求,类引用应指向 `app/` 下的入口类(由入口类继承 Base 实现)
- 长时间/重任务不建议用 `call`:它缺少 HTTP 上下文与控制器层通用能力,排错与复用成本更高
### 3) 注册到任务配置timer/config.php
默认配置文件位置(支持分层覆盖):
- 优先覆盖:`app/common/command/timer/config.php`(一旦存在,`app_file_path(...)` 将优先读取此文件)
- 框架默认:`extend/base/common/command/timer/config.php`(当 app 未提供时回落到该文件)
字段说明(兜底默认值由 Base 层的 `initConfigItem()` 提供):
- `name`:任务唯一名称(用于 Cache key不可重复
- `type``site``call`
- `target`
- `site`:以 `/` 开头的相对路径(建议指向 `tools/timer.*` 控制器的 `do` 方法)
- `call`callable
- `frequency`:执行频率(秒),小于 0 会被修正为 0
- `concurrency`:并发数量(默认 1`site` 类型会自动把并发参数写入 query
- `run_type`:调度策略(仅对 `site` 类型生效,`call` 类型不受影响)。可选值见「多节点协调 > run_type 调度」
示例site
```php
return [
[
'name' => 'clear_log',
'type' => 'site',
'target' => '/tools/timer.ClearLog/do',
'frequency' => 600,
'concurrency' => 1,
],
];
```
示例call
```php
return [
[
'name' => 'system_host_register',
'type' => 'call',
'target' => [\app\common\service\HostService::class, 'heartbeat'],
'frequency' => 30,
],
];
```
## 运行与验证
### 运行命令
- 常规运行:`php think timer`
- 只跑一轮(便于验证):`php think timer --temp`
- 无任务时不输出“no request”`php think timer --quit`
### 本地调试(指定请求 Host
site 任务会按站点域名发起请求,默认从 `sysconfig('site','site_domain')` 读取。
- 本地调试:`php think timer --local --local-host=http://localhost --local-port=8000`
### 运行模式
配置在 [timer.php](../../../config/timer.php)
- `normal`:单进程循环 + Guzzle async默认
- `parallel`Workerman 多进程模式(并发更高,相关连接参数在 `timer.php` 中)
## 常见坑位(快速自检)
- `name` 重复:会导致 Cache key 冲突,表现为任务“莫名其妙不跑/跑得不对”
- `concurrency` 与控制器侧 `$concurrency` 不一致:会触发 `concurrency id/count error`
- 只依赖控制器侧 `$frequency`:它只是防刷,不是调度;调度频率以定时器侧 Cache 节流为准
## 多节点协调
定时器支持多节点部署以数据库MySQL作为协调中心。多个节点连接同一个数据库即可自动组成集群。
### 节点注册
每个节点启动后通过 `system_host_register` call 任务自动注册心跳(每 30 秒一次)。节点 ID 生成规则为 `{hostname}-{8位md5}`,持久化在 `runtime/node_id.lock` 文件中,重启后保持不变。
### 主节点选举
- 第一个注册的节点自动成为主节点
- 管理员可在主机列表页面手动切换主节点
- 相关 API`HostService::setMasterNode()` / `HostService::getMasterNode()`
### run_type 调度
`run_type` 仅对 `site` 类型任务生效,`call` 类型任务始终在所有节点执行。可选值:
| run_type | 行为 | 适用场景 |
|----------|------|----------|
| `auto`(默认) | 两阶段 DB 行锁竞争BEGIN / SELECT FOR UPDATE / UPDATE / COMMIT 抢占,抢占成功后释放锁再执行。每个频率窗口内只有一个节点执行 | 通用场景 |
| `main` | 仅主节点执行 | 需要集中处理的任务 |
| `all` | 所有节点各自独立执行 | 节点本地清理等 |
| `manual` | 仅当 DB 中 `manual_trigger=1` 时执行,执行后自动重置为 0。通过管理后台触发 | 运维手动触发 |
### 配置同步
`TimerService::syncConfigToDatabase()` 在定时器启动时运行,将 PHP 配置文件中的任务同步到 `system_timer_config` 表。同步时不会覆盖数据库中已管理的字段(`run_type``status`),以管理后台的设置为准。
### 执行日志
`TimerControllerBase` 提供 `logStart()` / `logEnd()` 方法记录执行日志。`host_id` 会自动注入到 site 任务的 URL 参数中,用于标识执行节点。
### 管理后台
| 页面 | 路径 | 功能 |
|------|------|------|
| 定时器配置 | `/admin/system.timer_config/index` | 管理 run_type、status、手动触发 |
| 定时器日志 | `/admin/system.timer_log/index` | 只读执行日志查看 |
| 主机列表 | `/admin/system.host/index` | 节点状态、主节点管理 |
### 日志清理
```bash
php think admin:timer:log:clean --days=30
```
### 相关数据表
- `ul_system_timer_config`任务协调配置run_type、status、manual_trigger 等)
- `ul_system_timer_log`:执行日志记录
- `ul_system_host`:新增 `is_master` 字段,标识主节点