面向哲思的编程与架构
笔记哲思阅读动态搜索RSS 订阅
切换到深色模式
搜索
RSS 订阅
切换到深色模式
© 2026 Vic Chen. All rights reserved.CC BY-NC-ND 4.0
← 笔记
现代计算的基础设施(四):在边缘执行代码

现代计算的基础设施(四):在边缘执行代码

2026年4月26日3,75811分钟

把代码放到离用户最近的节点运行,听起来简单,但它要求一套与传统服务器完全不同的运行时设计。CF 为什么要自研 workerd,而不是直接用 Node.js?V8 Isolate 怎么在单进程内隔离数千个 Worker?Durable Objects 如何用单实例模型解决跨请求状态和强一致性?控制平面与数据平面分离为什么是边缘基础设施的核心架构模式?以及 Fly.io 选择 Firecracker microVM 的取舍逻辑,和这些约束如何影响 Next.js 的功能边界。


目录
  • 1. Edge Function vs Serverless Function:部署位置的差异
  • 2. 为什么 CF 要自研 workerd
  • 2.1 Node.js 的包袱
  • 2.2 workerd 的设计目标
  • 2.3 workerd 的内部架构
  • 2.4 Node.js 兼容层的问题
  • 3. Durable Objects:有状态的边缘
  • 3.1 Durable Objects 的设计
  • 3.2 DO 解决了什么问题
  • 3.3 DO 的代价
  • 4. V8 Isolate 对 Next.js 的架构约束
  • 4.1 具体约束分析
  • 5. 控制平面与数据平面分离
  • 5.1 配置分发的一致性问题
  • 6. Fly.io 的另一条路:Firecracker at Edge
  • 7. 本站执行层
  • 8. 专线 SaaS 的映射
目录
  • 1. Edge Function vs Serverless Function:部署位置的差异
  • 2. 为什么 CF 要自研 workerd
  • 2.1 Node.js 的包袱
  • 2.2 workerd 的设计目标
  • 2.3 workerd 的内部架构
  • 2.4 Node.js 兼容层的问题
  • 3. Durable Objects:有状态的边缘
  • 3.1 Durable Objects 的设计
  • 3.2 DO 解决了什么问题
  • 3.3 DO 的代价
  • 4. V8 Isolate 对 Next.js 的架构约束
  • 4.1 具体约束分析
  • 5. 控制平面与数据平面分离
  • 5.1 配置分发的一致性问题
  • 6. Fly.io 的另一条路:Firecracker at Edge
  • 7. 本站执行层
  • 8. 专线 SaaS 的映射
目录
  1. 1. Edge Function vs Serverless Function:部署位置的差异
  2. 2. 为什么 CF 要自研 workerd
  3. 2.1 Node.js 的包袱
  4. 2.2 workerd 的设计目标
  5. 2.3 workerd 的内部架构
  6. 2.4 Node.js 兼容层的问题
  7. 3. Durable Objects:有状态的边缘
  8. 3.1 Durable Objects 的设计
  9. 3.2 DO 解决了什么问题
  10. 3.3 DO 的代价
  11. 4. V8 Isolate 对 Next.js 的架构约束
  12. 4.1 具体约束分析
  13. 5. 控制平面与数据平面分离
  14. 5.1 配置分发的一致性问题
  15. 6. Fly.io 的另一条路:Firecracker at Edge
  16. 7. 本站执行层
  17. 8. 专线 SaaS 的映射
架构基础设施Serverless云原生
相关文章
  • 01
    现代计算的基础设施(五):一次请求的完整旅程2026/05
  • 02
    现代计算的基础设施(二):并发的重新发现2026/04
  • 03
    现代计算的基础设施(一):隔离的代价2026/04
← 上一篇现代计算的基础设施(三):边缘不是一种技术
下一篇 →现代计算的基础设施(五):一次请求的完整旅程

评论

© 2026 Vic Chen · 面向哲思的编程与架构CC BY-NC-ND 4.0

上一篇:现代计算的基础设施(三):边缘不是一种技术讲清楚了 BGP Anycast 和 PoP 架构。这篇聚焦执行层:代码真正跑在边缘节点上时,运行时需要解决哪些问题,这些约束又如何影响框架的功能边界。

1. Edge Function vs Serverless Function:部署位置的差异

这两个概念经常被混用,但它们描述的是不同的东西。

  • Serverless Function(AWS Lambda、Vercel Function)运行在区域数据中心——服务器在特定地理位置,函数代码路由到最近的区域执行,典型 RTT(用户到执行节点)50~200ms。

  • Edge Function(CF Workers、Vercel Edge Functions)运行在边缘 PoP——代码在每个 PoP 上都有副本,用户请求由 Anycast 路由到最近的 PoP 本地执行,典型 RTT < 20ms。

图1:Edge Function vs Serverless Function 延迟对比

延迟差异是真实的,但代价也是真实的:边缘 PoP 的计算资源远少于数据中心,运行时必须做出相应的约束。


2. 为什么 CF 要自研 workerd

CF Workers 的执行层运行时叫 workerd,2022 年开源,Rust + C++ 实现,约 10 万行代码。

为什么不直接用 Node.js?

2.1 Node.js 的包袱

Node.js 是为开发者体验优化的运行时,它带来了大量对服务端开发有用但对边缘执行有害的特性:

  • 庞大的依赖树:Node.js 的标准库和内置模块(fs、child_process、crypto、net、http……)加起来有数十万行 C++ 代码。每个模块都可能有安全漏洞,都是需要维护的攻击面。

  • 单 Isolate 架构:原生 Node.js 一个进程对应一个 V8 Isolate,不支持在单进程内运行多个相互隔离的 Isolate。CF 需要在一个进程里跑数千个 Worker,这需要自己管理 Isolate 的生命周期。

  • 启动开销:Node.js 进程启动需要初始化整个运行时(libuv、v8、所有内置模块),耗时 100ms 以上。边缘执行需要微秒级实例化,必须绕过这个初始化路径——这就是 V8 Snapshot 的用武之地,但 Node.js 没有对外暴露足够的控制接口。

  • 全局状态污染:Node.js 的 process、global、require 等全局对象在多 Isolate 场景下需要严格隔离,但 Node.js 的设计假设只有一个全局上下文。

2.2 workerd 的设计目标

workerd 从零设计,目标明确:

  • 多 Isolate 管理:单进程内运行数千个 Worker,自主管理 Isolate 生命周期
  • 最小化攻击面:只暴露必要的 Web 标准 API
  • V8 Snapshot 集成:微秒级实例化
  • 资源限制可插拔:CPU / 内存 / 网络 per-isolate
  • 确定性执行:无文件系统、无随机时钟

只暴露 Web 标准 API:workerd 实现了 WinterCG(Web-interoperable Runtimes Community Group)定义的 API 集合:fetch、Request、Response、Headers、URL、crypto(Web Crypto API)、Cache、ReadableStream……这些 API 在浏览器中也存在,是有标准规范的,行为可预测。

不暴露 fs、child_process、net(raw TCP)等 Node.js 特有 API,这些 API 要么无法在无文件系统的边缘环境运行,要么是安全风险。

2.3 workerd 的内部架构

图2:workerd 进程内部架构——HTTP API Server、请求调度器、Isolate 池与 Cap'n Proto RPC 的分层结构

workerd 使用 Cap'n Proto(Chen 注:CF 自研的序列化 / RPC 框架,采用基于偏移量的内存布局,读取时无需解码步骤,实现零拷贝;同时也是 workerd 的跨进程通信总线。) 作为内部 RPC 框架(CF 自研,比 Protocol Buffers 更快,零拷贝序列化),KJ(Chen 注:Cap'n Proto 仓库内附带的 C++ 异步 I/O 框架,提供协程、Promise 和事件循环抽象,workerd 用它替代 libuv 直接对接 epoll/kqueue。) 作为 C++ 异步框架(也是 Cap'n Proto 的一部分)。这套组合让 workerd 的 I/O 处理绕开了 libuv,直接使用操作系统的 epoll/kqueue,性能更可控。

2.4 Node.js 兼容层的问题

CF Workers 也提供了一个 Node.js 兼容标志(nodejs_compat),允许使用部分 Node.js 内置模块(buffer、crypto、events、path、stream……)。

但这个兼容层是多边妥协的结果,而不是完整实现:

  • fs 没有:边缘节点没有持久文件系统
  • net(raw TCP)没有:出于安全考虑,只允许 HTTP/HTTPS 出站
  • child_process 没有:无法 fork 子进程
  • async_hooks 支持不完整:部分追踪 API 行为与 Node.js 不一致
  • 第三方库对 Node.js API 的假设:很多 npm 包在 CF Workers 上运行时会遇到「这个功能不支持」的运行时错误

这不是 CF 工程能力的问题,是架构约束的必然结果:边缘 PoP 没有文件系统,没有操作系统进程,把 Node.js 的完整 API 塞进 V8 Isolate 在物理上就是做不到的事。


3. Durable Objects:有状态的边缘

无状态边缘执行有一个根本性的限制:每个请求到达哪个 PoP 是由网络拓扑决定的,对于同一个用户的两次请求,可能路由到不同的 PoP——这意味着无法在 Worker 进程内维护跨请求的状态。

CF 的 Durable Objects(DO) 是这个问题的架构解法。

3.1 Durable Objects 的设计

一个 Durable Object 是一个 V8 Isolate + 一块持久化键值存储的组合,但有一个关键约束:全球只有一个实例。

图3:Durable Object 全球唯一实例路由

所有对 chat-room-42 的请求,无论用户在哪里,都会被 CF 的路由层转发到 DO 当前运行的节点。DO 内部是单线程执行(基于 Actor 模型),所有请求串行处理,不需要锁,天然一致。

DO 的持久化存储基于强一致性——写入确认后,下次读取一定能读到最新值(线性一致性),而不是最终一致性。

3.2 DO 解决了什么问题

传统分布式状态的三个核心挑战:

挑战传统方案Durable Objects 方案
并发写冲突分布式锁 / CAS / 事务单实例串行执行,无并发
跨请求状态Redis / 数据库Isolate 内存 + 持久化存储
强一致性主从复制 + 同步写单实例 + 持久化日志

实时协作(多人同时编辑文档)、WebSocket 连接状态管理、游戏房间——这些场景都需要在一个地方维护一致的状态,DO 的单实例模型天然满足这个需求。

图4:Durable Object 作为 WebSocket Hub 的消息处理时序——单实例串行广播,无需分布式锁

3.3 DO 的代价

全球唯一实例意味着:所有请求必须路由到 DO 所在的那个节点。如果 DO 被放置在弗吉尼亚,上海用户的每次请求都要跨越太平洋——边缘部署的延迟优势全部抵消。

DO 的延迟特性取决于:

  • DO 的位置:CF 会把 DO 放在最先访问它的客户端所在的区域(或手动指定)
  • 用户分布:用户越集中在某个区域,DO 越有价值;用户全球分散时,总有一部分用户会遭遇高延迟

这是 DO 设计上无法绕过的物理约束:一致性要求单点,单点意味着某些用户距离更远。


4. V8 Isolate 对 Next.js 的架构约束

Next.js 在 CF Workers 上支持不完整,这是一个被反复讨论的问题。根本原因在于 Next.js 的功能设计假设了完整的 Node.js 运行时,而 CF Workers 的 V8 Isolate 模型无法提供这些。

4.1 具体约束分析

  • next/image 图片优化:Next.js 的图片优化依赖 sharp(一个基于 libvips 的原生 Node.js 模块),需要编译成 native addon,无法在 V8 Isolate 中运行。CF Workers 环境下,图片优化请求会 fallback 到无优化版本,或需要使用 CF 自己的图片转换服务替代。

  • Middleware 的 Node.js API:Next.js Middleware 在 Vercel 上运行在 Edge Runtime(一个受限的 Node.js 子集),但 CF Workers 的兼容层与 Vercel 的 Edge Runtime 并不完全对齐,部分 API 行为不一致。

  • Server Actions:Next.js 的 Server Actions 在服务端执行,需要访问完整的 Node.js API(包括与数据库的 native 驱动通信)。CF Workers 的网络限制(只允许 HTTP/HTTPS 出站,无 raw TCP)使得 PostgreSQL、MySQL 等原生协议的数据库连接无法直接使用——必须通过 HTTP 代理或 CF 的 Hyperdrive 服务。

  • fs 模块读取文件:某些 Next.js 内部实现在运行时读取本地文件(如国际化消息文件、配置文件),在无文件系统的边缘环境中需要特殊处理。

图5:Next.js 功能层在 CF Workers 上的可用性矩阵

这不是 @cloudflare/next-on-pages(CF 的 Next.js 适配器)的工程问题,是底层运行时的架构约束。适配器能做的是尽量 polyfill、转换代码、提供替代实现,但无法凭空创造出 V8 Isolate 里不存在的 native 能力。


5. 控制平面与数据平面分离

CF 和 Vercel 的全球执行基础设施都基于同一个架构模式:控制平面与数据平面分离。

图6:控制平面与数据平面分离

关键设计原则:用户请求的执行路径绝对不经过控制平面。控制平面负责配置分发、代码部署、指标聚合,这些都是异步操作;数据平面独立处理所有实时请求,即使控制平面发生故障,已部署的 Worker 依然在运行。

这个分离带来了两个重要特性:

  1. 水平可扩展性:增加一个新 PoP 只需要从控制平面同步最新的代码和配置,然后开始接收流量——控制平面本身不参与请求处理,不成为瓶颈。

  2. 故障隔离:控制平面的故障(部署系统宕机、指标收集延迟)不影响已运行的数据平面。用户感知的降级只有「无法发布新版本」,而不是「服务中断」。

5.1 配置分发的一致性问题

控制平面向全球数百个 PoP 分发代码和配置,这是一个典型的分布式一致性问题:如何保证所有 PoP 在同一时间运行相同版本的代码?

答案是:不需要保证。

边缘部署接受短暂的版本不一致。一次部署触发后,新版本代码会在数秒到数十秒内逐渐同步到所有 PoP。在这个时间窗口内,不同 PoP 可能运行不同版本的代码。

这意味着部署时如果有 breaking change,需要做兼容性处理(比如 API 变更需要前向兼容),不能假设全球同步切换。这是 Edge 部署与传统蓝绿部署的根本区别。


6. Fly.io(Chen 注:PaaS 平台,主打「把容器/VM 部署到离用户最近的节点」。底层用 Firecracker microVM 提供隔离,每个应用实例是一个轻量级 VM,支持任意语言和原生模块。与 CF Workers 的 V8 Isolate 模型相比,运行时能力完整,但 PoP 密度(~40 个城市)远低于 CF(300+),冷启时间也更长(~125ms vs <1ms)。) 的另一条路:Firecracker at Edge

Fly.io 选择了与 CF 完全不同的边缘执行方案:在边缘节点运行 Firecracker microVM,提供完整的 Linux 运行时。

图7:CF Workers(V8 Isolate)与 Fly.io(Firecracker microVM)模型对比

Fly.io 的选择反映了一个不同的取舍:宁愿 PoP 少一些,也要保留完整的运行时能力。这让它能支持任何语言(不只是 JS),能跑任何依赖原生模块的代码,能维护进程内状态(Fly 有持久 Volume)。

代价是 PoP 密度不如 CF,冷启时间也更长。对于需要完整运行时且能接受稍高延迟的场景,Fly.io 是一个比 CF Workers 更合适的选择。


7. 本站执行层

本站的 Middleware 运行在 Vercel 的 Edge Runtime(基于 V8 Isolate,不是完整 Node.js),负责统计 UV 和 PV——每个请求都经过 Middleware,记录到 Upstash Redis。

这个 Middleware 用了以下 Web 标准 API:Request、Response、NextResponse(Vercel 扩展)、fetch(访问 Upstash HTTP API)——全部在 Edge Runtime 的支持范围内,没有 Node.js 专有 API,所以没有兼容性问题。

如果在 Middleware 里加一行 const fs = require('fs'),部署就会失败——这正是 V8 Isolate 架构约束的直接体现。

API Routes(评论读写、内容审核)跑在 Fluid Function(完整 Node.js),可以用任何 Node.js API,包括原生 crypto 模块和 Node.js 的 Buffer API。这两类函数在同一个 Next.js 项目里,但运行在两种完全不同的执行环境里——这个隐式的环境差异是理解 Next.js on Vercel 的关键。


8. 专线 SaaS 的映射

控制平面与数据平面分离这个模式,是专线 SaaS 最值得直接借用的架构思路。

在专线 SaaS 场景下,需要面对的现实是:多个数据中心、多条专线、多个客户接入点,加上不同版本的服务在不同客户环境上的部署管理。

图8:专线 SaaS 控制/数据平面分离架构

关键实践:

  • 数据平面不依赖控制平面的实时可达性。每个数据中心节点在本地缓存最新的配置快照,即使到控制平面的专线发生抖动,业务请求依然能正常处理(用本地缓存的最后一份配置)。配置更新是最终一致性,不是强一致性。

  • 版本管理的复杂度:专线 SaaS 的版本管理比公网服务复杂——不同客户可能在不同的版本(合规要求、测试周期、合同约定),控制平面需要支持「客户 A 跑 v2.3,客户 B 跑 v2.1」的多版本并存。这直接影响部署系统的设计,不能用简单的「全局最新版本」滚动更新。

  • 可观测性的专线约束:指标和日志从数据平面上报到控制平面,需要走专线带宽。设计时要考虑:日志采样率、指标聚合粒度、是否在数据中心本地做预聚合再上报,避免可观测性流量占用大量专线带宽。

  • 运行时选型:在专线 SaaS 场景,V8 Isolate 的约束(不能用 fs、不能用原生模块)往往不可接受——服务代码很可能依赖数据库驱动、加密库、文件处理等能力。除非服务负载恰好是纯 HTTP 的轻量逻辑,否则应该选择完整 Node.js 容器(对应 Fluid Compute 模型)而不是 V8 Isolate(对应 CF Workers 模型)。


下一篇:现代计算的基础设施(五):一次请求的完整旅程