从半夜报警到稳如老狗:我的监控系统优化血泪史

Tech架构师
2026-01-03 14:09
阅读 592

凌晨三点,杭州的雨敲着窗,AirPods里放着Lo-fi Hip Hop,我盯着屏幕上第17条重复告警,手里的美式已经凉透。那一刻真想把 Prometheus 配置文件扔进西湖。

作为一个在杭州远程办公三年的独立开发者,自由是真自由——没人管我几点起床、穿不穿拖鞋写代码;孤独也是真孤独——出了问题只能自己扛,连个一起骂产品经理的同事都没有。去年接了个电商 SaaS 项目的运维模块重构,客户要求“系统要稳,告警要准,别动不动半夜call我”。听起来很合理,对吧?结果这成了我今年最痛苦也最有收获的一次技术挑战。

起因:一个被产品经理逼出来的监控需求

事情得从去年双11说起。那会儿我还在帮一家本地跨境电商做后端服务,系统跑在阿里云上,用的是公司默认的 Zabbix + 自定义脚本组合。双11当晚流量暴增,数据库连接池被打爆,但 Zabbix 只告诉我“CPU 飙高”,没任何上下文。我花了40分钟才定位到是某个新上线的促销接口没加缓存,疯狂查库。老板第二天黑着脸说:“你这个监控,跟没装有啥区别?”

更惨的是,测试环境和生产环境配置不一致,导致上线后一堆 false positive 告警。运维小哥(其实就我一个人)每天被钉钉轰炸,最后干脆把通知静音了——直到客户投诉系统卡顿,才发现 Redis 主从同步早就断了三天。

痛定思痛,我决定彻底重构监控体系。目标很明确:精准告警、快速定位、低成本维护。但说起来容易,做起来全是坑。

技术选型:为什么我放弃了 Zabbix?

一开始我考虑继续用 Zabbix,毕竟熟。但它的问题太明显:

  • 指标采集靠 agent 或 SNMP,对现代微服务架构支持弱
  • 告警规则写死在 UI 里,难以版本化
  • 缺乏标签(label)体系,多维查询几乎不可能

于是我转向云原生方案:Prometheus + Grafana + Alertmanager。这套组合拳在阿里、网易内部早已普及,社区生态也成熟。而且 Prometheus 的 pull 模型 + 多维数据模型,特别适合动态扩缩容的容器环境。

但现实很快打脸。

坑一:指标爆炸,存储撑不住

我把所有 Spring Boot 应用都加上了 Micrometer,暴露 /actuator/prometheus 端点。结果 Prometheus 一天抓取的样本数直接飙到 800 万+。一个月下来,磁盘占了 1.2TB,查询慢得像蜗牛。

原因:Micrometer 默认暴露太多无用指标,比如每个 HTTP 请求路径都生成独立的时间序列(http_server_requests_seconds_count{uri="/api/v1/order/{id}"})。而 Prometheus 对高基数(high cardinality)指标极其敏感。

解决方案

  1. 精简指标:在 application.yml 中过滤掉不必要的指标:

    management:
      metrics:
        enable:
          http: false  # 先关掉,后面按需开
        web:
          server:
            requests:
              autotime:
                enabled: true
        tags:
          application: ${spring.application.name}
    
  2. 统一 URI 标签:通过自定义 WebMvcTagsProvider,把动态路径归一化:

    @Bean
    public WebMvcTagsProvider customWebMvcTagsProvider() {
        return (request, response, handler, exception) -> Tags.of(
            Tag.of("uri", extractStablePath(request.getRequestURI()))
        );
    }
    
    private String extractStablePath(String uri) {
        // 把 /api/order/123 转成 /api/order/{id}
        return uri.replaceAll("/\\d+", "/{id}");
    }
    
  3. 启用 TSDB 压缩:升级 Prometheus 到 2.20+,开启 --storage.tsdb.wal-compression,存储占用减少 40%。

坑二:告警风暴 vs 告警沉默

Alertmanager 配置看似简单,但稍不注意就会翻车。

第一次上线,我写了条规则:rate(http_requests_total{status=~"5.."}[5m]) > 0。结果某个第三方支付回调偶尔失败,每分钟触发一次告警。Alertmanager 的 group_wait 设成 30s,导致每 30 秒发一条钉钉消息——整整骚扰了我 3 小时。

后来改成:

- alert: HighHttpErrorRate
  expr: |
    sum by (job, instance) (
      rate(http_requests_total{status=~"5.."}[5m])
    ) / 
    sum by (job, instance) (
      rate(http_requests_total[5m])
    ) > 0.05  # 错误率超过5%
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: "High error rate on {{ $labels.job }}"

但又遇到反向问题:某次数据库主从切换,持续 8 分钟,刚好卡在 for: 10m 之前恢复,告警根本没触发。等我发现时,用户订单已经积压了上千条。

教训for 时间要根据业务容忍度调整。核心交易链路设成 5m,非核心可放宽到 15m。同时,关键路径必须配 多级告警(warning + critical)。

实战:从“能用”到“好用”的三板斧

光有基础监控不够,得让它真正帮到开发。我总结了三个提升体验的关键实践。

1. 黄金指标全覆盖

Google SRE 提出的四大黄金信号:延迟、流量、错误、饱和度。我在每个服务里都确保这四类指标可查:

指标类型 Prometheus 表达式示例 用途
延迟 histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) 发现慢接口
流量 rate(http_requests_total[5m]) 监控 QPS 波动
错误 rate(http_requests_total{status=~"5.."}[5m]) 捕获服务异常
饱和度 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) CPU 使用率

Grafana 里做成统一 Dashboard 模板,新项目一键导入。再也不用每次从零画图。

2. 日志 + 指标联动

有一次线上出现偶发性超时,指标显示延迟 spike,但日志里找不到对应 traceId。原因是应用日志没和监控打通。

解决方案:注入 traceId 到 MDC,并在指标中携带

// 在拦截器中
String traceId = UUID.randomUUID().toString();
MDC.put("traceId", traceId);

// 同时让 Micrometer 指标带上 traceId(谨慎使用,避免基数爆炸)
Counter.builder("request_processed")
    .tag("trace_id", traceId)
    .register(meterRegistry)
    .increment();

更安全的做法是:只在 error 日志中记录 traceId,并通过 Loki 或 ELK 收集。Grafana 支持 Logs + Metrics 联合查询,点击异常点直接跳转日志,排查效率翻倍。

3. 自动化健康检查

以前每次发布,都要手动 check 一堆 URL 是否 200。现在我用 Prometheus 的 Blackbox Exporter 做主动探测:

- job_name: 'blackbox'
  metrics_path: /probe
  params:
    module: [http_2xx]
  static_configs:
    - targets:
      - https://api.myapp.com/health
      - https://admin.myapp.com/login
  relabel_configs:
    - source_labels: [__address__]
      target_label: __param_target
    - source_labels: [__param_target]
      target_label: instance
    - target_label: __address__
      replacement: 127.0.0.1:9115  # blackbox exporter 地址

配合 Alertmanager,服务不可达 2 分钟就告警。连 DNS 解析失败都能捕获,比被动监控靠谱多了。

面试题挑战:面试官最爱问的监控陷阱

最近帮朋友内推,发现大厂面试必问监控设计。结合我的踩坑经验,分享几个高频题:

Q:如何避免告警疲劳?
A:关键在 分层 + 聚合。例如:

  • 应用层:关注业务错误率(如支付失败率)
  • 系统层:关注资源饱和度(CPU、内存、磁盘 IO)
  • 依赖层:关注第三方服务可用性(用 Blackbox Exporter)
    告警规则按严重程度分级,非核心服务只发 warning,不 call 人。

Q:Prometheus 数据丢失怎么办?
A:短期方案是调大 --storage.tsdb.retention.time(默认15天),长期必须上 远程存储(如 Thanos、Cortex)。我们用 Thanos 实现了多集群指标聚合,历史数据查一年都没问题。

Q:如何监控 Serverless 函数?
A:Lambda 或 FC 这类平台,传统 agent 行不通。得依赖平台提供的指标(如 AWS CloudWatch),再用 exporter 拉到 Prometheus。重点监控 冷启动延迟并发限制

结语:监控不是成本,是保险

折腾半年,现在的系统终于“稳如老狗”。上周五晚上十点,客户系统突发流量高峰,Grafana Dashboard 自动标红,Alertmanager 推送一条钉钉:“API QPS 突增至 5000,错误率正常”。我扫了一眼,回了个“收到,自动扩容已触发”,然后继续听歌写代码。

远程开发者最大的安全感,不是自由,而是 系统出问题时你知道它会告诉你,且你知道怎么修。监控体系就是这份安全感的基石。

如果你也在杭州搞技术,或者正被监控折磨,欢迎交流。反正我一个人在家,咖啡管够,bug 管修(收费的那种 😏)。

最后送大家一句我贴在显示器边的话:“没有监控的系统,就像闭着眼开车——迟早出事。”

P.S. 本文所有配置和代码都经过生产验证,但别照搬!每个业务场景不同,先小范围试,再全量推。毕竟,谁也不想在周五晚上被自己写的告警吵醒,对吧?

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝