十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Wasp 后台任务(Jobs)实战:PgBoss 执行器、Cron 定时任务与 submit/delay API 全解析

Wasp 后台任务(Jobs)实战:PgBoss 执行器、Cron 定时任务与 submit/delay API 全解析 Wasp 后台任务Jobs实战PgBoss 执行器、Cron 定时任务与 submit/delay API 全解析【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp本文基于 Wasp 官方文档《Recurring Jobs》展开讲解如何用声明式 spec 定义后台 Jobjob()构造器、worker 函数签名、submit()/delay()调用链、scheduleCron 定时调度以及PgBoss执行器的 PostgreSQL 依赖、PG_BOSS_NEW_OPTIONS定制参数、数据保留策略与已知问题。读完后你可以直接在自己的 Wasp 项目中落地可重试、可延迟、可定时的后台任务并理解其底层的 pg-boss 实现细节。为什么需要后台 Job先响应后干活在大多数 Web 应用中用户向服务器发送请求并期望快速拿到响应。但有些工作天然需要更长时间发送一封邮件、调用一个慢速的外部 HTTP API、批量处理数据等。此时合理的做法是尽快响应用户把剩余工作放到后台完成。Wasp 对此提供了原生的后台 Jobs 能力其核心特性包括跨重启持久化Job 在服务重启之间会持久保留写入 PostgreSQL而非内存队列失败可重试Job 失败后可以被重试延迟执行Job 可以推迟到未来的某个时间点执行周期性调度Job 可以配置 recurring 的 Cron 调度计划。定义与使用一个 Job下面是一个完整示例写一个会打印消息并从数据库取出任务列表的 Job。第 1 步在 Wasp spec 中声明 Job在main.wasp.ts中用job()构造器声明 Job specimport { app, job } from wasp.sh/spec import { mySpecialJob } from ./src/workers/bar with { type: ref } export default app({ // ... spec: [ job(mySpecialJob, { executor: PgBoss, entities: [Task], }), ], })注意 worker 的导入使用了with { type: ref }——这是 Wasp 声明“引用 src 下函数供 spec 使用”的标准写法。从源码结构看job()构造器定义在 spec 包的 public API它的签名就是job(fn: Job[fn], config: JobConfig): Job即返回{ kind: job, fn, ...config }。构造器的 JSDoc 明确说明了JobConfig包含必填的executor以及可选的schedule、entities、performExecutorOptions等字段。第 2 步实现 worker 函数在声明之后实现 Job 的 worker 函数放在任意 NodeJS 文件中import 路径必须指向 Node 侧文件export const mySpecialJob async ({ name }, context) { console.log(Hello ${name}!) const tasks await context.entities.Task.findMany({}) return { tasks } }TypeScript 版本可以借助 Wasp 生成的泛型类型做类型安全约束import { type MySpecialJob } from wasp/server/jobs import { type Task } from wasp/entities type Input { name: string; } type Output { tasks: Task[]; } export const mySpecialJob: MySpecialJobInput, Output async ({ name }, context) { console.log(Hello ${name}!) const tasks await context.entities.Task.findMany({}) return { tasks } }worker 函数的约定来自官方文档worker 必须是async函数其返回值即 Job 的结果接受两个参数args提交 Job 时传入的数据context: { entities }上下文对象包含你在 Job spec 中声明的 entities。关于MySpecialJob这个类型Wasp 会为每个 Job 生成一个泛型类型名字由传给job()的 worker 函数名转成 PascalCase并挂在wasp/server/jobs模块下。它接收两个类型参数——Inputworker 第一个参数的类型与Outputworker 返回值类型从而让参数与返回值获得完整的类型检查。第 3 步提交 Job定义成功后你可以在 Operations、setupFn或任意 NodeJS 代码中提交工作import { mySpecialJob } from wasp/server/jobs const submittedJob await mySpecialJob.submit({ name: Johnny }) // 若想让它在未来执行加一个 .delay()。 // 它接收秒数、Date 或 ISO 日期字符串。 await mySpecialJob .delay(10) .submit({ name: Johnny })到此就完成了Job 会由PgBoss执行效果等同于你直接调用了mySpecialJob({ name: Johnny })。示例中mySpecialJob接收参数但传参不是必需的——取决于你怎么实现 worker 函数。从生成代码的模板可以印证这条调用链生成的 server 入口在启动时先await startPgBoss()再启动 Web 服务见 server.ts 模板。这意味着只有当项目里声明了 PgBoss 执行器的 Job 时模板中该 import 由isPgBossJobExecutorUsed条件包裹pg-boss 才会被引入并启动。周期性任务给 Job 加 schedule如果有些工作需要周期性地执行比如每小时清理一次数据只需在 Job spec 中加一个scheduleimport { app, job } from wasp.sh/spec import { mySpecialJob } from ./src/workers/bar with { type: ref } export default app({ // ... spec: [ job(mySpecialJob, { executor: PgBoss, schedule: { cron: 0 * * * *, args: { name: Johnny }, // optional }, }), ], })在这个示例中你不需要在 JavaScript/TypeScript 中主动调用任何东西——可以理解为mySpecialJob({ name: Johnny })每小时自动被调度和触发一次。底层的实现位于 pgBossJob.tsregisterJob()在 pg-boss 启动完成后若job.jobSchedule存在就调用boss.schedule(jobName, cron, args, options)注册调度如果同名 schedule 已存在会被更新为新的 cron 表达式、参数和选项——也就是说重定义 schedule 是幂等的服务重启不会创建重复调度。Job 执行器PgBossWasp 通过job executor抽象来支持 Jobsexecutor 负责任务的调度、监控与执行。目前 Wasp 只支持一个执行器——PgBoss。PgBoss 是什么PgBoss是构建在 PostgreSQL 之上的轻量级任务队列适合低吞吐量的生产场景不需要额外基础设施或复杂运维。它利用 PostgreSQL配合SELECT ... SKIP LOCKED语句作为存储与同步机制让你直接基于已有的 Postgres 数据库获得传统任务队列的绝大多数能力。前提要求PgBoss要求你的数据库 provider 在schema.prisma中设置为postgresql。相关配置见 数据模型文档。局限性务必了解与 Web 服务器同进程运行PgBoss随 Web 服务器一起启动不是独立进程或服务。因此它不适合 CPU 密集型的负载——它要与你的应用逻辑共享 CPU不支持独立水平扩展Wasp 中的PgBoss执行器目前不支持把 pg-boss 单独作为 worker/进程/线程横向扩展。这意味着只要服务器不运行Job 就不会被处理如果需要提升 Job 处理吞吐就得运行多个 Web 服务器实例每个实例各带一个PgBoss。底层启动机制startPgBoss 与 pgBossStarted从 pgBoss.ts 模板 可以看到执行器的完整生命周期模块加载时即创建 pg-boss 实例默认选项为{ connectionString: config.databaseUrl }若设置了PG_BOSS_NEW_OPTIONS环境变量则用JSON.parse解析后整体覆盖默认选项解析失败会打印错误日志pgBossStarted是一个延迟 resolve 的 Promise所有需要访问 pg-boss 的代码如submit()都await pgBossStartedstartPgBoss()通过状态机Unstarted → Starting → Started / Error保证服务生命周期内只启动一次调用boss.start()自动在数据库中创建所需的 pg-boss 内部表成功后 resolvepgBossStarted。这个设计的精妙之处在于 registerJob 中的注释注册 handler 时并不await pgBossStarted因为如果模板代码在 NodeJS 模块加载阶段阻塞等待startServer()内的 Promise模块引导过程就会失败。而 pg-boss 本身允许在 worker 尚未注册时就send任务——一旦 worker 注册完成它们会直接消费队列中最早的任务所以提前提交不会丢任务。定制 PgBoss 实例PG_BOSS_NEW_OPTIONS如果需要定制PgBoss实例的创建可以设置环境变量PG_BOSS_NEW_OPTIONS为一个字符串化的 JSON 对象其中包含初始化参数参见 pg-boss 的new(options)文档。注意设置PG_BOSS_NEW_OPTIONS会覆盖 Wasp 的所有默认值因此你必须在其中显式包含connectionString参数# 在 .env 文件中 PG_BOSS_NEW_OPTIONS{connectionString:postgresql://user:passwordserver:5432/database,archiveCompletedAfterSeconds:86400,deleteAfterDays:30,maintenanceIntervalMinutes:5} # 在 shell 中 PG_BOSS_NEW_OPTIONS{connectionString:postgresql://user:passwordserver:5432/database,archiveCompletedAfterSeconds:86400,deleteAfterDays:30,maintenanceIntervalMinutes:5}关于在环境变量中内嵌 JSON 的转义技巧参见 JSON 环境变量文档。这一行为与源码一致createPgBoss() 中PG_BOSS_NEW_OPTIONS一旦存在且可解析就直接替代默认的{ connectionString }。数据库自动建表无需手动配置使用PgBoss时数据库的准备工作由 Wasp 服务器自动完成不需要体现在你的 schema 或迁移里。参考信息如下详细说明见 pg-boss 官方文档所有 Job 数据存放在独立的数据库 schemapgboss中包含job、archive、schedule等内部追踪表这些表大多有name列对应你的 Job 标识符并维护参数、状态、返回值、重试信息、开始与过期时间等元数据。已知问题重命名带 schedule 的 JobWasp 用你传给job()的 worker 函数名派生 Job 名称例如job(emailReminder, ...)创建名为emailReminder的 Job该名称会写入pgboss各表的name列。如果你重命名了一个带schedule的 Jobpg-boss 仍会按旧名字持续调度但旧名字已没有对应的 handler这些调度记录就会变成陈旧数据并最终过期。解决办法是从pgboss.schedule表中删除对应行BEGIN; DELETE FROM pgboss.schedule WHERE name emailReminder; COMMIT;重要只有在你对 SQL 操作足够熟悉时才直接改数据库不确定时考虑保留旧 Job 名或在开发环境用全新数据库重启。Job 数据保留与清理默认情况下PgBoss在 Job 完成或失败后保留数据12 小时之后把数据移入归档表归档保留7 天后删除。想改变这一行为用PG_BOSS_NEW_OPTIONS设置归档archiveCompletedAfterSeconds/archiveFailedAfterSeconds与删除deleteAfterSeconds/deleteAfterMinutes等参数PG_BOSS_NEW_OPTIONS{connectionString:...your postgres connection url...,archiveCompletedAfterSeconds:86400,deleteAfterDays:30,maintenanceIntervalMinutes:5}API 参考JavaScript API 总览worker 函数执行 Job 工作的async函数。由于 Wasp 在服务器端执行 Jobs其 import 路径必须指向 NodeJS 文件。它接收args: Input提交 Job 时传入的数据context: { entities: Entities }包含 Job spec 中声明的 entities 的上下文对象。示例即前文的 src/workers/bar.js 形态export const mySpecialJob async ({ name }, context) { console.log(Hello ${name}!) const tasks await context.entities.Task.findMany({}) return { tasks } }导入 Jobimport { mySpecialJob } from wasp/server/jobsTypeScript 下可以连同类型一起导入import { mySpecialJob, type MySpecialJob } from wasp/server/jobs再次强调类型安全 Jobs 的机制每个 Job 生成一个以 PascalCase worker 名命名的泛型类型如MySpecialJob类型参数Input对应 worker 的argsOutput对应返回值两者都在wasp/server/jobs模块中可用。submit(jobArgs, executorOptions)jobArgs: InputexecutorOptions: object向执行器提交 Job可附带一个 JSON 参数worker 函数会收到它以及 executor 专属的提交选项const submittedJob await mySpecialJob.submit({ name: Johnny })对应实现见 PgBossJob.submit()它await pgBossStarted拿到 boss 实例然后调用boss.send(jobName, jobArgs, options)。选项的合并顺序为spec 中的 defaultJobOptions → delay 设置的 startAfter → 本次 submit 传入的 jobOptions后者覆盖前者所以单次提交可以临时覆盖默认选项。delay(startAfter)startAfter: int | string | Date必填延迟 Job handler 的调用时机可以是整数延迟的秒数默认 0字符串ISO 日期字符串表示在该时刻执行Date在该日期执行。const submittedJob await mySpecialJob .delay(10) .submit({ name: Johnny }, { retryLimit: 2 })实现上delay() 返回一个携带startAfter的新PgBossJob实例原实例不被修改提交时该值被注入到 pg-boss 的send选项中实现延迟入队。追踪已提交的 JobSubmittedJobsubmit()的返回值是SubmittedJob实例包含jobId该 Job 在执行器中的 IDjobNameWasp 从你传给job()的 worker 函数派生的 Job 名称executorNameJob 执行器名称的 Symbol。此外还有按执行器命名空间划分的方法对象。对于 pg-boss可以访问pgBossdetails()获取 pg-boss 专属的 Job 详情对应 pg-boss 的getJobById(id)。从 PgBossDetails 类型 看Wasp 还做了类型收窄——只有当state: completed时output才可能是你的Output失败时是错误对象其余状态created/retry/active/expired/cancelled下output为nullcancel()尝试取消该 Job对应 pg-boss 的cancel(id)resume()尝试恢复一个已取消的 Job对应 pg-boss 的resume(id)。三者见 PgBossSubmittedJob 类。小结Wasp 的 Jobs 能力可以概括为一条清晰的链路在main.wasp.ts中用job()声明 spec指定executor: PgBoss、可用entities与schedule.cron→ worker 函数以async (args, context) result形态实现 → 任意服务端代码通过wasp/server/jobs中的 Job 对象submit()可链式delay()提交 → pg-boss 借助 PostgreSQL 持久化、重试、延迟与 Cron 调度并在 Web 服务器启动时随startPgBoss()自动初始化数据库结构。理解PG_BOSS_NEW_OPTIONS的整体覆盖语义、PgBoss 与 Web 服务器同进程的扩展局限、以及重命名 scheduled Job 时pgboss.schedule表的残留问题能让这一机制在真实生产环境中更可靠地落地。【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表