全链路性能监控体系

优化做了,但效果好不好?哪些页面需要优化?线上真实用户体验如何?没有监控,优化就是盲人摸象。本章构建一套从浏览器到服务端到数据库的全链路性能监控体系,让每一次请求的耗时都可追踪、可归因、可告警。

1. 监控分层架构

1.1 三层监控模型

┌─────────────────────────────────────────────────┐
│  前端层:Web Vitals + 自定义埋点 + 错误监控      │
│  (用户真实体验 = RUM,Real User Monitoring)     │
├─────────────────────────────────────────────────┤
│  服务端层:API 耗时 + SSR 渲染时间 + 链路追踪     │
│  (OpenTelemetry + 结构化日志)                   │
├─────────────────────────────────────────────────┤
│  基础设施层:数据库查询 + 缓存命中率 + CPU/内存    │
│  (数据库监控 + 资源监控)                        │
└─────────────────────────────────────────────────┘

每一层回答不同的问题:

  • 前端层:用户体验好不好?哪些页面慢?
  • 服务端层:哪个 API 慢?慢在哪个环节?
  • 基础设施层:是数据库慢还是缓存没命中?服务器资源够不够?

1.2 关键指标体系

层级指标目标值告警阈值
前端LCP< 2.5s> 4s
前端CLS< 0.1> 0.25
前端INP< 200ms> 500ms
前端JS 错误率< 0.1%> 1%
服务端API P95< 200ms> 1s
服务端SSR TTFB P95< 500ms> 2s
服务端错误率< 0.1%> 1%
基础设施DB 查询 P95< 50ms> 200ms
基础设施缓存命中率> 90%< 70%

2. 前端 Web Vitals 自动上报

2.1 web-vitals 库集成

// plugins/web-vitals.client.ts
export default defineNuxtPlugin(() => {
  onNuxtReady(async () => {
    const { onLCP, onCLS, onINP, onFCP, onTTFB } = await import('web-vitals')
 
    function reportMetric(metric: any) {
      const body = {
        name: metric.name,
        value: metric.value,
        rating: metric.rating,       // 'good' | 'needs-improvement' | 'poor'
        delta: metric.delta,
        id: metric.id,
        navigationType: metric.navigationType,
        url: window.location.href,
        userAgent: navigator.userAgent,
        timestamp: Date.now(),
      }
 
      // 使用 sendBeacon 确保页面关闭前发送
      if (navigator.sendBeacon) {
        navigator.sendBeacon('/api/analytics/vitals', JSON.stringify(body))
      } else {
        fetch('/api/analytics/vitals', {
          method: 'POST',
          body: JSON.stringify(body),
          keepalive: true,
        })
      }
    }
 
    onLCP(reportMetric)
    onCLS(reportMetric)
    onINP(reportMetric)
    onFCP(reportMetric)
    onTTFB(reportMetric)
  })
})

2.2 自定义性能指标

除了 Web Vitals 标准指标,还需要业务相关的性能指标:

// app/composables/usePerformanceMark.ts
export function usePerformanceMark(name: string) {
  const startTime = ref(0)
 
  function start() {
    startTime.value = performance.now()
    performance.mark(`${name}:start`)
  }
 
  function end() {
    performance.mark(`${name}:end`)
    const measure = performance.measure(name, `${name}:start`, `${name}:end`)
 
    // 上报自定义指标
    reportCustomMetric(name, measure.duration)
  }
 
  return { start, end }
}
 
// 使用示例
const { start, end } = usePerformanceMark('video-player-init')
start()
await initPlayer(container, options)
end()  // 上报播放器初始化耗时

2.3 前端错误监控

// plugins/error-monitor.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Vue 组件渲染错误
  nuxtApp.hook('vue:error', (error, instance, info) => {
    reportError({
      type: 'vue-error',
      message: error instanceof Error ? error.message : String(error),
      stack: error instanceof Error ? error.stack : undefined,
      componentName: instance?.$options?.name,
      info,
      url: window.location.href,
    })
  })
 
  // 未捕获的 Promise 拒绝
  window.addEventListener('unhandledrejection', (event) => {
    reportError({
      type: 'unhandled-rejection',
      message: event.reason?.message || String(event.reason),
      stack: event.reason?.stack,
      url: window.location.href,
    })
  })
 
  // 资源加载失败(图片、脚本、样式)
  window.addEventListener('error', (event) => {
    if (event.target instanceof HTMLElement) {
      reportError({
        type: 'resource-error',
        tagName: event.target.tagName,
        src: (event.target as any).src || (event.target as any).href,
        url: window.location.href,
      })
    }
  }, true)
})

3. 服务端性能监控

3.1 API 耗时中间件

// server/middleware/performance.ts
export default defineEventHandler((event) => {
  const start = performance.now()
  const method = event.method
  const path = event.path
 
  // 响应完成后记录
  event.node.res.on('finish', () => {
    const duration = performance.now() - start
    const statusCode = event.node.res.statusCode
 
    // 结构化日志
    console.log(JSON.stringify({
      type: 'api-performance',
      method,
      path,
      statusCode,
      duration: Math.round(duration),
      timestamp: new Date().toISOString(),
    }))
 
    // 慢请求告警
    if (duration > 1000) {
      console.warn(`[SLOW API] ${method} ${path} took ${Math.round(duration)}ms`)
    }
  })
})

3.2 SSR 渲染时间监控

// plugins/ssr-timing.server.ts
export default defineNuxtPlugin((nuxtApp) => {
  const start = performance.now()
 
  nuxtApp.hook('app:rendered', () => {
    const duration = performance.now() - start
    const event = useRequestEvent()
 
    // 添加 Server-Timing 头(可在 DevTools 的 Network 面板看到)
    if (event) {
      setResponseHeader(event, 'Server-Timing', `ssr;dur=${Math.round(duration)}`)
    }
 
    console.log(JSON.stringify({
      type: 'ssr-performance',
      path: event?.path,
      duration: Math.round(duration),
      timestamp: new Date().toISOString(),
    }))
  })
})

Server-Timing 头会在浏览器 DevTools 的 Network 面板中显示,开发调试时非常方便。

3.3 OpenTelemetry 链路追踪

OpenTelemetry(OTel)是可观测性的行业标准,可以追踪一个请求从浏览器到服务端到数据库的完整链路:

// server/plugins/otel.ts
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
 
export default defineNitroPlugin(() => {
  const sdk = new NodeSDK({
    serviceName: 'ai-video-platform',
    traceExporter: new OTLPTraceExporter({
      url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
    }),
    instrumentations: [getNodeAutoInstrumentations()],
  })
 
  sdk.start()
})

OTel 的自动 instrumentation 会自动追踪:

  • HTTP 请求(入站和出站)
  • 数据库查询(支持 PostgreSQL、MySQL、Redis)
  • 外部 API 调用

一个完整的 Trace 看起来像:

[Trace: 视频详情页请求]
├── SSR 渲染 (450ms)
│   ├── useFetch /api/videos/123 (50ms)
│   │   └── DB: SELECT * FROM videos WHERE id=123 (8ms)
│   ├── useFetch /api/videos/123/comments (300ms)
│   │   └── DB: SELECT * FROM comments WHERE video_id=123 (250ms)  ← 慢查询!
│   └── Vue 渲染 (100ms)
└── 响应发送 (2ms)

一眼就能看出瓶颈在评论查询——需要加索引或缓存。

4. 数据库查询性能监控

4.1 Drizzle ORM 查询日志

// server/utils/db.ts
import { drizzle } from 'drizzle-orm/node-postgres'
 
export const db = drizzle(pool, {
  logger: {
    logQuery(query, params) {
      const start = performance.now()
      // Drizzle 的 logger 在查询前调用
      // 需要配合 OTel 或自定义 wrapper 获取耗时
      console.log(JSON.stringify({
        type: 'db-query',
        query: query.substring(0, 200),  // 截断,避免日志过长
        timestamp: new Date().toISOString(),
      }))
    },
  },
})

4.2 慢查询发现与优化

常见慢查询模式及解决方案:

模式症状解决
缺少索引SELECT 全表扫描CREATE INDEX 添加索引
N+1 查询列表页每个 item 一次查询改用 JOIN 或批量查询
大偏移分页OFFSET 10000 很慢改用游标分页(WHERE id > lastId
**SELECT ***返回不需要的列只 SELECT 需要的字段

4.3 缓存命中率监控

// server/utils/cache-monitor.ts
const stats = { hits: 0, misses: 0 }
 
export async function monitoredCache<T>(
  key: string,
  queryFn: () => Promise<T>,
  ttl = 60
): Promise<T> {
  const storage = useStorage('cache')
  const cached = await storage.getItem<T>(key)
 
  if (cached) {
    stats.hits++
    return cached
  }
 
  stats.misses++
  const result = await queryFn()
  await storage.setItem(key, result, { ttl })
 
  // 定期输出命中率(每 100 次请求)
  if ((stats.hits + stats.misses) % 100 === 0) {
    const hitRate = stats.hits / (stats.hits + stats.misses) * 100
    console.log(JSON.stringify({
      type: 'cache-stats',
      hits: stats.hits,
      misses: stats.misses,
      hitRate: `${hitRate.toFixed(1)}%`,
    }))
  }
 
  return result
}

5. AI 视频生成耗时追踪

5.1 异步任务监控

AI 视频生成是一个长耗时异步任务(几十秒到几分钟),需要专门的监控:

// server/api/ai/generate.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const taskId = generateId()
  const startTime = Date.now()
 
  // 记录任务开始
  console.log(JSON.stringify({
    type: 'ai-task-start',
    taskId,
    model: body.model,
    promptLength: body.prompt.length,
    timestamp: new Date().toISOString(),
  }))
 
  try {
    const result = await generateVideo(body)
 
    // 记录任务完成
    console.log(JSON.stringify({
      type: 'ai-task-complete',
      taskId,
      duration: Date.now() - startTime,
      model: body.model,
      outputSize: result.fileSize,
      timestamp: new Date().toISOString(),
    }))
 
    return result
  } catch (error) {
    // 记录任务失败
    console.error(JSON.stringify({
      type: 'ai-task-error',
      taskId,
      duration: Date.now() - startTime,
      model: body.model,
      error: error instanceof Error ? error.message : String(error),
      timestamp: new Date().toISOString(),
    }))
    throw error
  }
})

5.2 任务状态看板

汇总 AI 任务数据可以构建运营看板:

指标计算方式用途
平均生成时间AVG(duration)用户体验基线
P95 生成时间PERCENTILE(duration, 0.95)尾部延迟
成功率complete / (complete + error)服务稳定性
模型耗时分布GROUP BY model不同模型的性能对比
队列等待时间入队到开始执行的时间容量规划

6. 监控数据可视化

6.1 日志平台选择

平台特点适用
Grafana + Loki开源,日志 + 指标 + 追踪一体自建监控
Datadog全功能 SaaS,开箱即用预算充足的团队
Sentry专注错误监控 + 性能错误追踪
Vercel AnalyticsVercel 部署自带Vercel 用户
腾讯云 CLS / 阿里云 SLS国内云厂商方案国内部署

6.2 告警策略

级别触发条件通知方式
P0 紧急错误率 > 5% 或服务不可用电话 + 短信
P1 重要API P95 > 2s 或 Web Vitals Poor > 20%企业微信/钉钉
P2 一般缓存命中率 < 70% 或慢查询增多邮件
P3 信息日常性能波动看板展示

6.3 Server-Timing 开发调试

Server-Timing 头在开发时特别有用——无需打开日志,在 DevTools Network 面板就能看到服务端耗时拆解:

// server/middleware/server-timing.ts
export default defineEventHandler((event) => {
  const timings: string[] = []
  const start = performance.now()
 
  // 记录各阶段耗时
  event.context.timing = {
    mark(name: string) {
      timings.push(`${name};dur=${Math.round(performance.now() - start)}`)
    },
  }
 
  event.node.res.on('finish', () => {
    timings.push(`total;dur=${Math.round(performance.now() - start)}`)
    setResponseHeader(event, 'Server-Timing', timings.join(', '))
  })
})

在 DevTools 中看到的效果:

Server-Timing: db;dur=12, cache;dur=2, render;dur=45, total;dur=60

7. 监控最佳实践

7.1 采样策略

不需要采集 100% 的数据——对于高流量站点,采样可以大幅降低成本:

数据类型采样率原因
Web Vitals100%数据量小,全量采集
API 性能日志10-100%视流量而定
OTel Traces1-10%Trace 数据量大
错误日志100%每个错误都重要

7.2 日志规范

统一结构化日志格式,方便后续查询和分析:

// 统一的日志接口
interface PerformanceLog {
  type: 'api-performance' | 'ssr-performance' | 'db-query' | 'cache-stats' | 'ai-task-start' | 'ai-task-complete'
  timestamp: string
  duration?: number
  [key: string]: any
}

7.3 避免监控本身成为瓶颈

  • 前端上报用 sendBeacon(非阻塞)
  • 服务端日志异步写入(不 await
  • 采样率动态调整(流量高峰时降低)
  • 监控数据和业务数据分开存储

本章小结

  • 三层监控:前端(Web Vitals + 错误)→ 服务端(API + SSR + Trace)→ 基础设施(DB + 缓存)
  • Web Vitals 上报web-vitals 库 + sendBeacon + 自定义业务指标
  • API 监控:中间件记录耗时 + 结构化日志 + 慢请求告警
  • OpenTelemetry:自动 instrumentation 追踪 HTTP → DB 的完整链路,一眼定位瓶颈
  • Server-Timing:开发调试利器,DevTools 直接看服务端耗时拆解
  • AI 任务监控:taskId 串联开始/完成/失败,构建运营看板
  • 告警分级:P0 电话/P1 即时通讯/P2 邮件/P3 看板