渡劫code 的整个后端跑在 Cloudflare 边缘网络上,没有一台传统服务器。这篇文章把 Workers / KV / D1 / R2 / Durable Objects 五种原语的分工讲清楚:它们各自解决什么问题、彼此如何协作,以及我们在真实项目里的绑定方式。选型结论先行——无状态走 Worker,配置与限流走 KV,关系数据走 D1,大文件走 R2,需要单点协调的有状态逻辑走 DO。
为什么是全边缘架构
个人项目最贵的成本不是机器,是运维时间。传统 VPS 方案要操心系统更新、备份、证书续期、数据库主从,还要防着国内访问绕路。Cloudflare 的方案把这一切换成了「绑定即用」:请求在离用户最近的 PoP 被执行,存储与计算同厂原生打通,免费额度足够单人项目跑到上市。国内可达性则由 三端同步架构 兜底——静态镜像走 Gitee Pages,边缘 API 由 Workers 直服,不做地域跳转。
五种原语各司其职
| 原语 | 数据模型 | 一致性 | 渡劫code 中的用途 |
|---|---|---|---|
| Workers | 无状态函数 | — | 全部 API 入口(Hono 路由)、鉴权中间件 |
| KV | Key-Value | 最终一致(约 60s 全球生效) | 限流计数、特性开关、IndexNow key |
| D1 | SQLite 关系库 | 强一致(单库单写点) | 评论、点赞、浏览、账号数据 |
| R2 | 对象存储 | 强一致 | 用户上传的文件与项目快照 |
| Durable Objects | 单例有状态对象 | 强一致(串行) | WebSocket 房间、Shell 会话 |
wrangler.toml:一次声明所有绑定
所有原语通过 wrangler.toml 声明式绑定,Worker 代码里通过 env 访问,不需要任何 SDK 客户端初始化:
name = "dujiecone"
main = "worker/index.ts"
compatibility_date = "2026-10-01"
[[kv_namespaces]]
binding = "CACHE"
id = "<kv_namespace_id>"
[[d1_databases]]
binding = "BLOG_DB"
database_name = "blog"
database_id = "<d1_database_id>"
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "dujiecode-assets"
[[durable_objects.bindings]]
name = "SHELL_ROOM"
class_name = "ShellRoom"
[[migrations]]
tag = "v1"
new_classes = ["ShellRoom"]
DO 的 class 必须出现在
[[migrations]]里才会被实例化,这是最容易踩的一步——漏配的表现为运行时No such class。
Workers:无状态入口层
Worker 本身不存任何数据,只做路由、鉴权、编排。我们用 Hono 组织路由,统一信封 {code: 0, data} 返回(契约细节见评论系统实战):
import { Hono } from 'hono';
const app = new Hono<{ Bindings: Env }>();
app.get('/api/blog/stats', async (c) => {
const slugs = (c.req.query('slugs') || '').split(',').filter(Boolean);
const db = c.env.BLOG_DB;
// D1 prepared statement + batch,单次往返
const stmts = slugs.map((s) =>
db.prepare('SELECT slug, views, likes FROM stats WHERE slug = ?').bind(s));
const rows = await db.batch(stmts);
return c.json({ code: 0, data: rows.map((r) => r.results).flat() });
});
KV:最终一致的配置与限流桶
KV 的定位是读多写少、可容忍秒级延迟的数据。限流计数器是典型场景:评论接口按访客指纹计数,expirationTtl 到点自动清零,不用自己写过期逻辑:
async function rateLimit(env: Env, key: string, limit = 2, windowSec = 60) {
const k = `rl:${key}`;
const used = Number((await env.CACHE.get(k)) || '0');
if (used >= limit) return false;
await env.CACHE.put(k, String(used + 1), { expirationTtl: windowSec });
return true;
}
注意 KV 的最终一致性:写入后最长约 60 秒才全球生效,且同一 key 的并发写可能互相覆盖。所以精确计数(浏览量、点赞数)必须放 D1,KV 只做「粗略限流」这类丢几次也无所谓的场景。
D1:关系型数据的家
凡是需要事务、唯一约束、复杂查询的数据都进 D1。评论系统就是活样本:UNIQUE(slug, visitor) 保证 UV 级浏览统计不重复,UNIQUE(visitor, comment_id) 保证一人一赞,楼中楼靠 parent_id 自关联。写入走 batch() 把多条语句合成一次往返,且任一条失败整体回滚——相当于隐式事务。更完整的 Schema 与坑位见 D1 边缘 SQLite 实战。
R2:S3 兼容的对象存储
R2 对个人项目最大的吸引力是零出站流量费,且 API 与 S3 兼容,老代码改个 endpoint 就能迁。在 Worker 里直接走绑定,连签名都不用:
// 上传:POST /api/files body: multipart/form-data
app.post('/api/files', async (c) => {
const key = `uploads/${crypto.randomUUID()}`;
await c.env.ASSETS.put(key, c.req.body(), {
httpMetadata: { contentType: c.req.header('content-type') || 'application/octet-stream' },
});
return c.json({ code: 0, data: { key } }, 201);
});
Durable Objects:有状态协调者
DO 是 Cloudflare 最被低估的原语:每个对象是全局唯一的单例,拥有自己的存储,请求被串行化执行,天然就是分布式锁。渡劫code 的实时通道——WebSocket 房间与 Shell 会话——全部由 DO 承载,每个房间一个对象实例:
export class ShellRoom {
private sessions: Map<WebSocket, string> = new Map();
async fetch(req: Request) {
const [client, server] = Object.values(new WebSocketPair());
this.sessions.set(server, crypto.randomUUID());
server.accept();
return new Response(null, { status: 101, webSocket: client });
}
}
配合 hibernation API,空闲房间不烧 CPU 时间;连接恢复时从 storage 重建状态。这套设计的完整取舍见 AI 编程工作台架构。
部署与调试:wrangler 日常
本地开发用 wrangler dev,它会在本地模拟全部绑定:KV 落到本地存储、D1 起本地 SQLite、DO 起本地实例,断网也能写代码。怀疑绑定在边缘上的行为不一致时,加 --remote 直连真实环境复现;线上排错靠 wrangler tail 实时看日志,用 --status error 只保留错误:
npx wrangler dev # 本地全绑定模拟
npx wrangler dev --remote # 本地代码 + 真实边缘绑定
npx wrangler tail --status error # 实时错误日志
npx wrangler deploy # 发布(生产由 Actions 执行)
发布一律走 GitHub Actions 而非本地 wrangler deploy,保证每次上线都有 commit、日志与回滚点可回溯——部署纪律见三端同步进阶。另外 compatibility_date 不要频繁改动:它决定运行时行为版本,升级前先在预览部署里跑一遍测试。
决策速查表
| 问题 | 答案 |
|---|---|
| 用户会话放哪? | 短周期 JWT + DO/KV 存储,不放 D1 |
| 浏览量精确统计? | D1 UPSERT,KV 会丢写 |
| 接口限流? | KV 计数器 + expirationTtl |
| 大文件上传? | R2,Worker 只做预签名/中转 |
| 多人实时协作? | DO WebSocket,单房间单实例 |
小结
全边缘架构的本质是把「状态」按一致性和延迟要求分层:越强一致、越靠近数据库的层越贵也越该少用。Workers 负责编排,KV/D1/R2 各管一段,DO 收拾所有「必须只有一个」的场景。五件套用对了,个人项目也能拥有生产级的全球架构——而且月账单是 0。