跳转到内容

V7: 系统故障 —— 「半夜挂了没人知道」

周六凌晨 3 点,PostgreSQL 数据磁盘写满了,系统开始返回 500 错误。Redis 缓存还能撑一会儿,但写操作全部失败。周一早上 9 点,财务打开页面发现白屏,找到你。系统已经挂了整整两天,没有人收到任何告警。

老板很生气:“系统挂了两天才知道?以后故障必须第一时间通知到我。”

当前状态 (V6):系统有完整功能和安全加固,但日志是 fmt.Println 散落在代码里,没有健康检查端点,没有监控指标,没有告警机制。出了问题只能事后翻服务器日志。


层级问题影响
表象系统挂了两天无人知晓周末报销数据丢失,用户体验极差
直接原因没有健康检查和告警故障发生后无人感知
系统原因没有监控指标采集磁盘逐渐写满的趋势无法预判
设计缺失日志非结构化,无法检索和分析排障靠 grep 翻日志,效率极低
根本原因可观测性为零(no observability)系统是个黑盒,出了事只能猜
  • 日志 (Logs):结构化 JSON,带 request_id 串联请求链路
  • 指标 (Metrics):Prometheus 格式,量化系统健康度
  • 追踪 (Traces):本阶段暂不引入(单服务场景 request_id 足够)

graph TD
User["用户"] --> Nginx --> Go["Go/Gin"]
Go --> DB["PostgreSQL"]
Go --> Logs["日志收集<br/>(结构化 JSON)"]
DB --> Logs
Logs --> Monitor["Prometheus<br/>+ Grafana"]
Monitor -->|"告警"| Alert["📱 手机/邮件通知"]

新增:结构化日志 + 健康检查 + Prometheus + Grafana + 告警 解决:半夜挂了 5 分钟内收到通知

决策点选项 A选项 B选择理由
日志格式文本日志(人类可读)JSON 结构化日志B方便 ELK/Loki 检索,可程序解析
日志库标准库 logzerolog / zapB高性能 + 结构化 + 级别过滤
指标采集自定义上报Prometheus client_golangB业界标准,Grafana 原生支持
可视化终端看日志Grafana DashboardB图形化趋势,非技术人员也能看
告警人工巡检Grafana Alerting 规则B7x24 自动检测,支持多渠道通知
可观测性体系:
├── 应用层
│ ├── 结构化日志(zerolog):每条日志带 request_id, method, path, status, latency
│ ├── Prometheus 指标:/metrics 端点
│ └── 健康检查:/health 端点(检查 DB + Redis 连通性)
├── 采集层
│ └── Prometheus server:定时拉取 /metrics
├── 可视化层
│ └── Grafana:Dashboard + Alerting
└── docker-compose 新增:prometheus + grafana 容器
指标名类型说明
http_requests_totalCounter请求总数(按 method, path, status 分标签)
http_request_duration_secondsHistogram请求延迟分布
http_requests_in_flightGauge当前正在处理的请求数
db_connections_activeGauge数据库活跃连接数
db_connections_idleGauge数据库空闲连接数
cache_hits_totalCounterRedis 缓存命中次数
cache_misses_totalCounterRedis 缓存未命中次数

我有一个 Go(Gin) + GORM + PostgreSQL + Redis 的团队记账工具,目前没有任何监控。
周末系统挂了两天没人知道,需要加上完整的可观测性体系。
请帮我实现以下功能:
1. **结构化日志中间件**
- 使用 rs/zerolog 库
- 新建 server/middleware/logger.go
- 每个请求生成唯一 request_id(UUID v4),写入 context 和响应头 X-Request-ID
- 请求完成时输出 JSON 日志,字段包括:
timestamp, request_id, method, path, status, latency_ms, client_ip, user_id(如有JWT), error(如有)
- 日志级别:2xx=info, 4xx=warn, 5xx=error
- 启动时根据环境变量 LOG_LEVEL 设置级别(默认 info)
2. **健康检查端点**
- 新建 server/handlers/health.go
- GET /health 返回 JSON:
{
"status": "ok" | "degraded" | "down",
"checks": {
"database": {"status": "ok", "latency_ms": 5},
"redis": {"status": "ok", "latency_ms": 2}
},
"timestamp": "2024-01-01T00:00:00Z"
}
- DB 检查:执行 SELECT 1,超过 5 秒算 down
- Redis 检查:执行 PING,超过 3 秒算 down
- 任一组件 down 则整体 status = "down"
- 该接口不需要认证
3. **Prometheus 指标**
- 使用 prometheus/client_golang 库
- 新建 server/middleware/metrics.go
- 注册以下指标:
- http_requests_total (Counter): labels=[method, path, status]
- http_request_duration_seconds (Histogram): labels=[method, path]
buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
- http_requests_in_flight (Gauge)
- 在 main.go 暴露 GET /metrics 端点(使用 promhttp.Handler())
- 新建 server/middleware/db_metrics.go:
- 定时(10s)采集 DB 连接池状态:active, idle, max_open
- 注册为 Gauge 指标
4. **Grafana Dashboard + Prometheus 配置**
- docker-compose.yml 新增 prometheus 和 grafana 服务
- prometheus.yml 配置:
scrape_interval: 15s
targets: ["host.docker.internal:8080"](或 app 容器名)
- grafana provisioning:
- datasource: Prometheus
- dashboard JSON:4 个面板
(1) QPS 折线图 (rate(http_requests_total[1m]))
(2) P50/P95/P99 延迟 (histogram_quantile)
(3) 错误率 (status=~"5.." / total)
(4) DB 连接池使用率
5. **告警规则**
- 在 Grafana 或 Prometheus alerting rules 中配置:
- 错误率 > 5% 持续 2 分钟 → 告警
- 健康检查失败持续 1 分钟 → 告警
- DB 活跃连接数 > 80% 最大连接数持续 5 分钟 → 告警
- 告警通知先配置为 webhook(后续可接钉钉/飞书)
6. **请求日志与指标中间件注册**
- 在 Gin 路由初始化时,按顺序注册:
(1) Recovery 中间件
(2) Request ID 中间件
(3) Metrics 中间件
(4) Logger 中间件
(5) 业务路由
请给出完整代码文件,包括 docker-compose 新增部分和配置文件。

  • 启动服务后请求 API,终端输出 JSON 格式日志
  • 日志包含 request_id,且与响应头 X-Request-ID 一致
  • 请求报错时日志级别为 error,包含 error 字段
  • 设置 LOG_LEVEL=debug 后可看到更多日志
  • curl http://localhost:8080/health 返回 {"status": "ok", ...}
  • 停止 PostgreSQL 后再请求,database 状态变为 down
  • 停止 Redis 后再请求,redis 状态变为 down
  • 整体状态相应变为 down
  • curl http://localhost:8080/metrics 返回 Prometheus 格式文本
  • 发几个请求后,http_requests_total 计数增加
  • http_request_duration_seconds 有 bucket 数据
  • db_connections_active 有值
  • docker-compose up 后访问 http://localhost:3000(admin/admin)
  • Prometheus 数据源连接正常(绿色)
  • Dashboard 四个面板有数据展示
  • 手动触发一些请求,图表实时更新
  • 停止数据库,1 分钟内告警触发
  • 用压测工具打出 5xx 错误,错误率告警触发
  • 告警恢复后状态变回 OK

主题对应模块
可观测性 = 日志 + 指标 + 追踪,三者缺一不可→ Module 15 (可观测性)
结构化日志是可检索日志的前提→ Module 15
request_id 串联一次请求的所有日志→ Module 15
Prometheus 拉模型 vs 推模型的权衡→ Module 15
RED 方法:Rate, Error, Duration 三个黄金指标→ Module 15
docker-compose 编排多服务本地开发环境→ Module 16 (Docker Compose)

现象:/metrics 是公开的,攻击者可以看到系统内部状态。 原因:指标端点没有做访问控制。 解决:生产环境 /metrics 只允许 Prometheus 服务器 IP 访问,或放在内部端口(如 :9090)不对外暴露。

现象:INFO 级别日志每天几 GB,磁盘又满了(和故障原因一样)。 原因:日志没有做轮转(rotation)和清理策略。 解决:使用 lumberjack 做日志轮转(按大小/天数),或输出到 stdout 由 Docker 管理日志。生产环境日志级别设为 WARN。

现象:Prometheus 内存暴涨,查询越来越慢。 原因:把 URL path 里的动态参数(如 /api/expenses/123)当标签,每个 ID 一个时间序列。 解决:path 标签用路由模板(/api/expenses/:id),不用实际 URL。用 Gin 的 c.FullPath() 而不是 c.Request.URL.Path

现象:Kubernetes/负载均衡每秒调健康检查,每次查询 DB 和 Redis。 原因:健康检查做了真实的依赖查询,频率又高。 解决:区分 liveness 和 readiness:liveness 只检查进程存活(直接返回 200),readiness 检查依赖组件。或者缓存健康状态(每 10 秒实际检查一次)。

现象:面板显示 “No data”。 原因:Prometheus 抓取目标配错了地址,容器内 localhost 不等于宿主机 localhost。 解决:Docker 内部使用服务名通信(如 app:8080),或使用 host.docker.internal(Mac/Windows)。用 Prometheus 的 Status > Targets 页面确认抓取状态。