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

资讯详情

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

Cloudflare Agents 生命周期任务队列实战:用 Durable Object 演练崩溃恢复与 OOM 熔断

Cloudflare Agents 生命周期任务队列实战:用 Durable Object 演练崩溃恢复与 OOM 熔断 Cloudflare Agents 生命周期任务队列实战用 Durable Object 演练崩溃恢复与 OOM 熔断【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents本指南基于当前仓库中的 examples/next/lifecycle 示例与 agents/lifecycle 源码实现讲解如何在一个原生 Durable Object上组合agents框架的 Lifecycle 能力把「每 5 秒自调度任务、实例重启恢复、内存限制熔断、真实 OOM 死亡与平台自动重试」完整跑通并逐项验证。读完本文你将掌握 Lifecycle 任务队列的推入/取消/重排 API、三种故障场景的观察方法与熔断器的内部工作机理并能部署到 Cloudflare Workers 上亲自操作。概述一个把故障当作业的演示项目examples/next/lifecycle是当前仓库examples/next系列中专门用于演练Lifecycle 任务队列韧性的示例。与常见的 Agent 示例不同它刻意不展示「智能对话」而是构造了一个故意崩溃、故意 OOM的 Durable Object用agents/lifecycle把任务队列组合进一个普通DurableObject子类通过一组 HTTP 路由/start、/stop、/restart、/oom、/oom-real驱动不同的故障剧本用 SQLite 持久化计数器和日志把「哪个内存实例isolate写了哪条日志」记录下来从而让实例重启、OOM 死亡都以 isolate-id 变化的形式直接可见而持久化的计数与任务则穿越故障保持不变。演示的核心目标源自示例 README“exercising the Lifecycle-owned job queue against real platform failure: instance restarts, simulated memory-limit strikes (the alarm circuit breaker), and genuine out-of-memory isolate deaths.”—— 即在真实的平台故障实例重启、模拟内存限制命中、真实的 OOM 隔离区死亡面前验证 Lifecycle 任务队列的行为。部署与目录结构示例是一个标准的 Cloudflare Worker 工程先看它的结构examples/next/lifecycle/ ├── src/index.ts # Durable Object 与所有路由处理 ├── env.d.ts # wrangler types 生成的环境类型 ├── package.json # dev / deploy / typecheck / types 脚本 ├── tsconfig.json └── wrangler.jsonc # Durable Object 绑定与迁移配置package.jsonexamples/next/lifecycle/package.json提供了四个脚本脚本命令用途devwrangler dev本地开发运行deploywrangler deploy部署到 Cloudflaretypechecktsc --noEmit类型检查typeswrangler types env.d.ts --include-runtime false重新生成env.d.ts部署即运行npm run deploywrangler.jsoncexamples/next/lifecycle/wrangler.jsonc中值得注意的配置点durable_objects.bindings把名称DoAgent绑定到类DoAgentmigrations使用new_sqlite_classes声明 SQLite 支持任务队列的cf_agents_jobs表正是存放在 Durable Object 的 SQLite 存储中compatibility_date使用 2026-06-11并开启nodejs_compat兼容标志Lifecycle 内部使用 Node 的async_hooks见下文。路由一览五个操作与一个状态面板示例的所有入口都在部署后 worker 的/agents/do-agent/name路径下。下表与示例 README 的「Drive it」章节一一对应路由方法作用/statusGET返回 isolate id、熔断击数strike counter、各类计数器、待处理任务、已布防的 alarm、最近日志/startPOST推入一个持久化的tick任务该任务每 5 秒自我重排/stopPOST取消tick任务队列清空后删除 alarm 并让对象休眠hibernate/restartPOST调用ctx.abort()强制重置当前实例tick任务在下次唤醒时恢复/oomPOST推入一个在派发时抛出平台内存限制错误的任务观察熔断器击数退避 30s、60s第 3 击时封存并清除该任务而tick任务持续运行/oom-realPOST推入一个真正分配内存直到 isolate 死亡的任务连续两次死亡平台在全新 isolate 上重试 alarm第三次运行成功完成/status返回的日志demo_log表记录了每个内存实例写日志时所在 isolate 的 id所以重启和 OOM 死亡表现为 isolate-id 的跳变而持久化的计数器与任务状态则一路穿过这些故障。这是整个演示最重要的观察手段。路由分发逻辑位于 examples/next/lifecycle/src/index.ts是DoAgent.onRequest里的一个switch/restart会先记录日志再用setTimeout(..., 100)让当前响应先送达然后调用this.ctx.abort(demo restart)/oom与/oom-real只是把对应的故障任务推入队列。组合方式普通 Durable Object Lifecycle示例的关键设计是不继承框架的 Agent 基类而是让一个普通DurableObject通过Lifecycle.install(...).use(...)组合出任务队列能力。核心代码examples/next/lifecycle/src/index.tsexport class DoAgent extends DurableObjectEnv { private readonly demo new JobDemo(); readonly lifecycle Lifecycle.install(this).use(this.demo); onAlarm(): void { // Runs once per alarm invocation, after due jobs are driven. } async onRequest(request: Request): Promisevoid | Response { // ... 路由分发 } }对照源码Lifecycle.installpackages/agents/src/lifecycle/durable-object-lifecycle.ts可知它等价于new Lifecycle(host)后再调用installHandlers()。installHandlers同一文件 L249-L271会把fetch、alarm、webSocketMessage、webSocketClose、webSocketError这些运行时处理函数安装到宿主类上宿主已存在的同名处理函数会保留例如 Agent 自身的 sub-agent 路由与 alarm 熔断器因此示例里宿主类不再需要手写onAlarm之外的平台 handlerLifecycle.install之后即可获得完整的任务队列与 alarm 事件循环。Lifecycle构造器L208-L240会创建两个核心对象JobQueue——SQLite 背书的持久化任务队列对应cf_agents_jobs表JobDriver——alarm 事件循环驱动器负责 due 任务派发、重试、死因守护deadman预布防以及内存限制熔断器。LifecycleOptions目前只暴露一个可配置项L144-L151配置项类型默认值含义maxAlarmMemoryLimitStrikesnumber3熔断器封存恢复工作前允许连续多少次以内存限制重置结束的 alarm 调用注意agents/lifecycle整个导出面都标注为experimentalpackages/agents/src/lifecycle/index.tsAPI 在稳定前可能变动。Capability 基类如何写一个拥有任务队列的能力示例中JobDemo继承自LifecycleCapabilitypackages/agents/src/lifecycle/capability.ts。LifecycleCapability基类要求子类在构造时传入一个非空的capabilityId并提供claimsselective或catch-all声明对请求的分派方式。被Lifecycle.use()注册的 capability 会获得一组作用域化服务LifecycleServicescapability.ts#L72-L103其中与本演示最相关的是jobs——即该 capability 专属的任务队列视图。队列访问表面LifecycleJobsjob-queue.ts#L108-L129提供方法签名说明push(options: LifecycleJobPushOptions) PromiseLifecycleJob推入任务同 id 重复 push 会替换该任务cancel(id: string) Promiseboolean取消任务无匹配时返回falsereschedule(id: string, time: number) Promiseboolean重排任务到期时间get(id: string) LifecycleJob \| undefined读取单个任务list() LifecycleJob[]按到期时间列出该 owner 的全部任务rearm() Promisevoid从队列状态重新计算物理 alarm任务 id 是按 ownercapability id 或host作用域隔离的同 id push 只替换自己 owner 的行跨 owner 撞 id 会抛错见 job-queue.ts#L223-L279 的ON CONFLICT ... WHERE capability excluded.capability逻辑与所有权错误提示。LifecycleJobs.push接受的LifecycleJobPushOptionsjob-queue.ts#L47-L79完整字段如下字段类型默认说明fnstring必填序列化的函数名owner 派发时据此switchtimenumber必填到期时间epoch 毫秒非法值会抛Invalid job timepayloadunknown无任意 JSON 可序列化载荷队列对它不透明idstring自动生成nanoid(9)稳定 id带 id push 即替换retryRetryOptions队列默认{maxAttempts:3, baseDelayMs:100, maxDelayMs:3000}派发重试策略singleflightbooleanfalse前一次运行仍在执行时跳过本次hungTimeoutSecondsnumber30单飞任务被判定为挂起、以及慢派发告警的秒数阈值exclusivebooleanfalse该任务 pending 期间抑制普通 alarm 候选recoveryLoopbooleanfalse标记为可确定性耗尽内存的恢复循环任务参与熔断器退避/封存三种任务派发结果JobOutcomeonJob的返回值LifecycleJobOutcomejob-queue.ts#L94-L97是驱动循环决定任务去留的依据undefined不返回或返回undefined任务完成从队列删除{ rescheduleAt: number }把任务挂起到未来的某个时间点yield任务保持 due下一轮立即再次唤醒。示例中的tick任务examples/next/lifecycle/src/index.ts#L125-L133就是「运行 → 返回{ rescheduleAt: Date.now() intervalSeconds * 1000 }→ 5 秒后再唤醒」的自循环case tick: { const count this.bump(ticks); this.log(tick, tick #${count} ran on isolate ${isolateId()}); const intervalSeconds (job.payload as { intervalSeconds?: number })?.intervalSeconds ?? 5; return { rescheduleAt: Date.now() intervalSeconds * 1000 }; }一个值得注意的实现细节applyOutcome是受派发标记保护的job-queue.ts#L431-L455。每次派发都会给行打上running 1而派发期间发生的同 idpush/reschedule会清除该标记——于是「更新的持久化意图」优先于「本次运行返回的结果」旧结果被静默丢弃。如果同一个任务既被 owner push 又返回了 outcome两者应来自同一份持久化状态以保证一致。队列的持久化与 alarm 布防任务队列的存储表在 job-queue.ts#L202-L221 定义即cf_agents_jobsWITHOUT ROWID表包含id、capability、fn、time、payload、retry_options、singleflight、hung_timeout_seconds、exclusive、recovery_loop、running、execution_started_at、created_at等列。每次队列变更push/cancel/reschedule后Lifecycle 都会调用rearmAlarm()durable-object-lifecycle.ts#L749-L770重新推导物理 alarm从队列中计算nextAlarmTime有任务则setAlarm队列空则deleteAlarm。这正是 README 中/stop之后「空队列删除 alarm、对象休眠」的实现机理——队列不再需要唤醒时Durable Object 进入无 alarm、无连接的完全休眠状态。nextAlarmTimejob-queue.ts#L466-L505的推导规则存在exclusive任务时直接取最早的 exclusive 任务时间否则取「最早就绪任务单飞中且未挂起的任务除外的时间与当前时间的较大者」与「在飞单飞任务的 hung 复查时间」中的较小时刻过期overdue行不会被丢弃会立即重放。故障剧本一实例重启/restart/restart用ctx.abort()强制销毁当前 isolatesrc/index.ts#L216-L221。由于tick任务的到期时间、demo_counters计数器和demo_log日志全部持久化在 Durable Object 的存储中重启后/status里的isolate.id会变成一个新的随机 8 位短 idisolateId()在 src/index.ts#L13-L19 用crypto.randomUUID().slice(0, 8)生成ticks计数器继续累加tick任务在下一个到期时刻由新的 isolate 继续执行。于是「实例重启」这一抽象概念被落成一个可观察的信号同一行日志流中 isolate id 发生跳变但计数不归零。这正是 README 所说的 restarts and OOM deaths are visible as isolate-id changes while the durable counters and jobs carry straight through them。故障剧本二模拟内存限制命中/oom/oom推入一个boom任务src/index.ts#L99-L109它的onJob会主动抛出平台的内存限制重置错误Durable Objects isolate exceeded its memory limit and was reset.并且通过retry: { maxAttempts: 1 }保证每次 alarm 调用恰好构成一次干净的 strike。这个场景直接对标本仓库的内存限制熔断器实现issue 编号 #1825。在JobDriver中job-driver.ts#L469-L548识别平台故障runAlarm捕获错误后调用isDurableObjectMemoryLimitReset判断只有这类错误才会进入熔断处理其他错误原样抛出保持平台的 alarm 自动重试语义job-driver.ts#L189-L203。持久化 strike 计数OOM_ALARM_STRIKES_KEY存储键cf_agents:oom_alarm_strikes上的计数器加一多次流在 alarm 内的任务 交给后台的trackAlarmWork工作观察到同一次重置时共享同一条 strike 记录避免重复计数job-driver.ts#L556-L566。退避而非热循环未封存时被击中的任务被retime到now min(300, 30 * strikes) * 1000——即第 1 击退避 30s、第 2 击退避 60s与 README 表格中的 backoff 30s, 60s 完全一致job-driver.ts#L579-L581。封存并清除strike 数达到阈值默认 3时sealed true删除该任务、清空 strike 计数器、触发 capability 与宿主的onAlarmMemoryLimit策略钩子并安排一次 isolate 重置来回收内存job-driver.ts#L534-L547。同时示例代码里tick任务与boom任务互不相干boom被封存清除时ticks计数仍在持续增长证明熔断器只处置肇事任务、不影响同队列的健康任务。故障剧本三真实 OOM 死亡/oom-real/oom-real是更极端的剧本src/index.ts#L111-L123推入realOom任务前两次运行会不断push(new Array(1_000_000).fill(runs))直到 isolate 真实死亡第三次运行检查real-oom-runs计数为 3 时正常返回undefined完成。这个剧本验证的是Lifecycle 死因守护deadman与平台 alarm 重试的组合行为任务派发前JobDriver会先布防一个 30 秒后的 fallback alarmDEADMAN_ALARM_DELAY_MS 30_000见 job-driver.ts#L52 与 L290-L296即使 isolate 死在派发中途、平台无法自动重试这个 deadman alarm 仍会在新 isolate 上唤醒对象继续队列isolate 死亡后平台在全新 isolate 上重放 alarm队列行time 已过期仍然存在#driveDueJobs会再次把它当作 due 任务派发前两次运行各记一次real-oom-runs第三次运行因runs 2而存活返回undefined删除任务。所以real-oom-runs计数器最终等于 3任务完成——平台重试 持久化队列让「注定会先死两次的任务」第三次成功收尾。README 所说的 the platform retries the alarm on fresh isolates and the third run completes 指的就是这一整套流程。慢派发与挂起任务的守护除了熔断与 deadman驱动循环还内置了两道对任务健康度的守护job-driver.ts#L333-L427单飞挂起恢复singleflight: true的任务若上一次运行超过hungTimeoutSeconds默认 30s仍标记为running会被强制重置isHungRow判定见 job-queue.ts#L174-L176并打印 Forcing reset of hung job 告警。慢派发看门狗任何一次派发超过 hung 阈值setTimeout看门狗会告警并发出job:slow_dispatch遥测事件提示 onJob must detach unbounded work and return——因为驱动循环是逐个内联 await 的一个长派发会饿死同一对象上的其他所有任务。此外当一次 alarm 循环为同一 owner 处理的 due 任务超过 10 个时会触发job:backlog_warning告警JOB_BACKLOG_WARNING_THRESHOLD 10job-driver.ts#L46提示开发者检查是否用稳定 id 而不是重复 push 一次性任务。如何在 /status 里读懂故障/status的返回体由JobDemo.status()组装examples/next/lifecycle/src/index.ts#L165-L194字段含义如下字段来源故障观察要点isolate.id/isolate.bootedAt内存中的随机短 id 与启动时间重启/OOM 后 id 变化oomStrikes存储键cf_agents:oom_alarm_strikes/oom后 1 → 2 → 3封存后清零countersdemo_counters表ticks、boom-attempts、real-oom-runs持久累加jobscf_agents_jobs表每个任务的id、fn、dueInMs、retryalarmstorage.getAlarm()物理 alarm 时间队列空时为nulllogdemo_log表保留最近 60 条每条日志的isolate列显示写入者推荐的观察流程POST /start启动 tickGET /status记录初始isolate.idPOST /restart后立即GET /status观察 isolate id 变化而ticks不归零POST /oom后分三次间隔GET /status观察oomStrikes从 1 涨到 3、boom-attempts累计、任务在封存后被清除同时ticks持续增长POST /oom-real后等待并轮询GET /status观察real-oom-runs从 0 到 3期间 isolate id 至少变化两次POST /stop后GET /status观察jobs清空、alarm变为null对象休眠。测试支撑与实现佐证本仓库为 Lifecycle 的故障语义提供了充分的测试证据可作为深入阅读的入口packages/agents/src/tests/tasks/memory-limit.test.ts 用runDurableObjectAlarm驱动真实 alarm断言熔断器对确定性 OOM 任务的退避与封存行为包括 strike 计数轮询waitForStrikes与 isolate 重置的容错处理packages/agents/src/tests/lifecycle/startup.test.ts 覆盖 Lifecycle 启动与 handler 安装路径packages/agents/src/tests-d/lifecycle-export.test-d.ts 以类型测试锁定agents/lifecycle的导出面。实现侧的核心文件集中在 packages/agents/src/lifecycle/其中 durable-object-lifecycle.ts组合、handler 安装、alarm 布防、job-queue.tsSQLite 队列与任务操作、job-driver.ts驱动循环、deadman、慢派发看门狗、内存限制熔断器、capability.tsCapability 基类与服务面四份文件构成了本演示的全部底层机制。小结examples/next/lifecycle用最小化的代码把 Cloudflare 平台故障变成可重复、可观察的演练/start/stop验证持久化自调度任务的推入、重排与休眠/restart验证实例重启后任务与计数穿越故障/oom验证内存限制熔断器以 30s/60s 退避并在第 3 击封存/oom-real验证真实 OOM 下 deadman 预布防 平台重试让任务第三次完成。整条链路背后是agents/lifecycle在 packages/agents/src/lifecycle/job-driver.ts 与 packages/agents/src/lifecycle/job-queue.ts 中的实现SQLite 持久化、受派发标记保护的结果提交、从队列状态推导物理 alarm、死因守护与熔断器。部署后按上文流程操作/status面板即可对这套「任务队列 故障恢复」机制建立直观的工程认知。【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表