10 KiB
任务调度器使用说明
MyBoot 内置基于 APScheduler 的任务调度器,支持 Cron、固定间隔与一次性任务。应用启动时,若已注册任务且调度器已启用,会自动启动调度。
1. 快速开始(推荐)
定时任务必须定义在 @component 装饰的类中,由自动配置在组件注册时扫描并加入 app.scheduler。
from myboot.core.decorators import component, cron, interval, once
from myboot.core.config import get_config
@component()
class DataSyncJobs:
"""定时任务组件"""
@cron("0 2 * * *") # 每天 02:00(5 位 Cron)
def sync_daily(self):
print("每日同步")
@interval(minutes=30) # 每 30 分钟
def health_check(self):
print("健康检查")
@once("2025-12-31 23:59:59") # 指定时刻执行一次
def year_end_task(self):
print("年末任务")
@cron("0 */5 * * *", enabled=get_config("jobs.report.enabled", True))
def report(self):
"""enabled 可从配置读取,为 False 时不注册"""
print("报表任务")
要求与约定:
| 项 | 说明 |
|---|---|
| 类装饰器 | 必须使用 @component(),不支持模块级函数或 @service 类中的 @cron |
| 方法可见性 | 以 _ 开头的私有方法不会被扫描 |
| 依赖注入 | 可在组件 __init__ 中注入 @service,在任务方法内使用 |
| 包扫描 | 任务类需位于自动发现包内(默认 app),见项目启动与 auto_discover 配置 |
应用生命周期中,存在已注册任务且调度器启用时,会在启动钩子阶段调用 scheduler.start(),关闭时 scheduler.stop()。
2. 配置
在 config.yaml 或环境变量中配置(环境变量嵌套键使用双下划线 __,详见 配置管理使用说明)。
scheduler:
enabled: true # 是否允许启动调度器
timezone: "Asia/Shanghai" # 任务触发时区(建议显式设置)
max_workers: 10 # 线程池大小
on_all_workers: false # 多 worker 时是否在非 primary 进程也启用
| 配置项 | 环境变量示例 | 默认值 | 说明 |
|---|---|---|---|
scheduler.enabled |
SCHEDULER__ENABLED=false |
true |
为 false 时调度器不启动 |
scheduler.timezone |
SCHEDULER__TIMEZONE=Asia/Shanghai |
UTC |
所有触发器使用的时区 |
scheduler.max_workers |
SCHEDULER__MAX_WORKERS=20 |
10 |
并发执行任务的上限 |
scheduler.on_all_workers |
SCHEDULER__ON_ALL_WORKERS=true |
false |
见下文「多 Worker」 |
config.py 中默认片段:
scheduler:
enabled: true
timezone: "UTC"
max_workers: 10
3. Cron 表达式
解析逻辑见 myboot/core/scheduler.py 中 _parse_cron:
- 优先使用
CronTrigger.from_crontab(5 位,Unix/APScheduler 风格) - 解析失败时回退为手动构造
CronTrigger(支持 5 位 或 6 位)
3.1 五位格式(推荐)
顺序:分 · 时 · 日 · 月 · 周
分 时 日 月 周
│ │ │ │ └── 星期(0=周一 … 6=周日,见下表)
│ │ │ └────── 月(1-12 或 *)
│ │ └────────── 日(1-31 或 *)
│ └────────────── 时(0-23)
└────────────────── 分(0-59)
星期字段(APScheduler from_crontab)与 Linux crontab 不同:
| 值 | APScheduler(本项目 5 位默认路径) | 传统 Linux crontab |
|---|---|---|
| 0 | 周一 | 周日 |
| 1 | 周二 | 周一 |
| 2 | 周三 | 周二 |
| … | … | … |
| 6 | 周日 | 周六 |
使用 5 位表达式时,请按 APScheduler 星期编号理解,避免与系统
crontab的「周日=0」混淆。
常用示例(5 位,时区以 scheduler.timezone 为准):
| 表达式 | 含义 |
|---|---|
0 * * * * |
每小时整点 |
0 2 * * * |
每天 02:00 |
0 23 * * 1 |
每周二 23:00(周字段 1 = 周二) |
30 8 * * 0 |
每周一 08:30 |
*/15 * * * * |
每 15 分钟 |
0 9-17 * * 1-5 |
工作日 09:00–17:00 每小时整点 |
3.2 六位格式(兼容旧写法)
顺序:秒 · 分 · 时 · 日 · 月 · 周
仅在 from_crontab 无法解析时走手动分支,例如:
| 表达式 | 含义 |
|---|---|
0 0 * * * * |
每小时整点(秒=0,分=0) |
0 */5 * * * * |
每 5 分钟 |
0 0 2 * * * |
每天 02:00:00 |
3.3 字段支持
支持 APScheduler 常见写法:*、,、-、/ 及范围。复杂表达式以 APScheduler Cron 文档 为准。
4. 装饰器 API
4.1 @cron
@cron(cron_expression: str, enabled: bool | None = None, **kwargs)
cron_expression:5 位或 6 位 Cron 字符串enabled:False时跳过注册;None默认启用**kwargs:传给 APScheduleradd_job的额外参数(如name、max_instances)
4.2 @interval
@interval(seconds=None, minutes=None, hours=None, enabled=None, **kwargs)
三者至少指定其一,内部统一换算为秒。例如 @interval(minutes=5) 每 5 分钟执行。
4.3 @once
@once(run_date: str, enabled=None, **kwargs)
run_date 支持格式:
YYYY-MM-DD HH:MM:SSYYYY-MM-DD HH:MMYYYY-MM-DD(当天 00:00:00)
时间为 naive,由调度器全局时区 scheduler.timezone 解释。
5. 多 Worker 与进程模型
多进程部署(server.workers > 1)时:
- 默认仅 primary worker(
MYBOOT_IS_PRIMARY_WORKER=1)启用调度器,避免重复执行 - 设置
scheduler.on_all_workers: true可在每个 worker 都运行调度(一般仅特殊场景需要)
任务在线程池中执行;max_instances 默认为 3(APScheduler job_defaults),同一任务并发实例数受此限制。
6. 编程式 API
除装饰器外,可通过 app.scheduler 或 get_scheduler() 动态管理任务。
from myboot.core.scheduler import get_scheduler
scheduler = get_scheduler() # 注意:与 Application 可能不是同一实例,推荐用 app.scheduler
job_id = scheduler.add_cron_job(func=my_func, cron="0 2 * * *")
scheduler.add_interval_job(func=my_func, interval=60)
scheduler.add_date_job(func=my_func, run_date="2025-12-31 23:59:59")
scheduler.remove_job(job_id)
info = scheduler.get_job_info(job_id)
all_jobs = scheduler.list_all_jobs()
| 方法 | 说明 |
|---|---|
add_cron_job(func, cron, job_id=None, **kwargs) |
添加 Cron 任务 |
add_interval_job(func, interval, job_id=None, **kwargs) |
interval 单位为秒 |
add_date_job(func, run_date, job_id=None, **kwargs) |
一次性任务 |
remove_job(job_id) |
移除任务 |
get_job_info(job_id) |
任务类型、下次执行时间等 |
list_all_jobs() |
所有任务摘要 |
start() / stop() |
启停(应用生命周期通常自动处理) |
is_enabled() / is_running() / has_jobs() |
状态查询 |
get_config() |
当前调度器配置摘要 |
未指定 job_id 时,默认生成 cron_{模块名}.{限定名}(如
cron_app.jobs.SyncJobs.daily_sync),interval_/date_ 前缀同理。
限定名包含类名,因此不同类中的同名方法不会冲突(0.2.0 起,issue #14;
此前为 cron_{函数名},同名方法会因 ID 冲突导致启动失败)。
注册时即校验 ID 唯一性:显式传入重复的 job_id 会抛出 SchedulerError;
自动生成的 ID 重复(同一函数注册多次)会追加 8 位随机后缀并记录 warning。
6.1 ScheduledJob 类
继承 myboot.jobs.scheduled_job.ScheduledJob 可实现带重试、状态跟踪的任务,并通过 add_scheduled_job 注册:
from myboot.jobs.scheduled_job import ScheduledJob
class CleanupJob(ScheduledJob):
def run(self):
# 业务逻辑
return "ok"
job = CleanupJob(name="cleanup", trigger="0 3 * * *") # 或 trigger={'type': 'cron', 'cron': '...'}
app.scheduler.add_scheduled_job(job)
trigger 可为 Cron 字符串或字典:cron / interval(seconds/minutes/hours/days)/ date(run_date)。
7. 运维与排查
# 应用内
print(app.scheduler.get_config())
for job in app.scheduler.list_all_jobs():
print(job["job_id"], job.get("type"), job.get("next_run_time"))
# 健康检查等可结合 application 状态中的 scheduler 字段
日志:调度器绑定 logger 名 scheduler,注册任务时会输出 已添加 Cron 任务 等信息。
任务未执行时检查:
scheduler.enabled是否为true- 多 worker 下当前进程是否为 primary(或已开启
on_all_workers) - 装饰器
enabled=False是否被跳过 - Cron 位数与星期编号是否符合上文约定
timezone是否与预期一致- 应用启动时
has_jobs()是否为真(无任务不会start())
8. 最佳实践
- 统一用 5 位 Cron,减少与 6 位混用带来的理解成本。
- 显式设置
scheduler.timezone(如Asia/Shanghai),避免默认 UTC 造成「时间差 8 小时」。 - 任务逻辑保持幂等;错过触发时 APScheduler 有
misfire_grace_time(默认 30 秒),但仍可能补跑。 - 长耗时任务注意
max_workers与max_instances,避免占满线程池。 - 配置开关用
enabled=get_config('jobs.xxx.enabled', True),便于按环境关闭任务。 - IO 密集或阻塞操作在任务内自行控制超时与异常,避免拖垮调度线程。
9. 相关文档与代码
| 资源 | 说明 |
|---|---|
| scheduler_refactor_analysis.md | APScheduler 重构与能力对照(偏设计) |
| dependency-injection.md | 含定时任务的 @component 示例 |
myboot/core/scheduler.py |
调度器实现 |
myboot/core/decorators.py |
@cron / @interval / @once |
examples/convention_app.py |
完整示例 ScheduledJobs 组件 |