ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Egg + tegg 定时任务(Schedule Controller)完全指南:装饰器声明、interval/cron 模式与 worker/all 执行策略

Egg + tegg 定时任务(Schedule Controller)完全指南:装饰器声明、interval/cron 模式与 worker/all 执行策略 后端Web框架【免费下载链接】egg Born to build better enterprise frameworks and apps with Node.js Koa. https://307.run/eggcode项目地址https://gitcode.com/gh_mirrors/eg/egg点击查看免费下载导读本文围绕 tegg 提供的Schedule装饰器定时任务能力展开讲解如何在 Egg 应用中以声明式方式注册普通定时任务包括interval固定间隔与cron表达式两种触发模式、worker与all两种执行策略、immediate/disable/env三个任务参数以及其底层agent 调度 messenger 派发 worker 执行的实现原理。读完本文你将能够在任意 Egg tegg 项目中安全、规范地编写可灰度、可开关、可多环境控制的定时任务并理解其与框架自带app/schedule定时任务的差异。使用场景普通定时任务tegg 的Schedule装饰器支持普通定时任务其核心特征是在每一台部署机器上都会执行。例如一个应用在生产环境最少部署 2 台机器那么该定时任务在这 2 台机器上都会运行调度逻辑具体由worker/all模式决定每个 worker 是否执行。典型适用场景包括每台机器各自的数据清理、本地缓存预热、按机器维度执行的心跳上报等。如果需要全局只执行一次的分布式任务则不属于本文讨论的普通定时任务范畴。快速上手开启插件tegg 的定时任务插件是egg 内置插件默认开启。若需显式声明可在config/plugin.ts或config/plugin.js中配置export default { teggSchedule: true, };目录约定与重要警告:::warning ⚠️不要将代码写在app/schedule路径下。egg 框架默认会扫描该路径并自动注册为传统格式的定时任务与 tegg 的装饰器方式会产生冲突。 :::tegg 的定时任务控制器应放在app/port/schedule目录或其他 tegg 约定的业务目录下通过Schedule装饰器声明。控制器结构要求使用Schedule装饰器标识一个类为定时任务控制器后该类必须包含一个名称为subscribe的方法。框架调度定时任务执行时会调用该注解类的subscribe方法。方法签名对应 tegg 定义的ScheduleSubscriber接口export interface ScheduleSubscriber { subscribe(data?: any): Promiseany; }subscribe应为 async 方法。从 load_schedule.ts 的实现可见传统方式下 generator function 形式的任务会被直接拒绝schedule generator function is not support, should use async function instead装饰器方式同样建议遵循异步函数约定。interval 模式按固定间隔执行普通定时任务可配置为interval模式即每间隔指定的时间在每台机器上执行一次。间隔时间通过Schedule装饰器的scheduleData.interval参数设置interval传值数字类型时单位为毫秒数例如100表示每 100ms 执行一次interval传值字符类型时会通过ms工具转换为毫秒数例如5s表示每 5 秒执行一次。// app/port/schedule/Demo.ts import { Inject, Logger } from egg; import { IntervalParams, Schedule, ScheduleType } from egg/schedule; ScheduleIntervalParams({ type: ScheduleType.WORKER, scheduleData: { interval: 100, // 每 100ms 执行一次 // interval: 5s, // 每 5s 执行一次 }, }) export class IntervalScheduler { Inject() private logger: Logger; async subscribe() { this.logger.info(schedule called); } }底层实现interval 如何被解析从 timer.ts 的实现可以看到interval在getNextTick()中通过ms(this.scheduleConfig.interval)统一换算为毫秒若同时配置了cron则优先按 cron 计算下一次触发时间。换算后使用safeTimers进行调度protected safeTimeout(handler: () void, delay: number, ...args: any[]): number | ReturnTypetypeof setTimeout { const fn delay safeTimers.maxInterval ? setTimeout : safeTimers.setTimeout; return fn(handler, delay, ...args) as number | ReturnTypetypeof setTimeout; }当间隔超过safeTimers.maxInterval约 24.8 天时会退化为使用safe-timers避免 setTimeout 的 32 位毫秒溢出问题——因此超长间隔如数月一次也能正确执行。cron 模式按 cron 表达式执行普通定时任务同样支持 cron 表达式模式表达式规则由cron-parser解析。cron 表达式共 6 段秒为可选段* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun) │ │ │ │ └───── month (1 - 12) │ │ │ └────────── day of month (1 - 31) │ │ └─────────────── hour (0 - 23) │ └──────────────────── minute (0 - 59) └───────────────────────── second (0 - 59, optional)例如下列代码将会每日 3 点在每台机器上执行一次// app/port/schedule/CronDemo.ts import { Inject, Logger } from egg; import { CronParams, Schedule, ScheduleType } from egg/schedule; ScheduleCronParams({ type: ScheduleType.WORKER, scheduleData: { // 每日 3 点执行一次 cron: 0 0 3 * * *, // 每 5 秒执行一次 // cron: */5 * * * * *, }, }) export class CronSubscriber { Inject() private logger: Logger; async subscribe() { this.logger.info(schedule called); } }cron 解析与越界处理在 timer.ts 的构造函数中cron会通过cronParser.parseExpression(cron, cronOptions)预解析解析失败会抛出带 key 信息的TypeErrorthrow new TypeError([eggjs/schedule] ${this.key} parse cron instruction(${cron}) error: ${err.message}, { cause: err, });每次触发后getNextTick()会循环调用cronInstance.next()找到下一个未来时间点若表达式超出时间范围Out of the timespan range则记录日志并停止调度do { try { const nextInterval this.cronInstance.next(); nextTick nextInterval.getTime(); } catch (err) { this.logger.info([Timer] ${this.key} cron out of the timespan range, error: %s, err); return; } } while (now nextTick); return nextTick - now;同时cronOptions支持透传cron-parser的ParserOptions如currentDate、startDate、endDate、tz时区等可通过scheduleData.cronOptions配置。工作模式worker 与 all普通定时任务一般使用worker模式框架也提供all模式二者差异如下worker模式每台机器上只有一个 worker会执行该定时任务每次执行时 worker 的选择是随机的all模式每台机器上的每个 worker 都会执行该定时任务。import { Inject, Logger } from egg; import { IntervalParams, Schedule, ScheduleType } from egg/schedule; ScheduleIntervalParams({ type: ScheduleType.ALL, // 所有 worker 都会执行 scheduleData: { interval: 100, }, }) export class AllScheduler { Inject() private logger: Logger; async subscribe() { this.logger.info(schedule called); } }两种模式的派发差异源码视角ScheduleType在 tegg/core/types/src/schedule.ts 中定义export const ScheduleType { WORKER: worker, ALL: all, } as const;其执行策略分别在 strategy/worker.ts 与 strategy/all.ts 中实现二者都是TimerStrategy的子类仅handler()的派发目标不同// worker.ts —— 随机派发给一个 worker export class WorkerStrategy extends TimerStrategy { handler(): void { this.sendOne(); } } // all.ts —— 派发给所有 worker export class AllStrategy extends TimerStrategy { handler(): void { this.sendAll(); } }sendOne/sendAll位于 strategy/base.ts通过 agent 的 messenger 分别调用sendRandom(egg-schedule, info)与send(egg-schedule, info)完成消息广播并生成唯一的 Job idgetSeqId(): string { return ${Date.now()}${process.hrtime().join()}${this.count}; }worker 侧收到消息后见 app.ts 的egg-schedule事件处理会等待app.ready()然后创建一个匿名上下文执行任务const ctx this.#app.createAnonymousContext({ method: SCHEDULE, url: /__schedule?path${key}${schedule.scheduleQueryString}, }); await this.#app.ctxStorage.run(ctx, async () { return await schedule.task(ctx, ...info.args); });执行完毕后将success、workerId、rt耗时毫秒等结果通过sendToAgent(egg-schedule, ...)回传给 agent 收尾。定时任务参数immediate / disable / env普通定时任务的Schedule装饰器支持第二个参数用于指定定时任务运行参数immediate配置为true时该定时任务会在应用启动并 ready 后立刻执行一次随后再按 interval/cron 周期执行disable配置为true时该定时任务不会被启动env数组仅在指定的环境下才启动该定时任务。import { Inject, Logger } from egg; import { IntervalParams, Schedule, ScheduleType } from egg/schedule; ScheduleIntervalParams( { type: ScheduleType.WORKER, scheduleData: { interval: 100, }, }, { immediate: true, // 在应用启动并 ready 后立刻执行一次 // disable: true, // 为 true 时定时任务不会被启动 env: [devserver, test], // 仅在线下环境运行 }, ) export class ParamScheduler { Inject() private logger: Logger; async subscribe() { this.logger.info(schedule called); } }三个参数的底层行为ScheduleOptions类型定义同样位于 tegg/core/types/src/schedule.tsexport interface ScheduleOptions { // default is false immediate?: boolean; // default is false disable?: boolean; // if env has value, only run in this envs env?: EggEnvType[]; }disable在 schedule.ts 的registerSchedule()中schedule.disable为 true 的任务会被直接跳过注册agent 启动时也不会打印其注册日志见 app.ts 中对disable的过滤worker 侧若意外收到已禁用任务的派发消息也会记录disable警告后直接返回。env环境过滤发生在任务加载阶段load_schedule.ts当env为数组且不包含当前app.config.env时任务会被忽略并记录日志const env app.config.env; const envList schedule.schedule.env; if (Array.isArray(envList) !envList.includes(env)) { app.coreLogger.info([eggjs/schedule]: ignore schedule ${fullpath} due to \schedule.env\ not match); continue; }immediate在 timer.ts 的start()中配置了immediate的任务会通过setImmediate(() this.handler())立即派发一次否则进入#scheduleNext()等待第一个周期。常用配置项速查teggSchedule插件的完整配置对应 config/config.default.ts如下配置项类型默认值说明schedule.typeworker \| all-任务执行策略与装饰器参数一致schedule.intervalstring \| number-间隔数字为毫秒字符串经ms解析schedule.cronstring-cron 表达式schedule.cronOptionsCronOptions-透传给cron-parser的解析选项schedule.immediatebooleanfalse启动 ready 后立即执行一次schedule.disablebooleanfalse是否禁用该任务schedule.envstring[]-仅在指定环境启动schedule.directorystring[][]追加自定义定时任务目录绝对路径其中schedule.directory用于扩展默认的app/schedule加载目录加载时会合并loadUnits下的app/schedule与config.schedule.directory中的目录见 load_schedule.ts。此外插件内置了独立的scheduleLogger输出到egg-schedule.logconsoleLevel为NONE任务注册、触发、执行成功/失败及耗时都会写入该日志方便在生产环境排查定时任务问题。执行链路小结一次普通定时任务的完整执行链路可以概括为加载agent 启动时Scheduler.init()调用loadSchedule()扫描app/schedule及schedule.directory目录load_schedule.ts注册Scheduler.registerSchedule()按type找到对应的策略类worker→WorkerStrategyall→AllStrategydisable的任务被跳过schedule.ts调度TimerStrategy.start()依据interval/cron/immediate计算下一次触发时间timer.ts派发到达触发点后sendOne()随机选择一个 workermessenger.sendRandom或sendAll()广播给所有 workerbase.ts执行worker 收到egg-schedule消息等待app.ready()后创建匿名上下文调用subscribe()并将执行结果回传 agent 记录日志app.ts。注意事项不要将 tegg 定时任务代码放在app/schedule目录下避免与框架默认的定时任务扫描注册冲突subscribe必须为 async 函数且不要使用 generator function 形式worker模式下每次执行由 agent 随机选择 worker任务逻辑不应依赖具体由哪台机器执行生产环境多机部署时普通定时任务会在每台机器上各执行一次worker模式每机一次、all模式每机每个 worker 一次需要全局唯一执行时应采用分布式任务方案而不是依赖本功能去重任务执行耗时与调度间隔应合理设置避免长时间阻塞型任务与高频率interval相互叠加造成资源占用。赞分享后端Web框架【免费下载链接】egg Born to build better enterprise frameworks and apps with Node.js Koa. https://307.run/eggcode项目地址https://gitcode.com/gh_mirrors/eg/egg点击查看免费下载相关推荐终极指南如何快速重置Cursor试用限制并恢复AI编程助手功能终极指南如何快速重置Cursor试用限制并恢复AI编程助手功能 你是否在使用Cursor AI编程助手时遇到了Youve reached your tri后端Web框架Cloudflare Agents 任务调度完全指南从延迟执行到 Cron 定时任务的 Schedule 体系详解Cloudflare Agents 任务调度完全指南从延迟执行到 Cron 定时任务的 Schedule 体系详解 导读 本文围绕 Cloudflare AgAI AgentAgent 框架后端云原生MCP 服务实时通信Hermes Agent终极指南如何打造你的个人AI智能助手Hermes Agent终极指南如何打造你的个人AI智能助手 你是否曾经幻想过拥有一个全天候待命的AI助手能够帮你处理各种复杂任务从代码调试到文档整理从AI Agent人工智能AI 应用工具调用Agent 记忆交互助手RAG任务调度MCP 服务上一篇Editor.js 使用指南从基础操作到高级配置下一篇jsPDF基础教程从入门到实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表