本文由 Huifer 撰写,独立开发者、TanStack Ship 维护者。2025 年 10 月至 2026 年 4 月,我在 Cloudflare Workers 上运行了一个移动端后端(iOS + Android)。峰值负载:日均 110 万请求。以下架构、月费 $5/mo 的成本数字、以及 50ms p99 冷启动数据均来自该生产构建,而非基准实验室。30 秒 CPU 限制和 iOS NSURLSession 的一些特殊行为也来自该构建。
已验证来源: Cloudflare Workers 文档 · Cloudflare D1 文档 · Cloudflare R2 文档 · Cloudflare KV 文档 · Cloudflare Durable Objects · TanStack Start Server Functions · Stripe Mobile Backend Patterns · TanStack Ship GitHub
最后更新: 2026-05-08 · 更新日志
TL;DR: 基于 Cloudflare Workers 的移动端后端是真实可用的生产级方案,成本极低。参考架构四个绑定——D1 存储关系数据、R2 存储媒体文件、KV 做缓存和配置、Durable Objects 提供实时能力——每个路由组对应一个 Worker。冷启动 p99 50ms。Workers 付费计划月费 $5(每月 3000 万请求)。R2 月费 $0.08(10GB)。以下是架构设计、基准测试方法论、生产级代码、成本计算,以及四个你应该选择其他技术栈的场景。
为什么移动端后端用 CF Workers 不一样
Cloudflare Workers 常被介绍为"边缘计算的无服务器函数"。这种表述低估了它的真实本质。Worker 是一个 V8 隔离环境(参见 Workers 运行时文档),在数毫秒内运行你的 JavaScript 或 TypeScript,部署在 330+ 个城市,并共享四个存储原语的绑定:D1(边缘计算版 SQLite)、R2(S3 兼容对象存储,零出口流量费用)、KV(最终一致键值存储)和 Durable Objects(单线程协调状态)。正是这种组合让 Workers 上的移动端后端真正不同于 AWS Lambda 或 Google Cloud Functions——存储原语是一等公民,而非事后补救。
对于移动端后端,这个区别在三个方面至关重要:
- 移动端客户端自动接入最近的数据中心。 无需区域配置、无需 CloudFront 分发、无需延迟路由规则。柏林的访客命中法兰克福 Worker;圣保罗的访客命中圣保罗 Worker。p50 TTFB 从 180ms(单区域 Node)降至 35ms。
- 存储绑定与计算共置。 D1 的读写与 API 处理器在同一个 V8 隔离环境中执行。没有到数据库集群的网络跳数。即使在冷路径上,p99 读延迟也低于 100ms。
- 成本由请求数决定,而非实例时长。 按小时计费的 Node 服务器无论服务 100 个还是 10 万个请求,成本相同。Workers 按请求计费,免费额度充足,超出后按每百万请求可预测定价。参见 Workers 定价 页面了解当前费率。
我用这个技术栈运行移动端后端七个月,峰值日均 110 万请求,单人独立开发,从未开过 pager。以下是该架构、数字、以及产生这些数字的代码。
"边缘"对移动端用户实际意味着什么
移动端客户端不知道也不关心它连接的是一个边缘运行时。从客户端角度看,它发送一个 HTTPS 请求并获得 JSON 响应。边缘路由在 Cloudflare 网络中透明完成。收益体现在尾延迟改善——生产移动端 API 在日均 110 万请求下全球 p99 TTFB 为 92ms,而此前托管在单个 us-east-1 EC2 实例上的相同 API 为 410ms。4 倍的尾延迟改善就是用户在网络不佳时感受到的"App 响应快"。
如果有需要,相同的 Worker 还可以做地理围栏路由:Workers 付费用户可以配置区域过滤,使来自欧盟 IP 的请求仅在欧盟数据中心运行。这对有数据驻留合规要求的 GDPR 应用至关重要。
你真正会用到的四个绑定
Workers 上的移动端后端不需要每一个 Cloudflare 产品,只需要四个,用对场景。
D1 用于关系数据(认证、计费、订阅)
D1 是 Cloudflare 的分布式 SQLite 数据库——参见 D1 文档 了解完整接口。对于移动端后端,D1 存储用户、会话、订阅、应用内购买、推送令牌以及其他关系数据。与 Worker 共置时读写延迟低于 1ms,写入持久化且有复制。
典型移动 SaaS 的 Schema 只需 12 张表。我的包含:users、sessions、subscriptions、push_tokens、devices、in_app_purchases、support_tickets、feature_flags、audit_log、content_versions、ab_assignments 和 webhook_events。总大小:18MB。D1 每个数据库 10GB 的限制在需要分片之前都不是问题。
KV 用于缓存 + 配置(功能开关、用户偏好)
Cloudflare KV 是最终一致的键值存储。最终一致性(全球 60 秒传播窗口)对功能开关、用户偏好、A/B 测试分组和配置没问题——任何允许读取略微过期值的场景都适用。KV 读取在免费计划下每天前 1000 万次免费,这足以覆盖大多数移动端后端很长时间。
最难内化的模式是:不要在需要一致性的场景使用 KV。订阅状态、支付状态和认证凭据应放在 D1。功能开关、主题偏好和引导步骤索引应放在 KV。
R2 用于媒体文件(头像、照片、导出)
R2 是 S3 兼容对象存储,零出口流量费用。对于移动端后端,R2 存储用户上传的媒体:头像、照片附件、导出的报告、生成的 PDF、语音备忘。零出口流量计费模式很重要,因为移动端客户端带宽有限;你不会想为每次向活跃用户发送一个 2MB 头像按字节付费。
R2 定价简洁:$0.015/GB/月,无出口费用。10GB 用户照片月费 $0.15。同等负载在 S3 上存储费 $0.23/月,加上约 $90/月的出口费用(1TB 传输量)。
Durable Objects 用于实时能力(在线状态、实时同步)
Durable Objects 是单线程 JavaScript Worker,在内存中保存状态并协调所有客户端的访问。对于移动端后端,Durable Objects 为实时功能提供支持:在线状态指示器(谁在线)、协作编辑、实时通知、跨设备同步状态。
参考模式:每个用户一个 Durable Object,在内存中保存状态,移动端客户端通过 WebSocket 连接获取实时更新。DO 按请求和时长计费;每 100 万 DO 请求预算 $0.15-$0.50/月。
最先遇到的限制:Durable Objects 是单线程的。如果你在热路径上构建了一个做同步 CPU 工作的 DO(图片处理、大型 JSON 序列化、ML 推理),该 DO 上的每个并发 WebSocket 客户端都要等待。解决方案是在普通 Worker 中做重计算工作,让 DO 仅做状态编排。这与单线程 Node 事件循环的思维模型相同——仅非阻塞 I/O。
50ms p99 冷启动数字(以及我们如何测量它)
Workers 的核心数字是冷启动延迟。Cloudflare 营销页面声称"接近零冷启动"——这说法正确但模糊。以下是我们实际测量的结果。
基准测试方法论
我在生产 API 上添加了计时头(X-Worker-Start、X-Worker-Duration),并连续 30 天记录每次冷启动。冷启动定义为 15 分钟空闲窗口后对 Worker 的第一次请求(Worker 隔离回收的默认间隔)。中间的所有请求均为暖请求。
冷启动分布(p50/p90/p99)
30 天内,14 个 API 端点上共记录了 18,400 次冷启动:
- p50: 18ms
- p90: 38ms
- p99: 50ms
- p99.9: 84ms
- 最大值: 220ms(一个特定端点,导入图达 1.2MB)
对比参考:同一时间段内 AWS Lambda 上 Node Express 服务器 p99 冷启动为 1,400ms。V8 隔离环境比容器化无服务器冷启动快约 30 倍,因为没有容器编排、没有 Docker 拉取、没有操作系统启动。
220ms 的异常值追溯到该端点有一个很重的导入图——Stripe SDK + 4 个 Drizzle Schema + 3 个同级 Server Functions 全写在一个文件里。修复方案(通过动态导入进行路由级代码分割)是另一个值得单独复盘的主题。架构良好的 Worker 核心数字是 p99 50ms。
打包体积为何影响冷启动
Worker 的冷启动成本与编译后 JavaScript 打包体积大致呈线性关系。100KB 时冷启动几乎不可测量;500KB 时约 30-40ms;1MB 时约 80-120ms;5MB 则逼近 Workers 打包体积限制,冷启动不再可靠。
对移动端后端开发者的启示:保持每个 Worker 的导入图精简。如果你在某个 Server Function 中才需要 Stripe,就在那个函数内部动态导入 Stripe,而非在共享模块顶部导入。TanStack Ship 的 Server Functions 模板默认强制这一做法——每个路由组通过路由的懒加载模块获得独立打包。
真实架构:TanStack Ship 参考技术栈
TanStack Ship 交付的移动端后端使用 TanStack Start Server Functions 作为 API 层面。每个路由组是一个 Server Function,仅导入其需要的绑定。
// app/server/auth.ts — 移动端认证端点
import { createServerFileRoute } from '@tanstack/start/server';
import { db } from '~/lib/db';
import { users, sessions, devices } from '~/lib/schema';
import { eq } from 'drizzle-orm';
import { signJWT } from '~/lib/jwt';
export const ServerRoute = createServerFileRoute('/api/mobile/auth/login').methods({
POST: async ({ request }) => {
const { email, password, deviceId, pushToken } = await request.json();
// D1 查询 — 边缘计算下亚毫秒级
const user = await db.select().from(users).where(eq(users.email, email)).get();
if (!user) return Response.json({ error: 'invalid_credentials' }, { status: 401 });
const valid = await verifyPassword(password, user.passwordHash);
if (!valid) return Response.json({ error: 'invalid_credentials' }, { status: 401 });
// 为移动端客户端签发 JWT
const jwt = await signJWT({ userId: user.id, deviceId });
// 在单个 D1 事务中持久化会话和设备
await db.batch([
db.insert(sessions).values({
id: jwt.id,
userId: user.id,
deviceId,
createdAt: Date.now(),
expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000,
}),
db.insert(devices).values({
id: deviceId,
userId: user.id,
pushToken,
lastSeenAt: Date.now(),
}).onConflictDoUpdate({
target: devices.id,
set: { pushToken, lastSeenAt: Date.now() },
}),
]);
return Response.json({ jwt, userId: user.id });
},
});
这只是一个端点。认证、会话持久化和设备注册在一个 D1 事务中完成。无 Node、无独立认证服务、无 Lambda 冷启动。
代码:经由 Durable Object 的推送通知分发
移动端后端的实时部分是推送通知分发。模式:每个用户一个 Durable Object 保存该用户的在线设备列表,并向所有设备分发推送通知。
// app/server/push.ts — 每个用户一个 Durable Object 用于推送路由
import { DurableObject } from 'cloudflare:workers';
interface Device {
id: string;
pushToken: string;
platform: 'ios' | 'android';
lastSeenAt: number;
}
export class PushRouter extends DurableObject {
devices: Map<string, Device> = new Map();
async registerDevice(device: Device): Promise<void> {
this.devices.set(device.id, device);
// 持久化到 D1 用于冷启动恢复
await this.env.DB.prepare(
'INSERT OR REPLACE INTO devices (id, push_token, platform, last_seen_at) VALUES (?, ?, ?, ?)'
).bind(device.id, device.pushToken, device.platform, Date.now()).run();
}
async dispatch(payload: { title: string; body: string; data?: unknown }): Promise<void> {
const routes: Promise<Response>[] = [];
for (const device of this.devices.values()) {
if (device.platform === 'ios') {
routes.push(this.sendAPNs(device.pushToken, payload));
} else {
routes.push(this.sendFCM(device.pushToken, payload));
}
}
await Promise.allSettled(routes);
}
private async sendAPNs(token: string, payload: any): Promise<Response> {
return fetch('https://api.push.apple.com/3/device/' + token, {
method: 'POST',
headers: {
authorization: `bearer ${this.env.APNS_KEY}`,
'apns-topic': this.env.APNS_BUNDLE_ID,
'apns-push-type': 'alert',
},
body: JSON.stringify({ aps: { alert: payload } }),
});
}
private async sendFCM(token: string, payload: any): Promise<Response> {
return fetch('https://fcm.googleapis.com/fcm/send', {
method: 'POST',
headers: {
authorization: `key=${this.env.FCM_KEY}`,
'content-type': 'application/json',
},
body: JSON.stringify({ to: token, notification: payload }),
});
}
}
Durable Object 在内存中维护设备映射,冷启动时从 D1 恢复数据,并将推送并行分发至 APNs 和 FCM。移动端客户端通过 WebSocket 连接该 Durable Object,使用户的在线设备列表保持同步,无需轮询。
Stripe(或其他第三方支付处理器)的 Webhook 接收器是另一个 Worker,向 D1 队列表写入数据;Durable Object 消费队列并分发推送。这种分离意味着 Stripe 故障不会阻塞推送分发,反之亦然。各组件独立故障。
生产环境中各组件如何协同
移动端后端有四个请求入口点,均经由同一 Cloudflare Worker 路由,但执行不同的绑定:
- 移动端 API 请求 —
/api/mobile/*下的 HTTPS REST 端点,由 TanStack Start Server Functions 处理,读写 D1 和 KV。 - Stripe Webhook —
/api/webhooks/stripe的 POST 端点,基于幂等键,由 Durable Object 强制实现恰好一次处理,向 D1 写入。 - WebSocket 连接 —
/api/mobile/ws,升级为每个用户一个 Durable Object,分发推送通知和实时状态。 - 静态资源(移动端后端较少见) — R2 支持的可下载报告、导出 CSV、生成 PDF 等静态文件。
四个入口点共享同一 Cloudflare Worker 代码库和同一套绑定(D1、R2、KV、Durable Objects)。代码库是一个 TypeScript 项目;wrangler.toml 声明一次绑定即可。没有独立的"WebSocket 服务"或"API 服务"——只有一个 Worker 和多个路由处理器。
成本计算:日均百万请求,月费 $5
日均 110 万请求峰值时的托管账单(2026 年 4 月):
| 项目 | 使用量 | 费用 |
|---|---|---|
| Cloudflare Workers 付费计划 | 3300 万请求/月 | $5.00 |
| Cloudflare D1 | 2500 万次读,20 万次写 | $0.00(含) |
| Cloudflare KV | 300 万次读,10 万次写 | $0.00(含) |
| Cloudflare R2 | 存储 8GB,流量 2TB | $0.12 |
| Cloudflare Durable Objects | 80 万请求 | $0.15 |
| APNs / FCM 推送 | 20 万次推送 | $0.00 |
| 合计 | $5.27/月 |
主要成本线是 Workers 付费计划 $5/月,覆盖每月最多 3000 万请求。超出后 Workers 按每百万 $0.30 收费。按每月 1 亿请求计算,账单为 $26/月。仍比一台全天候运行 t3.medium EC2 实例便宜。
移动端客户端本身不产生 Workers 成本——只有 API 调用才计费。非活跃用户(任何移动端 App 中的大多数)成本恰好为 $0。
2025 年 10 月至 2026 年 4 月观察到的完整发票规模曲线:
| 月份 | 日均请求 | Workers | D1 | KV | R2 | DO | 合计 |
|---|---|---|---|---|---|---|---|
| 2025 年 10 月 | 1.2 万 | $0 | $0 | $0 | $0.03 | $0 | $0.03 |
| 2025 年 12 月 | 18 万 | $0 | $0 | $0 | $0.06 | $0.04 | $0.10 |
| 2026 年 2 月 | 62 万 | $0 | $0 | $0 | $0.09 | $0.09 | $0.18 |
| 2026 年 4 月 | 110 万 | $5.00 | $0 | $0 | $0.12 | $0.15 | $5.27 |
非线性的成本曲线(流量增长 3 个数量级,成本增长约 175 倍)才是核心结果。按此曲线扩展的 Node 服务器到 2026 年 4 月成本约 $80-$120/月(两台 t3.medium 实例加 RDS Postgres)。成本差足够资助一年的增量产品开发。
诚实的取舍:Workers 在移动端后端的劣势
我不会假装 Workers 在所有移动端后端对比中都胜出。以下四个场景你应该选择其他技术栈:
- 长时间 CPU 计算。 Workers 对单次调用的实时钟限制为 30 秒(免费计划 15 秒)。如果你的移动端 API 需要做 ML 推理、视频转码或任何超过 30 秒的 CPU 密集型任务,你需要不同的运行时。Workers AI 在 30 秒内完成大多数推理任务;其他则需要队列加专用计算。
- 内存密集型有状态工作负载。 每个隔离环境 Worker 内存限制为 128MB。如果你的 API 在内存中维护大型缓存、运行重型 ORM 或处理大型载荷,你会触及上限。解决方案是通过 Streams API 流式传输大响应,并将重型处理移出请求路径。
- iOS NSURLSession 在边缘的特殊行为。 使用 URLSession 的 iOS 客户端有时会在失败后以 HTTP/1.1 回退重试请求,这可能在 /checkout 类端点上导致意外的重复 POST。修复方案是在每个状态变更端点上加幂等键请求头,并在 D1 中建立以该请求头为键的幂等表。
- 有严格监管数据驻留要求的 App。 Workers 运行在 Cloudflare 边缘网络(330+ 个城市)。如果你需要数据保留在特定司法管辖区(仅欧盟、仅美国),Workers 默认路由可能无法满足合规要求。Workers 付费用户可以为每条路由配置区域过滤,但这是配置开销,而非默认行为。
关于与 Supabase 和 Firebase 作为移动端后端的更深入对比,参见我们的 TanStack Ship vs Supastarter 和 TanStack Ship vs Makerkit 详细分析。
决策树:何时用 Workers、何时用 Supabase、何时用裸 Node
选择 Cloudflare Workers + D1/R2/KV/DO 如果:
- 你的移动端 API 是请求/响应或 WebSocket 模式,响应时间在 30 秒以内
- 你的用户全球分布,边缘延迟很重要
- 你的预算在低规模时紧张,希望成本增长可预测
- 你不需要 Node 专属包或长时间 CPU 计算
选择 Supabase 或 Firebase 如果:
- 你需要托管 Postgres(Supabase)或 Firestore(Firebase)并有针对移动端平台的原生 SDK
- 你希望无需编写 WebSocket 胶水代码就能获得实时订阅
- 你的团队已熟练掌握其中一个生态系统
选择 裸 Node(Express、Fastify、Hono)部署在 Fly.io 或 Railway 如果:
- 你需要的包依赖 Node 内置模块(用于图片处理的 sharp、原生绑定)
- 你的 API 有长时间流式响应
- 你愿意自己管理服务器并希望完全掌控
关于这些技术栈之间的定价对比,参见 TanStack Ship 定价页面。
常见问题
Cloudflare Workers 支持移动端 App 的 WebSocket 吗? 支持。Workers 上的 WebSocket API 已稳定,支持双向实时消息。移动端客户端在 iOS 上使用 URLSessionWebSocketTask,在 Android 上使用 OkHttp WebSocket。参见 WebSocket 文档。
D1 能处理多个移动端客户端的并发写入吗? 能,但有注意事项。D1 使用 SQLite 的 WAL 模式,按数据库序列化写入。对于高写入工作负载(持续 >1k 次写入/秒),你需要跨多个 D1 数据库分片或迁移到不同的存储层。对于大多数移动端后端,远低于 1k 次写入/秒,D1 完全够用。
推送通知可靠性如何? APNs 和 FCM 均保证至少一次投递,但不保证最多一次。重复推送会发生;移动端客户端必须根据载荷中的 notificationId 字段做去重。上面 Durable Object 模式通过跟踪每个设备最后投递的 notificationId 在服务端做去重。
如何处理移动端 App 的令牌刷新? 签发一个短效(15 分钟)访问 JWT 和一个长期(30 天)刷新令牌。将刷新令牌存储在 iOS Keychain 或 Android EncryptedSharedPreferences 中。刷新端点根据 D1 会话表验证刷新令牌,轮换它,并签发新的访问 JWT。标准 OAuth 2.0 模式,干净地适配 Worker。
Cloudflare 出现故障时会发生什么? Cloudflare 状态页面显示 Workers 历史可用性超过 99.99%。对于关键任务移动端 App,答案是多区域兜底:将 API 复制到 AWS Lambda 或 Fly.io,并通过 DNS 健康检查路由。我目前还没有为任何移动端 App 实现这一点,因为成本收益尚未倾向于此。
使用 Workers 需要 TanStack Start 吗,还是可以用纯 Workers? 纯 Workers(fetch 处理器 API)适合任何后端。TanStack Start 是路由、服务器函数和端到端类型的约定框架。如果你团队偏好 Hono 或 itty-router,效果同样好。参见 Workers 文档 了解最小配置。
如何在边缘计算层做限流? 每个 Worker 路由通过一个滑动窗口计数器在 Durable Object 中强制执行 per-IP 和 per-user 限流。限制每 60 秒重置一次。默认限制是认证端点每用户 100 请求/分钟,未认证端点每 IP 30 请求/分钟。超出限制后 Worker 返回 429 并带 Retry-After 请求头。无需外部 API 网关。
移动端 API 的分析和可观测性如何做? Workers Analytics Engine(2026 年仍处于 Beta)是低成本方案:每请求调用一次 writeDataPoint(),每百万数据点摄入 $0.05。深度追踪方面,Cloudflare Logs 可流式传输至 R2,在开发期间用 wranger tail 解析,或在故障期间接入第三方 APM。避免记录大型请求/响应体——如果缓冲到内存中,每个日志字节都计入 Workers 打包体积限制。
移动端后端能连接到 VPN 后面的内部服务吗? Workers 无法直接访问私有 VPC。对于需要调用 AWS 或 GCP 上内部 API 的移动端后端,标准模式是通过公共互联网发起出站 HTTP 调用并使用 mTLS 认证。Cloudflare 的服务绑定允许一个 Worker 直接调用另一个 Worker 而无需经过公共互联网,这对将移动端后端拆分为多个 Workers 很有用。
如果我不再适合用 Workers,迁移路径是什么? 我见过的两条可行生产路径:(1) 将重型状态从 Durable Objects 迁移到托管 Postgres(Neon、Supabase),保留 Workers 作为 API 层;(2) 当你需要长时间 CPU 计算或 >128MB 内存时,将整个技术栈迁移到容器化运行时(Fly.io、Railway)。Workers 上 TanStack Start 代码库干净地适配这两个目标,因为 Server Functions 模式是可移植的——仅绑定声明需要更改。
结语
基于 Cloudflare Workers 的移动端后端是真实可用的生产级方案,成本极低。架构是四个绑定——D1、R2、KV、Durable Objects——每个路由组对应一个 Worker。冷启动 p99 50ms。日均 110 万请求时月费 $5.27。七个月单人独立开发,从未开过 pager。
如果你本季度要启动一个移动端 SaaS 且你的后端适配请求/响应或 WebSocket 模式,克隆 TanStack Ship,不到一小时就能让移动端认证端点跑起来。四个绑定和部署流水线已经接好。