跳到正文

持久化工作流

沙箱生命周期想做点简单的事情。每三十秒检查沙箱是否已闲置足够长以进入休眠。如果是,就截图并停止。如果没有,再等三十秒。

在一个长期运营的服务器里,那是个setInterval,然后你就回家了。在无服务器中,函数在一两分钟后会失效,并带走计时器。沙箱继续运行,你继续付费,而你的生命周期代码却被放在某个已经不存在的进程里。

你需要持久的基础设施。一个能跨越功能边界沉睡,并在另一端继续存在的东西。Vercel Workflow就是这样做的。即使使用不同的运行时,模式也很重要。

学习成果

你可以解释为什么setTimeout在无服务器环境中沙箱生命周期内失败,也可以勾勒出一个轮询沙箱、空闲时快照、部署后恢复的持久化工作流循环。

setTimeout 的问题

ts
setTimeout(() => checkAndSnapshot(), 30_000);

这在本地有效。它在无服务器中不工作。

调用setTimeout的函数返回。运行时会清理函数的进程。超时时间是垃圾回收。支票从未运行。沙箱还在继续运转。你的月账单会显示无限沙箱运行时间的费用。

即使函数在第一次检定前存活足够久,也不能保证下一次部署没有替换它。每次重新部署时,计时器状态都会丢失。

工作流程模式

Vercel Workflow揭示了一个不依赖于宿主进程存活的sleep()

ts
"use workflow";
import { sleep } from "workflow/sleep";

const POLL_INTERVAL = 30;
const INACTIVITY_WINDOW = 5 * 60;

export async function sandboxLifecycle(sandboxId: string) {
  while (true) {
    await sleep(POLL_INTERVAL);

    const status = await checkSandboxStatus(sandboxId);

    if (status === "expired") {
      break;
    }

    if (status.lastActivity + INACTIVITY_WINDOW < Date.now() / 1000) {
      await snapshotAndStop(sandboxId);
      break;
    }
  }
}

sleep(30)不会暂停功能。它会检查工作流程到耐用存储和退货。三十秒后,工作流程会从上次中断的地方恢复,无论在哪个函数实例中。跨部署。跨主机重启。无论平台在底层做什么。

这就是诀窍。循环体是普通代码。sleep就是魔力。

阶梯边界

在工作流程中,调用外部系统(提供者API、数据库、任何副作用)都存在于"use step"功能中:

ts
"use step";

export async function checkAndSnapshotStep(sandboxId: string) {
  const sandbox = await getSandbox(sandboxId);
  if (!sandbox.isActive) return { action: "stop" };

  const idle = Date.now() - sandbox.lastActivityAt;
  if (idle > INACTIVITY_WINDOW) {
    await sandbox.snapshot();
    await sandbox.stop();
    return { action: "hibernated" };
  }

  return { action: "continue" };
}

step 函数在瞬态失败时重试,成功时缓存。工作流程循环调用它们像正常功能一样。运行时间让耐用性得以实现。

成本数学

节省多少取决于你平时的闲置时间。一个合理的折衷情况:

设置行为每次会谈费用
没有生命周期沙箱运行至硬到期(4小时)4小时 x 0.02美元/分钟 = 4.80美元
基于不活动的休眠沙箱闲置5分钟后休眠25分钟 x $0.02/分钟 = $0.50

长时间的训练大约是一个数量级。节省的费用会随着用户和时间的积累而复利。

**注意:该模式能持续运行**

Vercel Workflow 是一个实现方式。时间是另一种。AWS Step Functions也是一个例子。原理是一样的:能够通过函数边界存活的睡眠,即使你没有,也能像写一个长时间运行的进程一样编写生命周期代码。如果你用的是不同的运行时,找sleep()和步进函数的等价物。只有当你真的想做时,才自己卷。

演示版戛然而止

本课程的本地和just-bash后端不运行持久化工作流。他们不需要。生命周期就是过程生命周期。没有什么不活跃的存在可以冬眠。

模块4的生命周期钩子就是云后端的定位。afterStart会启动持久化工作流。beforeStop会让工作流程结束。工作流程本身会通过同一个Sandbox界面调用回到沙箱,调用 snapshot()stop(),就像其他消费者一样。

这就是界面设计的原因。同步的进程中世界(本地)和异步多重部署的世界(云)都放在同一个表面之下,因为工作流运行句柄最难的部分。

动手试试

这是一个概念课。自我检视:

  1. 请用一句话解释为什么setTimeout在无服务器的生命周期沙箱失败
  2. 追踪工作流程循环,列出部署中所有可能检查sleep的地方
  3. 计算一下你实际运行过的工作负载节省了多少。你每次游戏的平均闲置时间是多少?

提交

这节课没有代码。下一课列出了即使持久化工作流接线正确,仍会被咬的生产陷阱。

完成标准

  • [ ] 你可以解释为什么 setTimeout 在无服务器中不起作用
  • [ ] 你可以画出工作流程流程,识别耐用性接缝
  • [ ] 你可以根据自己选择的工作量做成本计算

非官方简体中文翻译 · 原课程来自 Vercel Academy