21.13-生产故障排查流程
要点
- 故障排查需要系统化的方法,而不是凭直觉乱猜
- 遵循「发现 → 定位 → 缓解 → 修复 → 复盘」的流程
- 善用日志、指标、trace 快速定位问题
- 故障后必须做复盘,避免同类问题再次发生
内容
1. 故障排查的五步法
生产故障排查遵循五个步骤:
| 步骤 | 目标 | 关键动作 |
|---|---|---|
| 1. 发现 | 知道出了问题 | 监控告警、用户反馈 |
| 2. 定位 | 找到问题根因 | 日志分析、指标查询、trace 追踪 |
| 3. 缓解 | 减少影响范围 | 回滚、降级、限流 |
| 4. 修复 | 彻底解决问题 | 修复 bug、修复配置 |
| 5. 复盘 | 防止再次发生 | 分析根因、改进流程 |
2. 发现故障
故障通常通过以下渠道发现:
- 监控告警:错误率升高、延迟增加、服务不可用
- 用户反馈:用户报告功能异常
- 自动检测:健康检查失败
收到告警后,第一步是确认故障的真实性:
// 1. 检查健康状态
const health = await fetch('https://your-worker.com/health')
const status = await health.json()
if (status.status === 'error') {
console.error('服务不可用:', status.checks)
}
// 2. 检查错误率
const errorRate = await env.ANALYTICS.sql(`
SELECT
SUM(CASE WHEN blob3 >= '500' THEN _sample_interval ELSE 0 END) * 100.0 /
SUM(_sample_interval) AS error_rate
FROM ai_gateway_metrics
WHERE blob1 = 'http.request'
AND timestamp > NOW() - INTERVAL '5' MINUTE
`)
console.log('当前错误率:', errorRate[0].error_rate)
// 3. 检查 P99 延迟
const p99 = await env.ANALYTICS.sql(`
SELECT APPROX_QUANTILE(0.99, double1) AS p99_ms
FROM ai_gateway_metrics
WHERE blob1 = 'llm.call.success'
AND timestamp > NOW() - INTERVAL '5' MINUTE
`)
console.log('P99 延迟:', p99[0].p99_ms, 'ms')3. 定位问题
3.1 从告警到日志
告警告诉你「什么指标异常」,日志告诉你「具体发生了什么」:
// 1. 从告警提取关键信息
const alertInfo = {
type: 'high_error_rate',
errorRate: '15%',
startTime: '2026-06-21T10:30:00Z',
}
// 2. 查询对应时间段的错误日志
const errorLogs = await env.LOGS.query(`
SELECT
timestamp,
level,
event,
requestId,
error.message,
error.stack
FROM logs
WHERE level = 'error'
AND timestamp > '${alertInfo.startTime}'
ORDER BY timestamp DESC
LIMIT 100
`)
// 3. 分析错误模式
const errorPattern = analyzeErrorPattern(errorLogs)
console.log('错误模式:', errorPattern)3.2 从日志到 Trace
日志告诉你「哪个请求出错」,trace 告诉你「请求在哪里卡住」:
// 1. 找到一个错误的 requestId
const errorRequestId = errorLogs.results[0].requestId
// 2. 查询该请求的完整 trace
const trace = await env.TRACES.query(`
SELECT
traceId,
spanId,
parentSpanId,
operationName,
startTime,
duration,
tags,
logs
FROM traces
WHERE requestId = '${errorRequestId}'
ORDER BY startTime
`)
// 3. 可视化 trace 链路
console.log('Trace 链路:')
for (const span of trace.results) {
const indent = ' '.repeat(span.depth)
console.log(`${indent}${span.operationName} (${span.duration}ms)`)
}3.3 从 Trace 到代码
trace 告诉你「哪个函数慢」,代码告诉你「为什么慢」:
// 1. 从 trace 找到最慢的 span
const slowSpan = trace.results.reduce((slowest, span) =>
span.duration > slowest.duration ? span : slowest
)
console.log('最慢的操作:', slowSpan.operationName)
console.log('耗时:', slowSpan.duration, 'ms')
// 2. 查看该 span 的日志
const spanLogs = await env.LOGS.query(`
SELECT *
FROM logs
WHERE requestId = '${errorRequestId}'
AND timestamp BETWEEN ${slowSpan.startTime} AND ${slowSpan.startTime + slowSpan.duration}
`)
console.log('相关日志:', spanLogs.results)
// 3. 查看代码(需要知道 operationName 对应的代码位置)
// 例如:operationName = 'llm.call.openai' 对应 src/lib/llm.ts:callOpenAI()4. 常见故障场景
4.1 LLM API 超时
症状:P99 延迟升高,用户报告响应很慢
排查步骤:
- 检查 LLM API 的延迟分布:
SELECT
model,
AVG(duration) as avg_ms,
APPROX_QUANTILE(0.99, duration) as p99_ms,
COUNT(*) as call_count
FROM ai_gateway_metrics
WHERE blob1 = 'llm.call.success'
AND timestamp > NOW() - INTERVAL '1' HOUR
GROUP BY model- 检查是否有特定模型异常:
const byModel = await env.ANALYTICS.sql(`
SELECT
model,
AVG(duration) as avg_ms,
COUNT(*) as call_count
FROM ai_gateway_metrics
WHERE blob1 = 'llm.call.success'
AND timestamp > NOW() - INTERVAL '1' HOUR
GROUP BY model
HAVING avg_ms > 10000
`)
if (byModel.results.length > 0) {
console.log('延迟异常的模型:', byModel.results)
}-
检查 OpenAI 状态页面:https://status.openai.com
-
如果确认是 OpenAI 问题,启用降级策略:
// src/lib/llm-fallback.ts
export async function callLLMWithFallback(
body: ChatRequest,
env: Env
): Promise<ChatResponse> {
try {
// 尝试主模型
return await callLLM(body, env)
} catch (err) {
if (err.status === 504 || err.status === 503) {
// 超时或服务不可用,降级到备用模型
console.warn('主模型不可用,降级到备用模型')
return await callLLM({
...body,
model: 'gpt-3.5-turbo', // 降级到便宜快速的模型
}, env)
}
throw err
}
}4.2 数据库连接失败
症状:所有接口返回 500,健康检查失败
排查步骤:
- 检查健康状态:
curl https://your-worker.com/health- 查看错误日志:
const dbErrors = await env.LOGS.query(`
SELECT error.message, error.stack
FROM logs
WHERE event = 'database.query'
AND level = 'error'
AND timestamp > NOW() - INTERVAL '5' MINUTE
LIMIT 10
`)
console.log('数据库错误:', dbErrors.results)-
检查 D1 状态:https://www.cloudflarestatus.com
-
如果是 D1 问题,启用缓存降级:
// src/lib/db-fallback.ts
export async function queryWithCache<T>(
env: Env,
query: string,
params: any[],
cacheKey: string,
ttl: number = 300
): Promise<T> {
try {
// 尝试查询数据库
return await env.DB.prepare(query).bind(...params).first()
} catch (err) {
console.error('数据库查询失败:', err.message)
// 降级到缓存
const cached = await env.CACHE.get(cacheKey)
if (cached) {
console.warn('降级到缓存数据')
return JSON.parse(cached)
}
throw err
}
}4.3 Token 消耗异常
症状:成本告警,token 消耗突然增加
排查步骤:
- 检查是哪个用户消耗异常:
SELECT
user_id,
SUM(total_tokens) as total_tokens,
SUM(estimated_cost) as total_cost,
COUNT(*) as call_count
FROM cost_records
WHERE created_at > NOW() - INTERVAL '1' HOUR
GROUP BY user_id
ORDER BY total_tokens DESC
LIMIT 10- 检查是哪个模型消耗异常:
SELECT
model,
SUM(total_tokens) as total_tokens,
COUNT(*) as call_count
FROM cost_records
WHERE created_at > NOW() - INTERVAL '1' HOUR
GROUP BY model
ORDER BY total_tokens DESC- 检查是否有 prompt 注入攻击:
const suspiciousRequests = await env.DB.prepare(`
SELECT user_id, messages, total_tokens
FROM cost_records
WHERE total_tokens > 100000
AND created_at > ?
`).bind(Date.now() - 60 * 60 * 1000).all()
for (const req of suspiciousRequests.results) {
console.log('可疑请求:', req.user_id, req.messages)
}- 如果确认是攻击,禁用该用户:
await env.DB.prepare(`
UPDATE users SET status = 'disabled' WHERE id = ?
`).bind(suspiciousUserId).run()4.4 缓存雪崩
症状:延迟突然升高,数据库压力增大
排查步骤:
- 检查缓存命中率:
SELECT
time_slice(timestamp, 5, 'minute') AS time_bucket,
SUM(CASE WHEN blob1 = 'cache.hit' THEN _sample_interval ELSE 0 END) AS hits,
SUM(CASE WHEN blob1 = 'cache.miss' THEN _sample_interval ELSE 0 END) AS misses,
hits * 100.0 / (hits + misses) AS hit_rate
FROM ai_gateway_metrics
WHERE blob1 IN ('cache.hit', 'cache.miss')
AND timestamp > NOW() - INTERVAL '30' MINUTE
GROUP BY time_bucket
ORDER BY time_bucket- 如果命中率突然下降,可能是大量缓存同时过期:
// 检查缓存过期时间分布
const cacheKeys = await env.CACHE.list({ prefix: 'chat:' })
const expirationDistribution = cacheKeys.keys.reduce((dist, key) => {
const expiration = key.expiration
const bucket = Math.floor(expiration / 3600) * 3600 // 按小时分桶
dist[bucket] = (dist[bucket] || 0) + 1
return dist
}, {})
console.log('缓存过期时间分布:', expirationDistribution)- 如果是缓存雪崩,启用缓存预热:
// src/lib/cache-warmup.ts
export async function warmupCache(env: Env) {
// 查询最近 1 小时的热门请求
const hotRequests = await env.DB.prepare(`
SELECT messages, model, COUNT(*) as count
FROM cost_records
WHERE created_at > ?
GROUP BY messages, model
ORDER BY count DESC
LIMIT 100
`).bind(Date.now() - 60 * 60 * 1000).all()
// 预热缓存
for (const req of hotRequests.results) {
const cacheKey = generateCacheKey(req)
const cached = await env.CACHE.get(cacheKey)
if (!cached) {
// 缓存不存在,重新生成
const result = await callLLM({
model: req.model,
messages: JSON.parse(req.messages),
}, env)
await env.CACHE.put(cacheKey, JSON.stringify(result), {
expirationTtl: 3600 + Math.random() * 600, // 随机化过期时间
})
}
}
}5. 缓解故障
定位问题后,第一步是缓解影响,而不是立即修复:
5.1 回滚
如果故障是最近部署引起的,立即回滚:
# 查看最近的部署
npx wrangler deployments list
# 回滚到上一个版本
npx wrangler rollback5.2 降级
如果某个功能异常,临时关闭该功能:
// src/routes/chat.ts
app.post('/v1/chat/completions', async (c) => {
// 检查是否启用降级模式
const degradedMode = await c.env.FEATURE_FLAGS.get('degraded_mode')
if (degradedMode === 'true') {
// 降级模式:跳过缓存、简化逻辑
const result = await callLLM(body, c.env)
return c.json(result)
}
// 正常模式
// ...
})5.3 限流
如果流量过大导致服务过载,启用限流:
// src/middleware/rate-limit.ts
export async function rateLimit(c: Context, next: Next) {
const userId = c.get('userId')
const key = `ratelimit:${userId}`
const current = await c.env.RATE_LIMIT.get(key, 'json') || {
count: 0,
resetAt: Date.now() + 60 * 1000,
}
if (Date.now() > current.resetAt) {
current.count = 0
current.resetAt = Date.now() + 60 * 1000
}
// 动态调整限流阈值
const errorRate = await getErrorRate(c.env)
const limit = errorRate > 10 ? 10 : 60 // 错误率高时降低限流
if (current.count >= limit) {
return c.json({ error: 'Rate limit exceeded' }, 429)
}
current.count++
await c.env.RATE_LIMIT.put(key, JSON.stringify(current))
await next()
}6. 修复故障
缓解影响后,才能安心修复问题:
- 复现问题:在测试环境复现故障
- 定位根因:找到导致问题的代码或配置
- 修复问题:修改代码或配置
- 验证修复:在测试环境验证修复有效
- 部署上线:部署修复到生产环境
7. 故障复盘
故障修复后,必须做复盘,避免同类问题再次发生:
7.1 复盘模板
# 故障复盘报告
## 基本信息
- 故障时间:2026-06-21 10:30 - 11:00 (30 分钟)
- 影响范围:所有用户,约 1000 个请求失败
- 故障级别:P1
## 故障描述
10:30 收到告警,HTTP 错误率升高到 15%。用户报告 AI 对话功能无法使用。
## 时间线
- 10:30 收到错误率告警
- 10:35 确认为 LLM API 超时
- 10:40 检查 OpenAI 状态页面,确认 OpenAI 故障
- 10:45 启用降级策略,切换到备用模型
- 10:50 错误率恢复正常
- 11:00 OpenAI 恢复,切换回主模型
## 根因分析
OpenAI API 在 10:30 - 10:50 期间出现服务故障,导致所有调用超时。
## 改进措施
1. **短期**:添加 OpenAI 状态监控,在 OpenAI 故障时自动降级
2. **中期**:实现多模型自动切换,不依赖单一提供商
3. **长期**:建立模型抽象层,支持快速切换不同模型
## 负责人
- 故障处理:张三
- 复盘主持:李四
- 改进跟踪:王五7.2 复盘会议
复盘会议应该回答三个问题:
- 发生了什么:故障的时间线、影响范围
- 为什么发生:根因分析,不是表面原因
- 如何防止:具体的改进措施,而不是空话
避免「 blaming game」(甩锅游戏),重点是改进流程,而不是追究责任。
8. 实战:故障排查工具包
// src/lib/incident-toolkit.ts
// 1. 快速诊断
export async function quickDiagnosis(env: Env) {
const [health, errorRate, p99, activeUsers] = await Promise.all([
fetch('https://your-worker.com/health').then(r => r.json()),
getErrorRate(env),
getP99Latency(env),
getActiveUsers(env),
])
return {
health,
errorRate,
p99,
activeUsers,
timestamp: new Date().toISOString(),
}
}
// 2. 错误分析
export async function analyzeErrors(env: Env, minutes: number = 30) {
const errors = await env.LOGS.query(`
SELECT
event,
error.message,
COUNT(*) as count
FROM logs
WHERE level = 'error'
AND timestamp > NOW() - INTERVAL '${minutes}' MINUTE
GROUP BY event, error.message
ORDER BY count DESC
LIMIT 20
`)
return errors.results
}
// 3. 慢请求分析
export async function analyzeSlowRequests(env: Env, thresholdMs: number = 5000) {
const slowRequests = await env.ANALYTICS.sql(`
SELECT
method,
path,
userId,
requestId,
duration
FROM ai_gateway_metrics
WHERE blob1 = 'http.request'
AND double1 > ${thresholdMs}
AND timestamp > NOW() - INTERVAL '30' MINUTE
ORDER BY duration DESC
LIMIT 50
`)
return slowRequests.results
}
// 4. 成本异常分析
export async function analyzeCostAnomaly(env: Env) {
const today = await env.DB.prepare(`
SELECT SUM(estimated_cost) as total_cost
FROM cost_records
WHERE created_at > date('now')
`).first()
const yesterday = await env.DB.prepare(`
SELECT SUM(estimated_cost) as total_cost
FROM cost_records
WHERE created_at BETWEEN date('now', '-1 day') AND date('now')
`).first()
const increase = (today.total_cost - yesterday.total_cost) / yesterday.total_cost * 100
return {
today: today.total_cost,
yesterday: yesterday.total_cost,
increase: `${increase.toFixed(1)}%`,
}
}
// 5. 生成故障报告
export async function generateIncidentReport(env: Env): Promise<string> {
const diagnosis = await quickDiagnosis(env)
const errors = await analyzeErrors(env)
const slowRequests = await analyzeSlowRequests(env)
const costAnomaly = await analyzeCostAnomaly(env)
return `
# 故障诊断报告
生成时间: ${diagnosis.timestamp}
## 系统状态
- 健康状态: ${diagnosis.health.status}
- 错误率: ${diagnosis.errorRate.toFixed(2)}%
- P99 延迟: ${diagnosis.p99.toFixed(0)}ms
- 活跃用户: ${diagnosis.activeUsers}
## 错误分布
${errors.map(e => `- ${e.event}: ${e.count} 次 (${e.message})`).join('\n')}
## 慢请求 Top 10
${slowRequests.slice(0, 10).map(r => `- ${r.method} ${r.path}: ${r.duration}ms`).join('\n')}
## 成本异常
今日成本: $${costAnomaly.today.toFixed(2)}
昨日成本: $${costAnomaly.yesterday.toFixed(2)}
增长率: ${costAnomaly.increase}
`.trim()
}9. 小结
故障排查的关键点:
- 系统化方法:发现 → 定位 → 缓解 → 修复 → 复盘
- 善用工具:日志、指标、trace 是排查的三大利器
- 先缓解后修复:先减少影响,再彻底修复
- 故障复盘:分析根因,改进流程,防止再次发生
- 工具包:准备常用的诊断脚本,快速定位问题
故障是不可避免的,但可以通过系统化的排查流程和持续改进,减少故障的影响和频率。