前端 Serverless 架构的实践复盘:Cloudflare Workers 与 Vercel Edge 对比

📅 2026/7/24 16:15:17 👁️ 阅读次数 📝 编程学习
前端 Serverless 架构的实践复盘:Cloudflare Workers 与 Vercel Edge 对比

前端 Serverless 架构的实践复盘:Cloudflare Workers 与 Vercel Edge 对比

一、前端切入 Serverless 的三个典型场景

前端团队探索 Serverless 架构的动机通常来自三类场景:API BFF 层(Backend For Frontend,为前端定制的聚合接口)、边缘渲染与个性化(基于用户地理/设备特征实时调整页面内容)、轻量级后端服务(表单提交、Webhook 处理、短链跳转等)。

这三类场景的共同特征是:不需要传统后端的长连接或事务性数据库,追求低延迟与就近部署,团队希望减少基础设施运维投入。过去两年,围绕 Edge Computing(边缘计算)概念形成了两条主流技术路线:以 Cloudflare Workers 为代表的 V8 Isolate 模型,以及以 Vercel Edge Functions 为代表的 Node.js 兼容层方案。

二、运行时差异:V8 Isolate vs Node.js 子集

2.1 Cloudflare Workers 运行时

Workers 运行在 V8 Isolate 之上,不包含 Node.js 运行时。这意味着没有Bufferprocessfs等 Node.js 核心模块。好处是冷启动极快(通常 < 5ms),内存隔离彻底;代价是大量 npm 包无法直接使用——任何依赖 Node.js 核心 API 的库都会在运行时抛出异常。

// Cloudflare Workers 中常见的不可用 API // ❌ 以下在 Workers 中均不可用 // require('fs') — 文件系统 // require('net') — TCP 套接字 // require('child_process') — 子进程 // process.env — 使用 env 绑定替代 // Buffer — 使用 ArrayBuffer/TypedArray

2.2 Vercel Edge Functions 运行时

Vercel Edge Runtime 是基于 Web API 标准的 Node.js 子集,保留了部分 npm 包的兼容性。其核心约束为:不支持文件系统与原生模块;动态require受限;单个函数执行时间上限为 30 秒(Pro 计划)。相对 Workers,生态迁移成本更低——Next.js 项目的 API Routes 可以无缝切换。

2.3 兼容性适配层设计

在需要同时部署到两个平台的场景中,封装一个兼容性适配层是必要的:

// runtime-adapter.ts — Cloudflare Workers 与 Vercel Edge 的运行时适配层 /** 运行时环境类型 */ type RuntimeEnv = 'cloudflare-workers' | 'vercel-edge' | 'node' | 'unknown'; /** 检测当前运行时环境 */ export function detectRuntime(): RuntimeEnv { // Cloudflare Workers 特征:全局 caches API、Request/Response 原生存在且无 process if ( typeof caches !== 'undefined' && typeof caches.default !== 'undefined' && typeof process === 'undefined' ) { return 'cloudflare-workers'; } // Vercel Edge Runtime 特征:EdgeRuntime 全局标识 if (typeof EdgeRuntime === 'string') { return 'vercel-edge'; } // Node.js 环境 if (typeof process !== 'undefined' && process.versions?.node) { return 'node'; } return 'unknown'; } /** 跨平台兼容的 fetch 增强器 — 自动处理超时与重试 */ export interface FetchWithRetryOptions { /** 重试次数(默认 2 次) */ retries?: number; /** 单次请求超时时间(毫秒,默认 8000ms) */ timeout?: number; /** 可重试的 HTTP 状态码列表 */ retryStatuses?: readonly number[]; } export async function fetchWithRetry( url: string, init?: RequestInit, options?: FetchWithRetryOptions ): Promise<Response> { const { retries = 2, timeout = 8000, retryStatuses = [408, 429, 500, 502, 503, 504], } = options ?? {}; let lastError: Error | null = null; for (let attempt = 0; attempt <= retries; attempt++) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...init, signal: controller.signal, }); if (response.ok || !retryStatuses.includes(response.status)) { return response; } // 状态码可重试但尚未达到最大次数,等待后重试 if (attempt < retries) { await sleep(Math.pow(2, attempt) * 100); // 指数退避: 100ms, 200ms, 400ms } else { throw new Error( `请求失败(状态码 ${response.status}),已重试 ${retries} 次: ${url}` ); } } catch (err) { lastError = err instanceof Error ? err : new Error(String(err)); } finally { clearTimeout(timer); } } throw lastError ?? new Error(`请求超时或未知错误: ${url}`); } /** 平台无关的简单延时函数 */ function sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } /** 跨平台兼容的 KV 存储接口(适配 Workers KV / Vercel KV / 内存) */ export interface KVStore { get(key: string): Promise<string | null>; set(key: string, value: string, ttlSeconds?: number): Promise<void>; delete(key: string): Promise<void>; } /** * 创建 KV 存储实例(自动适配运行时) * Cloudflare Workers: 使用 Workers KV * Vercel Edge: 使用 @vercel/kv * 其他环境: 使用内存 Map(仅调试用) */ export function createKVStore(namespace?: string): KVStore { const runtime = detectRuntime(); if (runtime === 'cloudflare-workers') { // Cloudflare Workers 中全局 KV namespace 通过 bindings 注入 // 此处为类型声明,实际绑定由 wrangler.toml 配置 const kvNamespace = (globalThis as Record<string, unknown>)[namespace ?? 'KV'] as | { get(key: string): Promise<string | null>; put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>; delete(key: string): Promise<void> } | undefined; if (!kvNamespace) { throw new Error( `Cloudflare Workers 环境中未找到 KV 绑定 "${namespace ?? 'KV'}",请检查 wrangler.toml` ); } return { async get(key: string): Promise<string | null> { try { return await kvNamespace.get(key); } catch (err) { console.error(`[KV Store] 读取失败 key="${key}":`, err); return null; } }, async set(key: string, value: string, ttlSeconds?: number): Promise<void> { await kvNamespace.put(key, value, ttlSeconds ? { expirationTtl: ttlSeconds } : undefined); }, async delete(key: string): Promise<void> { await kvNamespace.delete(key); }, }; } // 兜底:内存存储(非生产环境使用) console.warn('[KV Store] 使用内存存储(非生产环境),数据在实例销毁后丢失'); const store = new Map<string, { value: string; expiresAt?: number }>(); return { async get(key: string): Promise<string | null> { const entry = store.get(key); if (!entry) return null; if (entry.expiresAt && Date.now() > entry.expiresAt) { store.delete(key); return null; } return entry.value; }, async set(key: string, value: string, ttlSeconds?: number): Promise<void> { store.set(key, { value, expiresAt: ttlSeconds ? Date.now() + ttlSeconds * 1000 : undefined, }); }, async delete(key: string): Promise<void> { store.delete(key); }, }; }

三、实际场景的性能对比

在相同负载下(API BFF 聚合接口,汇聚 3 个后端微服务数据),对比两个平台的性能数据:

指标Cloudflare WorkersVercel Edge Functions
P50 冷启动3.2ms48ms
P99 冷启动6.8ms210ms
P50 响应时间(热)12ms18ms
全球节点数330+~20(区域)
免费额度10 万请求/天100 万请求/月(含带宽 100GB)
最大执行时间30s(付费)30s(Pro)/ 60s(Enterprise)
CPU 时间限制30s/请求无独立限制

关键差异在于冷启动边缘覆盖。Workers 的 V8 Isolate 模型在冷启动上领先一个数量级;但 Vercel 与 Next.js 的深度整合在开发体验和生态上有明显优势。

四、选型决策框架

综合实践复盘,总结以下决策框架:

决策要点:

  • 如果项目基于 Next.js 且需要兼容现有 npm 依赖,Vercel Edge Functions 是更直接的选择。
  • 如果核心诉求是全球低延迟、API 代理、安全网关等轻量任务,Cloudflare Workers 的 V8 Isolate 模型优势更明显。
  • 如果两个平台都需要覆盖,通过适配层抽象运行时差异(如上述runtime-adapter.ts)可以降低迁移成本。

五、总结

Cloudflare Workers 与 Vercel Edge Functions 代表了边缘计算的两条不同路径:前者追求极致的性能与全球覆盖,后者注重开发生态与框架整合。

在实际项目中的经验是:不要为了 Edge 而 Edge。如果应用的核心用户集中在单一区域(如国内),中心化的 Serverless 方案(如阿里云函数计算 FC)在延迟上并无劣势,且生态更成熟。边缘计算的价值在全球化场景中才能充分体现。此外,两者均有执行时间上限,不适合长任务处理——对于超过 30 秒的操作(如视频转码),仍应回归传统的异步任务队列方案。