Edge Runtime:Isolate、分布式请求与平台配额
核验日期:2026-07-16。本文以 Cloudflare Workers 为代表解释 Edge Runtime,不把所有边缘平台视为同一实现。配额、API 和 compatibility flags 更新频繁;数值仅反映核验日官方文档,部署前必须重新检查 Workers Limits 和目标套餐。
Edge Runtime 的决定性变化在执行单位、生命周期和宿主能力,“服务器离用户更近”只是结果之一。传统 Node 服务通常以长寿命进程为中心,拥有文件系统、端口、进程信号和连接池;Cloudflare Workers 以事件驱动的 V8 Isolate 为中心,平台在全球节点按请求路由,实例可复用也可驱逐,能力通过 Web APIs 与 bindings 暴露,并受每请求 CPU、内存、子请求、连接和 bundle 等限制。
学习结果与适用边界
完成本文后应能:
- 解释进程、容器、VM、V8 Isolate 和请求 invocation 的关系,不把 isolate 称为线程或完整 VM。
- 从边缘入口、模块初始化、
fetchhandler、subrequest、stream、waitUntil到日志追踪一次请求。 - 说明全局变量可复用但不持久,实例可能并发处理多个请求,任何两个请求不保证到同一实例。
- 根据 CPU/wall time、128 MB isolate 内存、subrequest/connection、bundle/startup 等平台限制设计算法。
- 区分 Web API、Node compatibility、Wrangler polyfill/stub、平台 binding 和普通 npm package。
- 判断认证前置、重定向、缓存、轻量聚合适合边缘,而重 CPU、长事务、任意文件/进程/native addon 不适合。
- 使用 compatibility date 管理运行时行为升级,并保留回归和回滚。
Isolate 模型
How Workers works说明 Cloudflare 在一个 runtime 进程中运行许多 V8 isolates。每个 isolate 有隔离的内存上下文,比为每段代码启动完整 VM/进程轻;平台可快速创建、复用和驱逐。隔离仍依赖 runtime 与平台安全工程,不应将其简化为“V8 自己解决所有多租户风险”。
一个 isolate 可处理多个请求,await 外部 I/O 时同一事件循环可能推进其他请求。模块顶层常量和只读已解析数据可缓存;可变用户状态不能依赖全局,因为并发会交错、部署有多个位置/实例、平台可驱逐。即便某次压测总命中同一实例,也不能推出生产路由粘性。
“无冷启动”应怎样理解
Isolate 相比容器/进程显著轻,官方描述其启动非常快。但应用仍有 bundle 解析、模块求值、Wasm 编译、连接建立、cache miss 与地区首次执行,用户可以观察到冷/热差异。性能报告应把平台调度、模块初始化和上游连接分开,不用绝对的“零冷启动”。
一次请求的端到端流程
平台把标准 Request、bindings env 和 execution context ctx 交给 module worker 的 fetch。handler 返回 Response 后 body 可能继续流式发送;ctx.waitUntil() 可登记响应后的工作,但它有生命周期上限,并非 durable job queue。计费/限制可能区分 CPU time 与 wall time;等待网络通常不计 CPU,但连接、subrequest 和 duration 仍有限制。
Module Worker 与 bindings
interface Env {
CATALOG: KVNamespace;
ORIGIN: Fetcher;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (request.method !== "GET") {
return Response.json({ code: "METHOD_NOT_ALLOWED" }, {
status: 405,
headers: { allow: "GET" },
});
}
if (url.pathname !== "/technologies") {
return Response.json({ code: "NOT_FOUND" }, { status: 404 });
}
const cacheKey = new Request(url.origin + url.pathname + url.search, request);
const cached = await caches.default.match(cacheKey);
if (cached) return cached;
const response = await env.ORIGIN.fetch(request);
if (response.ok) ctx.waitUntil(caches.default.put(cacheKey, response.clone()));
return response;
},
} satisfies ExportedHandler<Env>;这是边界示例,不是完整缓存策略。Service Binding 避免经公网调用同账户 Worker;Cache API 行为、可用位置和 Set-Cookie/Vary 等需核对官方文档。response.clone() 可能增加流/内存压力,必须对大响应测试。写 cache 放在 waitUntil 中意味着请求成功不保证 cache 写一定成功,缓存只能是可重建优化。
bindings 是平台注入的 capability:KV、R2、D1、Durable Objects、Service Bindings、secrets 等。相较从全局 env 读取字符串,它能把资源与部署配置关联,但仍要限制权限、环境和目标。TypeScript interface 只做静态描述,错误/缺失 binding 需要部署校验。
生命周期:全局缓存可以,持久状态不可以
模块顶层在 isolate 初始化时执行,适合创建 immutable lookup、解析 schema、准备 client wrapper;不要每请求重复大初始化。全局可变 Map 可做有界 opportunistic cache,但必须满足:丢失不影响正确性;并发读写安全;有容量/TTL;不含跨用户 secret;部署版本切换能重建。
const cache = new Map<string, { expires: number; value: string }>();
function getFresh(key: string, now = Date.now()) {
const entry = cache.get(key);
if (!entry || entry.expires <= now) {
cache.delete(key);
return undefined;
}
return entry.value;
}这是每 isolate 的 best-effort cache,不是全局一致缓存。不能用它保存余额、锁、幂等记录或登录 session。需要单对象串行状态时评估 Durable Objects;需要全局低延迟读但可接受最终一致时评估 KV;关系查询/事务评估 D1/区域数据库。具体一致性和配额属于平台数据设计,不能从 API 名推断。
CPU time、wall time 与并发
核验日 Workers Limits列出:Free HTTP invocation CPU 10 ms;Paid 默认 30 秒、可配置最高 5 分钟;每 isolate 128 MB 内存;Free/Paid subrequest 与 bundle 等限制不同。HTTP wall duration 在客户端连接保持时没有同样的固定硬上限,但响应结束/客户端断开后未登记工作可能取消,waitUntil 也有有限延长窗口。
这些数字会变,架构文档应记录“依赖哪类限制”和核验链接,而不是把数值散落为永久常量。
| 资源 | 计量含义 | 容易误解 | 设计动作 |
|---|---|---|---|
| CPU time | isolate 实际执行代码的 CPU | await fetch 的等待等同 CPU | profile JSON/crypto/SSR,拆重计算 |
| wall time | invocation 从开始到结束 | 无 HTTP duration limit 等于永久后台进程 | 客户端、更新、waitUntil 都会结束工作 |
| memory | isolate heap + Wasm 等 | 每请求独享 128 MB | 同 isolate 多请求,流式与有界缓存 |
| subrequests | fetch/平台服务调用次数 | 一行 fetch 永远只计一次 | redirect 与内部调用也可能计数 |
| connections | 同 invocation 等待响应头的连接 | 无限 fan-out 会自动排队 | 有界并发、批量接口、取消未用 body |
| bundle/startup | 部署代码与启动预算 | bundler 成功就能部署 | 查动态依赖、Wasm、模块顶层工作 |
CPU 超限可返回 Cloudflare 1102,日志 outcome 为 exceededCpu;memory 超限也可能 1102 但 outcome 不同。排障必须查看平台 invocation outcome、CPU/wall 和内存,不能只看客户端同一状态页。
Fan-out 的隐藏成本
边缘聚合十个上游似乎减少客户端 RTT,却增加 subrequests、并发连接、最慢上游尾延迟和失败组合。使用有界并发、共同 deadline、每上游预算和部分失败策略。可由源站批量接口解决时,不在 Edge 写复杂 fan-out orchestrator。
Streams 与内存
代理大文件时把上游 Response.body 直接流向下游,避免 arrayBuffer/text/json 全量缓存。TransformStream 只做逐 chunk 转换,并尊重 backpressure。某些操作如签名、解压或 HTML 改写可能隐式缓存,结合 128 MB isolate limit 测试最坏 body。
async function proxy(request: Request): Promise<Response> {
const upstream = await fetch("https://origin.example.com/large", {
signal: request.signal,
});
return new Response(upstream.body, {
status: upstream.status,
headers: upstream.headers,
});
}真实代理不能原样转发所有 headers;需过滤 hop-by-hop、安全与缓存相关字段,重新验证 redirect 和目标 host,设置业务 deadline。客户端断开 signal 是否传播到所有平台/上游路径应集成测试。
Node.js compatibility:native、polyfill 与 stub
Workers 以 Web APIs 为首要宿主,同时提供 Node compatibility。启用 nodejs_compat 且 compatibility date 达到要求时,runtime 原生支持部分 Node APIs;Wrangler 可用 unenv 注入尚未原生实现的 polyfill。官方文档明确有些 mocked method 会 noop 或在调用时抛出“not implemented”。因此 import 成功不是功能成功。
{
"name": "runtime-catalog-edge",
"main": "src/index.ts",
"compatibility_date": "2026-07-16",
"compatibility_flags": ["nodejs_compat"]
}配置日期仅为本文核验示例;项目应选择经过回归的日期,不自动每天推进。Node 包评估顺序:检查是否依赖文件/进程/native addon;解析实际 bundle;在本地 Workers runtime 运行功能 fixture;部署 staging 运行;查看是否包含 stub;验证内存/CPU/stream;最后才认为兼容。
以下能力通常是硬风险:任意 fs 路径、监听自定义端口、child_process 真执行、native .node addon、V8 inspector/heap snapshot、OS signal/daemon、长期本地 socket pool。兼容 flag 页面可能为模块提供 stub 以便导入,stub 不能创造平台没有的进程能力。
Compatibility date:运行时行为的版本锁
Cloudflare 持续更新 Workers runtime。compatibility date 让项目选择截至某日期的行为,flags 可提前启用或临时保持旧行为。这比永久冻结 runtime 更安全,也意味着升级日期是需要评审的代码/配置变更。
测试范围包括 URL/Headers/Streams、Node APIs、module loader、WebSocket、cache、encoding 和错误类。升级 Wrangler/bundler 与 compatibility date 尽量分开,避免两个变量同时变化。记录旧/新日期、变更链接和回滚截止。
分布式执行与状态
请求可落到任意位置,两个连续请求不保证同 region/isolate。把状态放在单一中心数据库会让 edge compute 离用户近、数据却远,增加 backhaul latency。Smart Placement 等平台能力可能让计算靠近数据,但选择要以业务 trace 验证。
| 状态类型 | 合理位置 | 关键语义 |
|---|---|---|
| 公开静态/可缓存响应 | CDN Cache/Cache API | cache key、TTL、purge、stale |
| 配置/小型低频读 | KV 或配置 binding | 最终一致、版本、失效 |
| 大对象 | R2/object storage | streaming、range、metadata |
| 关系数据 | D1/外部数据库 | transaction、region、connection |
| 单实体协调 | Durable Object | object id、serialization、迁移 |
| request-scoped 临时值 | handler local | 不跨请求 |
| opportunistic cache | isolate global | 可丢失、有界、非权威 |
Edge Runtime 不能充当分布式数据库。写入、幂等、冲突和一致性会在全栈后端部分进一步设计;当前只需明确 isolate global 不具备持久化语义。
Cache:正确性先于命中率
Cache key 至少考虑 URL、method、内容协商、认证范围、tenant、版本和会影响响应的 headers。私有响应不得因遗漏用户/组织维度进入共享 cache。Vary、Set-Cookie、authorization 与平台缓存规则需核对。
缓存更新采用版本化 key 或明确 purge;stale-while-revalidate 可降低尾延迟,但后台更新失败时要可观察。waitUntil(cache.put(...)) 不应承载唯一业务写。缓存击穿通过 coalescing、TTL jitter 或源站保护处理,但 Durable coordination 有成本,不为低流量过度设计。
安全边界
Edge 位于公网最前面,适合做输入大小、method、host、认证 token 初筛和速率策略,但不能成为唯一授权来源。源站必须验证来自 Edge 的身份或只通过 Service Binding/私网可达,防绕过。
Secret 与 bindings
secret 通过平台 secret binding 注入,不写入 wrangler 明文或 bundle。日志不打印 env/binding。不同环境使用不同资源,preview/staging 不共享生产写权限。Worker bundle 和 Source Map 可能暴露代码结构,发布策略应明确。
SSRF 与代理
不要把 query 参数直接传给 fetch。解析 URL 后 allowlist scheme/host/port,禁 loopback/private/metadata,限制 redirect 并对每次跳转重新验证。Service Binding 优先于公网 URL。HTTP header 注入、cache poisoning、request smuggling 还要结合平台规范测试。
不可信代码
租户间 Isolate 是平台实现;在自己的一个 Worker 内用 eval 执行用户代码不会自动得到新的租户隔离和独立配额。使用 Workers for Platforms/专门 sandbox 产品或 VM 级环境,并阅读具体安全模型。
后台工作与 waitUntil
ctx.waitUntil(promise) 允许响应后继续有限工作,适合日志、cache 写、非关键通知。它不是持久队列:runtime update、异常、时间上限和重试语义都可能影响完成。关键任务写入 Queue/Durable storage 后再返回,并用幂等 consumer 处理。
多个后台任务要独立捕获错误和记录 outcome;一个 rejected Promise 不应让其他清理静默丢失。日志 flush 也受每请求日志量限制,核验日官方限制为每请求 256 KB,不能把 body 全打出。
本地开发与线上差异
Wrangler/Miniflare 提供高保真本地 runtime,但 DNS、路由、真实 bindings、全球位置、资源限制和平台缓存仍有差异。测试分三层:
- 纯 handler 单测:构造 Request 和 fake bindings,确定性、快速。
- 本地 runtime 集成:模块、Node compat、streams、scheduled/queue 与 limits 近似。
- Staging 线上:真实 route、secrets、bindings、compat date、region、logs 和资源 outcome。
不要用 Node 直接调用 handler 作为唯一测试,因为它绕过 Workers runtime。也不要让每个单测访问真实云服务。
性能与放置决策
Edge 的潜在收益来自减少用户到计算的 RTT、在边缘缓存/拒绝请求和减少源站负载。若每次请求仍去单一区域数据库,收益可能只剩 TLS/路由,且多一层 hop。用端到端 trace 分解 user→edge、edge CPU、edge→origin、origin/DB 和 response streaming。
基准至少按多个真实地区测 p50/p95/p99、cache hit/miss、cold/warm、origin distance、CPU、subrequests、errors 和费用。不能拿本地 wrangler dev 与生产 Node region 比。平台宣称 isolate 比进程启动快,是架构事实线索;业务选择仍需应用 bundle/数据位置实验。
适合与不适合
| 场景 | 适合度 | 条件/替代 |
|---|---|---|
| URL rewrite、redirect、A/B route | 高 | 规则稳定、可回滚、低 CPU |
| JWT 基础验证、bot/rate gate | 高 | key cache/rotation,源站仍授权 |
| 公共内容 cache、轻量个性化 | 高 | cache key 不泄露私有数据 |
| 多区域上游选择 | 高 | health/failover 与重试预算 |
| 轻量 SSR/API composition | 中 | bundle、CPU、subrequest、数据位置通过 |
| 大型图片/视频转码 | 低 | 用平台专用产品或异步 worker |
| 长 CPU ML 推理 | 低/条件化 | 专用 GPU/容器;Edge 只路由 |
| 任意 shell/native addon | 低 | 区域 Node/Bun/Deno/容器 |
| 跨多行强事务 | 低/条件化 | 数据库附近后端或 Durable model |
故障排查
| 现象 | 根因候选 | 最小证据 | 修正 |
|---|---|---|---|
| 本地正常线上 1102 | CPU/memory resource limit | platform outcome、CPU/wall、memory | profile/stream/拆任务/调整允许上限 |
| 偶发数据回退 | 把 isolate global 当权威状态 | colo/isolate/版本、持久层记录 | 状态写 durable store,幂等读取 |
| npm 包可 import 但调用报 not implemented | Wrangler stub | bundle 与 stack、compat 文档 | 换 Web/支持 API 或移回区域 runtime |
| cache 泄露用户数据 | key 缺少身份/tenant/Vary | cache key 与请求范围 | 禁共享缓存或包含正确版本/范围 |
| 大文件内存失败 | body 全量 buffer/clone | memory outcome、profile、body size | 端到端 stream,减少 clone/transform |
waitUntil 任务丢失 | 把有限后台执行当 durable queue | task id、invocation end、queue record | 先持久入队,再异步处理 |
| compatibility date 升级后行为变 | flag/default 改变 | old/new date contract diff | 回滚日期,按 changelog 修复再推进 |
发布、回滚与可观测性
每次发布记录 script version、bundle hash、compatibility date/flags、bindings/environment 和 Wrangler version。日志包含 request id、deployment id、colo(如平台安全允许)、cache status、subrequest count/关键上游、duration、CPU/wall/outcome 与稳定错误码。高基数值不进入 metrics label。
回滚必须同时考虑代码、bindings schema、Durable migrations、compat date 和 cache。旧代码若读不了新数据格式,仅回滚 bundle 不安全。对无状态读取服务,使用独立 route/canary 并保留旧 version 是最小路径。
官方资料
- How Workers works 与 Security model:Isolate、并发、驱逐和隔离。
- Runtime APIs:Web APIs、bindings、streams、fetch 与 handlers。
- Limits:CPU、memory、duration、subrequests、connections、bundle 与 logs。
- Node.js compatibility:runtime native APIs 与 Wrangler polyfills/stubs。
- Compatibility flags:日期、flags 和行为升级。
- Wrangler configuration:deployment、bindings 与环境配置。
交付检查
完成 Edge 示例时应留下:compatibility date/flags;bundle/部署版本;handler 单测、本地 runtime 与 staging 三层结果;bindings capability 清单;cache key/身份范围;CPU/memory/subrequest/connection 预算;大响应 streaming;waitUntil 非持久边界;Node package native/stub 验证;多地区 hit/miss p95/p99;驱逐/重试/幂等和完整回滚步骤。具备这些证据后,才能说选择的是可运维的边缘执行模型,而不是把区域服务机械搬近用户。
边缘容量演练:让配额进入设计
为一个代表请求写资源账本:解析/认证/序列化各需要多少 CPU;并行和总 subrequest 数;同时等待响应头的连接;最大 request/response body;stream transform 是否缓存;cache clone 数量;日志体积;waitUntil 工作。账本数值来自 staging trace 与 invocation logs,不凭代码行数猜测。
故意把每项推到边界:增加 JSON 大小观察 CPU;让六个以上上游同时慢响应观察连接调度;构造 redirect 链观察 subrequest 计数;慢速发送/读取 body 观察 memory;制造 cache miss fan-out 观察 origin;产生大量日志观察截断。每次只改变一个变量,并核对平台 outcome。演练使用隔离环境和低流量,避免费用与源站冲击。
若容量不足,按机制修正:CPU 高则减少解析/加密/SSR或外置;memory 高则端到端 stream、减少 clone/有界 cache;subrequest 高则批量接口、Service Binding 或预聚合;连接高则有界调度和及时取消 body;日志高则采样与结构化摘要。付费提高上限只有在业务负载合理且成本可接受时使用,不能替代无界算法修复。
多地区放置实验
选择接近用户、接近数据和跨洲三组位置,分别测 cache hit 与 miss。trace 拆出 user→edge、edge CPU、edge→origin、origin/DB。若 miss 的 edge→origin 占主要延迟,考虑 cache、源站多区域、Smart Placement 或让计算靠近数据;不要仅因 Worker 在全球运行就宣布后端全球化。
实验还要验证数据合规和 residency。请求可能在不同位置执行,bindings/数据产品有各自地域保证。敏感数据不能只依据延迟最优路由;记录处理位置、存储位置、跨境要求和平台合同。日志字段也可能跨区域进入集中系统,需要同样审查。
驱逐与并发演练
在 handler 全局放一个仅用于测试的实例标识和有界计数,观察不同请求可能复用或切换实例,但不把具体复用率写成契约。并发发送请求,在一个请求 await 时让另一个修改测试缓存,确认代码没有依赖原子请求级全局访问。随后部署新版本或等待实例变化,权威结果必须仍来自持久 binding,而不是内存。
关键后台任务演练分两组:只用 waitUntil 的 cache/log 可以故意失败而不影响业务正确性;订单/通知等任务必须先写 Queue/持久记录,模拟 consumer 重试仍只产生一次业务效果。给每个任务幂等键和状态,不能用“通常 waitUntil 会完成”代替交付保证。
Compatibility date 升级工作表
列出旧日期到新日期之间所有相关 flags/changelog,特别是 URL、Headers、streams、Node APIs、WebSocket、encoding、cache 和 error 行为。固定 Wrangler 与依赖,只改日期跑 contract;再固定日期升级 Wrangler,分离变量。staging 使用真实 bindings,canary 记录 deployment/date/outcome。回滚时确认新数据/cache 没有产生旧代码不可读格式。
Node compatibility package 单独检查 bundle 中 native/polyfill/stub。一个模块新日期下从 polyfill 变 native 可能改变边界行为和体积;import 成功仍要执行方法、失败、stream 与性能测试。长期依赖 stub 的包应替换或移回区域 runtime,不把 noop 当兼容。