跳到主要内容

线上故障排查

问题

Go 服务出现线上故障(接口报错、服务不可用、数据异常),如何快速定位和恢复?

答案

应急处理流程

核心原则

先止血,后定因。线上故障的第一优先级是恢复服务,而不是找根本原因。

第一步:止血

止血手段适用场景操作
代码回滚近期有发布回滚到上一个稳定版本
限流流量激增Sentinel/Nginx 限流
降级非核心功能故障Feature Flag 关闭故障功能
切换依赖服务故障切备用链路/降级缓存
扩容容量不足K8s HPA 扩容
// Feature Flag 降级
func GetRecommendation(ctx context.Context, userID string) ([]Item, error) {
// 推荐服务故障时降级到热门列表
if featureFlag.IsDisabled("recommendation") {
return getHotItems(ctx)
}
return recommendService.Get(ctx, userID)
}

第二步:定位

信息收集顺序

监控面板 → 错误日志 → 链路追踪 → pprof → 代码 diff

2.1 监控面板

快速看 RED 指标(Grafana):

  • Rate:QPS 是否异常
  • Errors:错误率是否飙升
  • Duration:P99 延迟是否增加

同时关注:

  • CPU / 内存 / Goroutine 数
  • 数据库连接池状态
  • 下游服务健康状况

2.2 错误日志

# 按时间排序查看错误日志
grep -i "error\|panic" /var/log/app/app.log | tail -100

# 如果是结构化日志(JSON),用 jq 过滤
cat app.log | jq 'select(.level=="error")' | tail -50

# ELK 查询
# level:error AND service:user-service AND @timestamp >= "2024-01-15T10:00:00"

2.3 链路追踪

# 从错误日志中提取 traceID
# 在 Jaeger/Zipkin 中搜索 traceID
# 看整条链路哪个环节耗时或报错

2.4 pprof(性能问题)

# CPU 飙高
go tool pprof http://pod-ip:6060/debug/pprof/profile?seconds=30

# 内存增长
go tool pprof http://pod-ip:6060/debug/pprof/heap

# Goroutine 泄漏
go tool pprof http://pod-ip:6060/debug/pprof/goroutine

2.5 代码 Diff

# 查最近的代码变更
git log --oneline -10
git diff HEAD~1 HEAD --stat

常见故障类型 & 排查重点

故障类型排查方向
接口大量 5xx错误日志 → panic 栈 → 下游依赖状态
接口超时链路追踪 → 慢查询 → 连接池满 → GC STW
CPU 飙高pprof CPU → 热点函数 → 死循环/正则
内存 OOMpprof heap → Goroutine 泄漏 → 大对象缓存
数据不一致并发操作 → 缓存一致性 → 分布式事务
部分用户异常灰度策略 → 特定参数 → 数据问题

第三步:修复

// Hotfix 注意事项
// 1. 修复代码尽量小,只改必要部分
// 2. 本地测试通过 + Code Review
// 3. 灰度发布验证
// 4. 如果不确定修复是否正确,先用降级止血

第四步:验证

# 验证清单
# ✅ 接口响应正常(curl 测试)
# ✅ 错误率恢复到正常水平
# ✅ 延迟 P99 恢复
# ✅ 监控面板所有指标正常
# ✅ 日志无新增异常
# ✅ 观察至少 15~30 分钟

第五步:复盘

## 故障复盘模板

### 基本信息
- 故障等级:P0/P1/P2
- 影响范围:xxx 接口、xx 用户
- 故障时长:10:05 ~ 10:35(30 分钟)

### Timeline
- 10:05 告警触发:接口错误率 > 5%
- 10:08 值班同学开始排查
- 10:12 确认是数据库连接池耗尽
- 10:15 回滚到上一版本止血
- 10:20 错误率恢复
- 10:35 确认服务稳定

### 根因分析
- 新版本引入了一个查询,忘了 rows.Close() 导致连接泄漏

### 改进措施
- [ ] 增加 db 连接池监控告警
- [ ] CI 加 rows.Close() 的 lint 检查
- [ ] 开发 Code Review checklist

Go 服务必备应急工具

工具用途
pprofCPU/内存/Goroutine 分析
Prometheus + Grafana实时监控
Jaeger/Zipkin链路追踪
ELK / Loki日志查询
Sentry错误聚合告警
kubectlK8s 操作(回滚、扩容)

常见面试问题

Q1: 线上出现 panic 怎么办?

答案

  1. Recovery 中间件确保服务不挂:
func RecoveryMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
stack := debug.Stack()
log.Printf("PANIC: %v\n%s", r, stack)
// 上报 Sentry
c.AbortWithStatus(500)
}
}()
c.Next()
}
}
  1. 根据 panic 栈信息定位具体代码行
  2. 通常原因:nil pointer、index out of range、map 并发读写

Q2: 服务偶发超时但大部分正常怎么排查?

答案

  • 看超时请求的链路追踪,找到慢环节
  • 可能原因:GC STW、锁竞争、数据库慢查询、DNS 解析
  • 收集多个超时 case 寻找共性
  • 注意 P99 vs P50 的差异

Q3: 如何做到一分钟内发现故障?

答案

  • Prometheus 采集间隔 15s + 告警规则 for: 1m
  • 关键接口用即时告警(error rate > 5% 持续 30s)
  • 多渠道:钉钉/飞书/电话/短信
  • 值班机制 + 自动 oncall 轮转

Q4: 回滚有哪些方式?

答案

  • 代码回滚:K8s kubectl rollout undo deployment/xxx
  • 配置回滚:配置中心回退版本
  • 数据库回滚:谨慎,通常只能补偿而非回滚
  • 流量回滚:灰度流量切回旧版本

相关链接