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
// 总延迟:30ms

4.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 : null

Promise.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' },
  })
})

优化点:

  1. 用户信息和配额并行查
  2. 流式返回——用户看到「打字机效果」,首字延迟低
  3. 使用量异步记录——不阻塞响应

9. 小结

边缘部署性能优化核心:

  • 物理距离短:边缘节点靠近用户,延迟低
  • 资源受限:CPU 时间、内存有限,不适合 CPU 密集任务
  • 流式处理:大文件、大响应用流式,避免内存爆炸
  • 并行 I/OPromise.all 同时发多个请求,减少延迟
  • 内存管理:避免大对象、及时释放引用、复用 buffer
  • 任务分级:轻量任务边缘处理,重任务放到队列或专门服务

边缘 Worker 的定位:「低延迟的轻量计算 + 交通指挥」。重活交给专门的服务,边缘只做路由、鉴权、缓存、限流。

下一篇是本系列最后一篇——Hono 性能调优实践,把前面所有知识点整合起来。