一、为什么选择 Cloudflare Workers

在 Serverless 架构百花齐放的今天,Cloudflare Workers 凭借其独特的优势脱颖而出。不同于 AWS Lambda 的冷启动延迟(通常在 200ms-2s),Workers 基于 V8 Isolate 技术,冷启动时间低于 5ms,几乎可以忽略不计。

1.1 核心优势

  • 全球 300+ 节点:用户请求自动路由到最近节点,延迟极低
  • 免费额度慷慨:每天 10 万次请求,个人项目完全够用
  • 生态完善:KV、D1、R2、Durable Objects、Queues 等存储方案一应俱全
  • 开发体验好:Wrangler CLI 一键部署,支持本地调试

1.2 适用场景

Workers 并非银弹,以下场景特别适合:

  • API 网关和 BFF 层
  • 静态网站的动态功能扩展(如评论系统、表单处理)
  • 反向代理和 A/B 测试
  • 定时任务(Cron Triggers)
  • Webhook 处理

不适合的场景:

  • 长时间运行的任务(CPU 时间限制 10ms-30s)
  • 大量计算密集型任务
  • 需要传统文件系统的应用

二、项目结构与配置最佳实践

2.1 目录结构

一个规范的 Workers 项目应该这样组织:

1
2
3
4
5
6
7
8
9
10
11
12
13
my-worker/
├── src/
│ ├── index.js # 入口文件
│ ├── router.js # 路由处理
│ ├── handlers/ # 请求处理器
│ │ ├── api.js
│ │ └── webhook.js
│ └── utils/ # 工具函数
│ ├── auth.js
│ └── response.js
├── wrangler.toml # 配置文件
├── package.json
└── .dev.vars # 本地环境变量(不提交)

2.2 wrangler.toml 配置详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
name = "my-worker"
main = "src/index.js"
compatibility_date = "2025-08-01"
account_id = "your-account-id"

# 自定义域名(比 workers.dev 更稳定,国内可访问)
routes = [
{ pattern = "api.example.com", custom_domain = true }
]

# KV 命名空间
[[kv_namespaces]]
binding = "MY_KV"
id = "your-kv-id"

# D1 数据库
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "your-d1-id"

# R2 存储桶
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"

关键配置说明

  • compatibility_date:决定 API 行为,建议使用最新日期
  • custom_domain = true:使用自定义域名而非 workers.dev,国内访问更稳定
  • 环境变量(Secret)不要写入配置文件,通过 wrangler secret put 设置

三、路由与请求处理

3.1 原生路由方案

Workers 没有内置路由,但可以用 URL API 轻松实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url)
const path = url.pathname

// 路由分发
if (path === '/api/health' && request.method === 'GET') {
return new Response(JSON.stringify({ ok: true }), {
headers: { 'Content-Type': 'application/json' }
})
}

if (path.startsWith('/api/users/') && request.method === 'GET') {
const userId = path.split('/')[3]
return handleGetUser(userId, env)
}

return new Response('Not Found', { status: 404 })
}
}

3.2 推荐:使用 itty-router

对于复杂项目,推荐使用 itty-router,一个仅 400 字节的路由库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Router } from 'itty-router'

const router = Router()

router.get('/api/health', () => ({ ok: true }))

router.get('/api/users/:id', ({ params }) => {
return { userId: params.id }
})

router.post('/api/users', async (request) => {
const body = await request.json()
// 处理用户创建
return { success: true }
})

// 404 兜底
router.all('*', () => new Response('Not Found', { status: 404 }))

export default {
fetch: router.handle
}

四、存储方案选择

4.1 KV:键值存储

KV 适合读多写少的场景,如配置缓存、会话存储:

1
2
3
4
5
6
7
8
9
10
// 写入(带过期时间)
await env.MY_KV.put('user:123', JSON.stringify(userData), {
expirationTtl: 3600 // 1小时后过期
})

// 读取
const data = await env.MY_KV.get('user:123', 'json')

// 删除
await env.MY_KV.delete('user:123')

注意事项

  • KV 是最终一致性,写入后可能有几秒延迟才能在全球节点读到
  • 不适合频繁写入的场景(每秒最多 1 次写入 per key)
  • Value 最大 25MB

4.2 D1:SQLite 数据库

D1 是 Cloudflare 的原生 SQLite 数据库,适合需要复杂查询的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 查询
const { results } = await env.DB.prepare(
'SELECT id, name, email FROM users WHERE id = ?'
).bind(userId).all()

// 插入
const result = await env.DB.prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
).bind(name, email).run()

console.log('插入ID:', result.meta.last_row_id)

// 事务
await env.DB.batch([
env.DB.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?').bind(100, fromId),
env.DB.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?').bind(100, toId)
])

D1 最佳实践

  • 使用参数化查询,避免 SQL 注入
  • 批量操作用 batch(),减少网络往返
  • 大表加索引,查询性能提升明显
  • 定期备份(wrangler d1 export

4.3 R2:对象存储

R2 是 S3 兼容的对象存储,零出口流量费:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 上传
await env.BUCKET.put('avatars/user-123.jpg', imageBuffer, {
httpMetadata: { contentType: 'image/jpeg' }
})

// 读取
const object = await env.BUCKET.get('avatars/user-123.jpg')
if (object) {
return new Response(object.body, {
headers: { 'Content-Type': object.httpMetadata.contentType }
})
}

// 删除
await env.BUCKET.delete('avatars/user-123.jpg')

五、性能优化技巧

5.1 减少 CPU 时间

Workers 有 CPU 时间限制(免费计划每次请求 10ms),需要注意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ❌ 低效:每次请求都做复杂计算
export default {
async fetch(request) {
const result = heavyComputation() // 耗时5ms
return new Response(result)
}
}

// ✅ 高效:缓存计算结果到全局变量
let cachedResult = null
export default {
async fetch(request) {
if (!cachedResult) {
cachedResult = heavyComputation()
}
return new Response(cachedResult)
}
}

5.2 并行 IO 操作

多个独立的 IO 操作应该并行执行:

1
2
3
4
5
6
7
8
9
10
11
// ❌ 串行:总耗时 = t1 + t2 + t3
const user = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(id).first()
const posts = await env.DB.prepare('SELECT * FROM posts WHERE user_id = ?').bind(id).all()
const comments = await env.DB.prepare('SELECT * FROM comments WHERE user_id = ?').bind(id).all()

// ✅ 并行:总耗时 = max(t1, t2, t3)
const [user, posts, comments] = await Promise.all([
env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(id).first(),
env.DB.prepare('SELECT * FROM posts WHERE user_id = ?').bind(id).all(),
env.DB.prepare('SELECT * FROM comments WHERE user_id = ?').bind(id).all()
])

5.3 响应缓存

对于不常变化的内容,使用 Cache API 缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default {
async fetch(request, env, ctx) {
const cache = caches.default
const cachedResponse = await cache.match(request)

if (cachedResponse) {
return cachedResponse
}

const response = await generateResponse(request)
// 缓存1小时
response.headers.set('Cache-Control', 's-maxage=3600')
ctx.waitUntil(cache.put(request, response.clone()))
return response
}
}

六、安全实践

6.1 CORS 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const corsHeaders = {
'Access-Control-Allow-Origin': 'https://your-domain.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
}

export default {
async fetch(request) {
if (request.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders })
}

const response = await handleRequest(request)
Object.entries(corsHeaders).forEach(([k, v]) => {
response.headers.set(k, v)
})
return response
}
}

6.2 请求频率限制

简单的 IP 限流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const RATE_LIMIT = 100  // 每分钟最多100次
const WINDOW = 60 // 时间窗口(秒)

export default {
async fetch(request, env) {
const ip = request.headers.get('CF-Connecting-IP')
const key = `rate:${ip}`
const count = parseInt(await env.RATE_KV.get(key)) || 0

if (count >= RATE_LIMIT) {
return new Response('Too Many Requests', { status: 429 })
}

await env.RATE_KV.put(key, String(count + 1), { expirationTtl: WINDOW })
return handleRequest(request)
}
}

七、调试与排错

7.1 本地开发

1
2
3
4
5
6
7
8
# 启动本地开发服务器
wrangler dev

# 远程开发(连接真实资源)
wrangler dev --remote

# 查看实时日志
wrangler tail

7.2 常见问题排查

问题 可能原因 解决方案
1015 错误 CPU 超时 优化计算逻辑,减少同步操作
1042 错误 内存超限 检查是否有内存泄漏,减少大对象
500 错误 未捕获异常 添加 try-catch,检查环境变量
KV 读取不到 最终一致性 写入后等待几秒,或使用 metadata
D1 查询慢 缺少索引 对常用查询字段加索引

八、部署与 CI/CD

8.1 手动部署

1
2
3
4
5
# 部署到生产环境
wrangler deploy

# 部署到预览环境
wrangler deploy --env staging

8.2 GitHub Actions 自动部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name: Deploy Worker
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

九、成本估算

资源 免费额度 超出价格
请求次数 10万/天 $0.50/百万次
CPU 时间 10ms/请求 $0.02/GB-s
KV 存储 1GB $0.50/GB/月
KV 读取 10万/天 $0.50/百万次
D1 存储 5GB $0.75/GB/月
D1 读取 500万行/天 $0.001/百万行
R2 存储 10GB $0.015/GB/月
R2 操作 100万/月 $0.004/万次

对于个人项目,免费额度基本够用。即使超出,成本也很低。

十、总结

Cloudflare Workers 是一个强大的边缘计算平台,特别适合个人开发者和小型团队。通过本文介绍的最佳实践,你可以构建出高性能、低成本的 Serverless 应用。

核心要点回顾:

  1. 合理选择存储方案(KV/D1/R2)
  2. 优化 CPU 时间和 IO 操作
  3. 重视安全(CORS、限流、验证)
  4. 善用缓存提升性能
  5. 建立完善的 CI/CD 流程

希望这篇指南能帮助你更好地使用 Cloudflare Workers。如有问题,欢迎在评论区交流。

Kang0234の小破站
正在加载二次元世界…