18.14-边缘部署性能优化
要点
- 边缘计算的核心优势:用户请求在最近的节点处理,物理距离短 = 延迟低
- Worker 的 CPU 时间有限制——免费 10ms,付费 30 秒,超了就被强制终止
- 流式处理是边缘友好的模式——不用把所有数据都加载到内存,边读边写
- 并行 I/O 是减少延迟的关键——
Promise.all同时发多个请求 - 内存管理要小心——Isolate 内存 128MB,大文件处理要用流不要全读
- 边缘不适合 CPU 密集型任务——图像处理、大规模排序等应该放到后端或任务队列
1. 边缘计算的特点
1.1 物理距离短
传统部署:用户在上海,服务器在美西,单次 round trip 200ms+。
边缘部署:用户在上海,请求落到东京或新加坡节点,单次 round trip 30-50ms。
传统:用户 → 美西服务器 → 返回
延迟:~200ms × 2 = 400ms
边缘:用户 → 上海节点(Cloudflare PoP)→ 返回
延迟:~30ms × 2 = 60ms
1.2 资源受限
边缘节点不是完整的服务器——每个 Isolate 资源有限:
- CPU 时间:免费 10ms,付费 30 秒
- 内存:128MB(可调到 256MB)
- 无本地文件系统(只有
/tmp临时目录,不保证持久) - 无 TCP Socket(只能用 HTTP/WebSocket)
这些限制意味着:边缘不适合 CPU 密集型任务(大规模图像处理、复杂排序),也不适合大内存任务(加载整个数据库到内存)。
1.3 无状态
Isolate 可以随时被回收、迁移。你的代码不能依赖本地状态——所有持久化数据必须放在 KV、D1、R2 等外部存储。
2. CPU 时间优化
2.1 付费计划的 CPU 时间
- 免费计划:10ms CPU 时间
- 付费计划($5/月):30 秒 CPU 时间
10ms 听起来很少——但大部分 Web 请求都是 I/O 密集型(查数据库、调 API),CPU 时间消耗在等待上,实际计算很少。
2.2 监控 CPU 时间
Worker 响应头里有 cf-worker-status,能看到 CPU 时间消耗:
export default {
async fetch(request: Request, env: Env) {
const start = Date.now()
const response = await handleRequest(request, env)
const duration = Date.now() - start
// 加到响应头,便于调试
response.headers.set('X-Worker-Duration', String(duration))
return response
},
}如果 duration 接近 10ms,免费计划可能要升级到付费——否则会被强制终止。
2.3 避免 CPU 密集操作
以下操作要避免(或放到任务队列):
- 大规模 JSON 序列化/反序列化(> 1MB)
- 图片处理(压缩、裁剪)
- 视频处理
- 复杂排序(> 10000 条记录)
- 加密/解密大文件
替代方案:
- JSON 处理:用流式解析器(
stream-json) - 图片处理:用 Cloudflare Images 或外部 API
- 大文件:用 R2 + 流式处理
3. 流式处理
3.1 场景
处理大文件(10MB+)不能全读进内存——Isolate 内存只有 128MB,同时处理 10 个请求就 OOM 了。
3.2 流式读取
// 从 R2 读大文件,流式处理
app.get('/api/files/:id/process', async (c) => {
const file = await c.env.BUCKET.get(c.req.param('id'))
if (!file) return c.notFound()
// 流式读取,不全读进内存
const reader = file.body.getReader()
const decoder = new TextDecoder()
let processed = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
// 处理这一块数据
const text = decoder.decode(value)
processed += text.length
// 释放内存
// value 是 Uint8Array,处理完就丢弃
}
return c.json({ processed })
})3.3 流式写入
// 流式返回大响应
app.get('/api/export', async (c) => {
const stream = new ReadableStream({
async pull(controller) {
const encoder = new TextEncoder()
// 一块一块生成数据
for (let i = 0; i < 10000; i++) {
const chunk = await loadChunk(i)
controller.enqueue(encoder.encode(JSON.stringify(chunk) + '\n'))
}
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'application/x-ndjson',
'Transfer-Encoding': 'chunked',
},
})
})流式处理的好处:内存占用恒定(只存当前块),不受文件大小影响。
4. 并行 I/O
4.1 场景
一个请求要查多个数据源:用户信息 + 配额 + 最近的订单。串行执行:
// 不好:串行,延迟累加
const user = await db.getUser(userId) // 10ms
const quota = await db.getQuota(userId) // 10ms
const orders = await db.getOrders(userId) // 10ms
// 总延迟:30ms4.2 Promise.all
// 好:并行,延迟取最大值
const [user, quota, orders] = await Promise.all([
db.getUser(userId), // 10ms
db.getQuota(userId), // 10ms
db.getOrders(userId), // 10ms
])
// 总延迟:10ms(三者同时开始,一起结束)4.3 带错误处理的并行
// 部分失败不影响其他
const results = await Promise.allSettled([
db.getUser(userId),
cache.get(`user:${userId}`),
externalAPI.getProfile(userId),
])
const user = results[0].status === 'fulfilled' ? results[0].value : null
const cached = results[1].status === 'fulfilled' ? results[1].value : null
const profile = results[2].status === 'fulfilled' ? results[2].value : nullPromise.allSettled 不会因为一个失败就抛错——适合「部分失败可容忍」的场景。
4.4 依赖关系的并行
有些操作有依赖关系,要分清哪些可以并行:
// 有依赖:必须先查用户,再查订单
const user = await db.getUser(userId) // 必须先跑
const orders = await db.getOrders(user.id) // 依赖 user.id
// 可并行:用户信息和配额可以独立查
const [user, quota] = await Promise.all([
db.getUser(userId),
db.getQuota(userId),
])
// 然后:用 user.id 查订单
const orders = await db.getOrders(user.id)5. 内存管理
5.1 避免大对象
// 不好:加载整个数据库到内存
const allUsers = await db.prepare('SELECT * FROM users').all()
// 好:分页查询
const users = await db.prepare('SELECT * FROM users LIMIT ? OFFSET ?')
.bind(100, offset)
.all()5.2 及时释放引用
async function processRequest(request: Request) {
const buffer = await request.arrayBuffer() // 可能很大
const result = await process(buffer)
// buffer 不再需要,但 JavaScript 是 GC 的
// 显式设为 null 帮助 GC
// (实际 JavaScript 引擎会自动处理,但长函数里显式释放更安全)
return result
}5.3 TypedArray 复用
频繁创建大 TypedArray 会造成 GC 压力。可以复用 buffer:
// 不好:每次都创建新 buffer
function processChunk(data: Uint8Array) {
const temp = new Uint8Array(data.length) // 每次分配
// ...
}
// 好:复用 buffer
const tempBuffer = new Uint8Array(1024 * 1024) // 1MB,模块级
function processChunk(data: Uint8Array) {
// 复用 tempBuffer,避免频繁分配
const view = tempBuffer.subarray(0, data.length)
view.set(data)
// ...
}6. 边缘不适合的任务
以下任务应该避免在 Worker 里直接跑:
| 任务 | 原因 | 替代方案 |
|---|---|---|
| 图片处理 | CPU 密集 | Cloudflare Images |
| 视频处理 | CPU + 内存密集 | Cloudflare Stream |
| 大文件转换 | 内存密集 | R2 + 任务队列 |
| 复杂 ML 推理 | CPU 密集 | AI Gateway 或外部 API |
| 大规模数据分析 | CPU + 内存密集 | D1 Analytics 或外部 |
边缘的优势是「低延迟的轻量计算」——重活应该交给专门的服务。
7. 边缘 + 后端的架构
典型的 AI 应用架构:
用户请求 → 边缘 Worker(路由、鉴权、缓存、限流)
│
├── 轻量任务:直接处理(查 KV、查 D1)
│
├── 重任务:发到任务队列(Cloudflare Queue)
│ └── 后台 Worker 慢慢处理
│
└── 特殊任务:转发到专门服务(图片处理、ML 推理)
边缘 Worker 做「交通指挥」——把合适的任务分给合适的处理者。
8. 实战:优化一个 AI 查询接口
原始版本(串行、阻塞):
app.post('/api/ai/query', async (c) => {
// 串行:查用户 → 查配额 → 查缓存 → 调 LLM → 记录使用量
const user = await db.getUser(c.get('userId'))
const quota = await db.getQuota(c.get('userId'))
const cached = await cache.get(prompt)
if (cached) return c.json({ answer: cached, source: 'cache' })
const result = await callLLM(env, prompt)
await recordUsage(c.get('userId'), result.usage)
return c.json({ answer: result.response })
})优化后(并行、流式):
app.post('/api/ai/query', async (c) => {
// 并行:用户信息和配额同时查
const [user, quota] = await Promise.all([
db.getUser(c.get('userId')),
db.getQuota(c.get('userId')),
])
// 检查配额
if (quota.used >= quota.limit) {
return c.json({ error: 'Quota exceeded' }, 429)
}
// 查缓存(如果有)
const cached = await cache.get(prompt)
if (cached) return c.json({ answer: cached, source: 'cache' })
// 流式返回
const stream = await callLLMStream(env, prompt)
// 异步记录使用量(不阻塞响应)
c.executionCtx.waitUntil(
stream.tee()[1].pipeTo(recordUsageStream(c.get('userId')))
)
return new Response(stream.tee()[0], {
headers: { 'Content-Type': 'text/event-stream' },
})
})优化点:
- 用户信息和配额并行查
- 流式返回——用户看到「打字机效果」,首字延迟低
- 使用量异步记录——不阻塞响应
9. 小结
边缘部署性能优化核心:
- 物理距离短:边缘节点靠近用户,延迟低
- 资源受限:CPU 时间、内存有限,不适合 CPU 密集任务
- 流式处理:大文件、大响应用流式,避免内存爆炸
- 并行 I/O:
Promise.all同时发多个请求,减少延迟 - 内存管理:避免大对象、及时释放引用、复用 buffer
- 任务分级:轻量任务边缘处理,重任务放到队列或专门服务
边缘 Worker 的定位:「低延迟的轻量计算 + 交通指挥」。重活交给专门的服务,边缘只做路由、鉴权、缓存、限流。
下一篇是本系列最后一篇——Hono 性能调优实践,把前面所有知识点整合起来。