Skip to content
返回质量保障

API 延迟优化

减少请求到响应时间,聚焦 p95 和 p99 非平均。

为什么

API 延迟是用户等待时间,且复合——一页 5 个 API 调用各 200ms 加载 1 秒,慢连接移动用户等更久。平均延迟误导因藏慢尾:平均 100ms p99 2s 意味 1% 用户等 20 倍久,那些是见超时和错误的用户。优化延迟是优化尾——p95 和 p99——因快请求已快,慢的是用户受苦处。每 100ms API 延迟降转化约 1%(Amazon 发现),故延迟直接是收入指标。

何时使用

用 APM 建延迟基线后。先优化最慢端点(帕累托——20% 端点致 80% 延迟)。查询或功能变更可能回退延迟时再优化。

如何做

1. 用 APM 测延迟按端点分解
   - Datadog、New Relic 或 OpenTelemetry + Jaeger
   - 每端点看 p50、p95、p99——非仅平均
   - 识别最慢端点和最慢百分位

2. profile 慢请求找时间花哪
   - 分布式追踪显 span 分解: DB、缓存、外部 API、计算
   - 是慢查询?缺索引?N+1?同步外部调用?
   - 先修主导 span——DB 500ms 时优化 10ms span 是浪费

3. 优化数据库(通常最大 span)
   - 为 WHERE、JOIN、ORDER BY 中列加索引
   - 消 N+1 查询: 用预加载(includes/join)或批量加载
   - 用连接池——每请求新连接加 50-100ms 握手
   - 读重端点考虑读副本

4. 为重复响应加缓存
   - 全响应缓存到 Redis/Memcached 带 TTL(如商品目录: 5 分钟)
   - 用 HTTP 缓存头(Cache-Control、ETag)供 CDN 级缓存
   - 应用级缓存贵计算(记忆化)
   - 写时失效——陈旧缓存是正确性 bug

5. 并行化独立操作
   - 端点调 3 个独立服务用 Promise.all 非顺序
   - 3 × 100ms 顺序 = 300ms;3 × 100ms 并行 = 100ms
   - 全程 async/await——永不阻塞事件循环做同步 I/O

示例

// 之前: N+1 查询、顺序外部调用——总 800ms
async function getOrderDetails(orderId: string) {
  const order = await db.query("SELECT * FROM orders WHERE id = $1", [orderId]); // 50ms
  const items = await db.query("SELECT * FROM order_items WHERE order_id = $1", [orderId]); // 50ms
  // N+1: 每商品一条查询取产品信息
  const products = await Promise.all(items.map(i =>
    db.query("SELECT * FROM products WHERE id = $1", [i.product_id]) // 50ms × N
  ));
  const shipping = await fetchShippingStatus(orderId); // 200ms 外部 API
  const tracking = await fetchTrackingInfo(orderId);   // 200ms 外部 API
  return { order, items, products, shipping, tracking };
}

// 之后: 批量查询、并行外部调用——总 250ms
async function getOrderDetails(orderId: string) {
  const [order, items, products] = await Promise.all([
    db.query("SELECT * FROM orders WHERE id = $1", [orderId]),           // 50ms
    db.query("SELECT * FROM order_items WHERE order_id = $1", [orderId]), // 50ms
    // 单查询带 JOIN 替代 N+1
    db.query(`
      SELECT p.* FROM products p
      JOIN order_items oi ON p.id = oi.product_id
      WHERE oi.order_id = $1
    `, [orderId]),                                                        // 50ms
  ]);
  // 并行替代顺序: 200ms 替代 400ms
  const [shipping, tracking] = await Promise.all([
    fetchShippingStatus(orderId),  // 200ms
    fetchTrackingInfo(orderId),    // 200ms(并行)
  ]);
  return { order, items, products, shipping, tracking };
}

常见问题

  • ⚠优化平均延迟宣布胜利而 p99 仍 5 秒——1% 用户仍见超时,那些是支持工单和弃购物车。
  • ⚠加缓存无失效策略——写后缓存服务陈旧数据,用户见错误信息,bug 间歇难复现。
  • ⚠并行化非真独立的调用(一个依赖另一个结果)——并行调用因输入未就绪失败,错误令人困惑。
  • ⚠加索引但不测改善——索引帮一查询但慢写,净延迟可能不变或更糟。

最佳实践

  • ✓生产中用 APM 每端点测 p95 和 p99 并设 SLO(如 p95 < 200ms)——尾是用户受苦处,平均藏它。
  • ✓用分布式追踪找主导 span(通常数据库)先优化——500ms 查询使 10ms 序列化优化相形见绌。
  • ✓用单 JOIN 或批量加载消 N+1 查询——这是最常见延迟 bug 且最易修,常减半延迟。
  • ✓用 Promise.all(或 asyncio.gather)并行化独立 I/O——对独立服务的顺序调用浪费并行化免费消的时间。