HELP jvm_memory_used_bytes Used bytes of a given JVM memory area.

何文★
2025-06-16 20:32
阅读 390

从“看不见”到“看得很清楚”:我们在一次故障排查中重建监控体系的故事

从“看不见”到“看得很清楚”:我们在一次故障排查中重建监控体系的故事

开篇:为什么我想聊这个话题?

我在一家中小型互联网公司做基础工具研发,日常工作中有一项很核心的任务就是维护、优化和扩展内部的各种开发支持工具。我们团队不大,五个人左右,但支撑着整个公司的工程效率、线上环境稳定性等关键环节。

今天想和大家聊聊一个让我印象非常深刻的项目——重构公司的监控告警系统。这次项目的起因并不复杂,却暴露了我们很多技术债务和长期忽视的问题。通过这次重构,不仅解决了当时紧急的业务问题,还让我们后续在线上运维方面变得从容许多。

这篇文章会尽量贴近实际的工作场景,我会以第一人称的角度来讲讲我们是怎么一步步把“监控这事儿”真正做好一点的。希望对你们有所帮助。


背景介绍:我们的服务怎么突然全挂了?

事情得从一个普通的上午说起。那天早上刚坐下没多久,钉钉群里就炸开了锅:

“用户反馈登录失败!”
“后端接口全报错!”
“数据库连接数飙到上限了!!!”

我们几个迅速进入战时状态,开始检查日志,但发现一个问题——根本没有及时有效的数据支撑。虽然之前也不是完全没做监控,但我们用的是开源方案 Prometheus + Grafana,配置很零散,指标也是各自为政,谁想加谁加,报警也设置得不规范。

那次事故持续了大概40多分钟,最后查出来是一个服务在升级过程中导致 Redis 连接池没有释放,最终打爆了中间件资源。但更糟的是:

  • 线上没人第一时间知道出了问题
  • 告警是半小时以后才触发
  • 我们根本不知道哪个节点出问题

这件事之后,老板说了一句话:“你们不是应该早就发现这个问题吗?”

这句话就像一记闷棍敲在头上,让我们意识到:监控这件事,不能再拖了。


我们的目标很朴素:看得见、看得懂、能响应

于是我们开启了监控体系建设的“第二版”。目标很朴素也很实用:

  1. 看得见当前系统的健康状态
  2. 出问题能及时告警并定位根源
  3. 让每个工程师都能理解这些信息,而不只是DevOps

我们重新梳理了已有的监控内容,包括:

  • 各个微服务的关键性能指标(QPS、响应时间)
  • 数据库连接数、慢查询统计
  • Kafka消息堆积情况
  • Redis内存、缓存命中率
  • JVM 指标(GC、堆栈等)
  • HTTP 错误码分布

但这些都不是最难的,难的是把这些东西组织好、整合起来,并且在关键时刻发挥作用。


第一步:技术选型上的“取舍大战”

我们开始讨论新的架构时,最先面临的就是技术选型。我这里特别要强调一下:不要盲目追求高大上,而是要看自己能不能驾驭。

我们对比了以下几种主流方案:

技术栈 特点 优缺点
Prometheus + Alertmanager 云原生友好,开箱即用,社区活跃 配置分散,规则复杂,集群化困难
ELK Stack(Elasticsearch + Logstash + Kibana) 强大的日志分析能力 实时性差,部署成本高
Zabbix 经典老牌,功能齐全 对容器支持较弱,学习曲线陡
OpenTelemetry + Tempo / Loki 新一代可观察性平台,集追踪、日志、指标一体 学习门槛较高
Datadog / NewRelic(SaaS方案) 全托管,可视化强 成本不可控,依赖第三方

最终我们选择了一个折中的组合:

Prometheus + VictoriaMetrics (TSDB) + Grafana + Alertmanager + Loki + Promtail + Node Exporter

为什么这么选?

  • Prometheus 是轻量级拉模型,适合我们的微服务结构
  • VictoriaMetrics 是 Prometheus 的高性能替代(兼容PromQL,存储效率高)
  • Loki 和 Promtail 是日志收集组件,与现有系统无缝衔接
  • 整体可以自建,节省成本,又不失灵活性

这套方案后来验证下来确实不错,特别是在资源有限的情况下,性价比非常高。


架构设计:如何构建“看得到”的能力?

我们的整体架构图如下(简化版本):

+-----------------+
|   Service A     |---+            +--------------+
+-----------------+   |            |              |
                      |            |  Grafana     |
+-----------------+   |    +-------+ Dashboards   |
|   Service B     |---+----|        +--------------+
+-----------------+   |    |
                      |    |    +------------------+
+-----------------+   |    |    | Prometheus Scrape|
|   Service C     |---+    +----> VM Agent         |
+-----------------+        |    | +----------------+
                           |    | |
                           |    | |
+----------------------+   |    | |
| DB & Middlewares      | -+    | |
+----------------------+       | |
                               | |
                     +---------v----------+
                     |  VictoriaMetrics    | 
                     | (Remote Write Store)|
                     +---------------------+


![团队协作平台-1](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025061620/e0a44e40-19c3-4a41-be34-39d7f1216c8d.jpg)


+-----------------+
|    Loki 日志采集 |<-----> Promtail
+-----------------+

其中几个关键点说明:

  • 每个服务都暴露 /metrics 接口(基于 Micrometer 或 Prometheus client)
  • 使用 VictoriaMetrics Agent 替代 Prometheus 拉取数据
  • 所有指标写入 VictoriaMetrics 单机版(VMStorage)
  • 使用 Grafana 展示面板(内置PromQL)
  • AlertManager 负责报警路由和抑制策略
  • Loki 收集所有服务的日志(通过 Promtail),并支持按关键字搜索、关联指标报警

实践细节:从零搭建一套监控系统

1. 微服务埋点(Java为例)

我们使用 Spring Boot 应用,所以直接引入了 Micrometer 来暴露指标:

<!-- pom.xml -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

然后在启动类中启用监控端点:

@SpringBootApplication
@EnableWebMvc
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

配置 application.yml:

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    metrics:
      enabled: true
    prometheus:
      enabled: true

访问 http://localhost:8080/actuator/prometheus 可以看到类似输出:

# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="heap",} 1.54986968E8
...

有了这个就可以被 Prometheus 抓取了。


2. VictoriaMetrics Agent 配置示例

我们不再使用原始的 Prometheus,改用 VictoriaMetrics 的 agent 来抓取指标:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

remote_write:
  - url: http://victoriametrics:8428/api/v1/write

scrape_configs:
  - job_name: 'spring-boot-services'
    static_configs:
      - targets: ['service-a:8080', 'service-b:8080']

这样所有的指标都会写入 VictoriaMetrics 提供的远程写接口。


3. Grafana 面板配置技巧

Grafana 中我们创建了一个统一的“服务大盘”模板:

  • 左边显示 QPS、错误率
  • 中间展示 JVM 内存变化趋势
  • 右侧是最近5分钟的错误日志摘要(Loki)

举个例子,JVM 内存的查询语句可以这样写:

jvm_memory_used_bytes{job="spring-boot-services"} / (1024*1024)

单位换算成 MB,直观易读。


踩坑经验:监控系统也有“暗礁”

在落地过程中我们踩了不少坑,有些至今想起来还会苦笑一声。

1. 监控指标太多导致性能崩溃?

最初我们试图抓取所有能拿到的指标,结果VictoriaMetrics的CPU飙到了90%以上,甚至影响到线上其他服务。

解决办法:

  • 在应用代码中限制暴露出的指标数量(如只保留核心业务相关)
  • Prometheus 抓取时加过滤,避免拉取非必要指标:
    metric_relabel_configs:
      - source_labels: [__name__]
        regex: '(jvm_.*)|(http_server_requests.*)'
        action: keep
    

2. 告警轰炸,没人敢睡觉

刚开始搞完告警规则,半夜收到几十条告警,全是“某个服务某一分钟内错误数超10”的通知,但实际上可能是偶发的异常。

解决办法:

  • 告警增加聚合和容忍度:

    record: job:http_errors_per_second:rate1m
    expr: rate(http_requests_total{status=~"5.."}[1m]) by (job)
    
    alert: HighHttpErrorRate
    expr: job:http_errors_per_second:rate1m > 10
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: High error rate on {{ $labels.job }}
      description: Error rate is above 10 per second (current value: {{ $value }})
    
  • 设置“抑制规则”,比如当某个服务不可达时,暂停其派生的所有告警

3. 一个指标名称引发的“血案”

我们有个服务叫 user-service,它的 JVM 指标里包含了两个实例标签:

{jvm_application_class=..., instance="user-service-74d6fc8dfb-qgklx:8080"}

结果在 Grafana 中显示的时候,每个 Pod 名字都是独立的图,根本看不出总体趋势。

解决办法:

在查询时忽略 Pod 名字:

max by (job) (rate(http_requests_total[1m]))

或者在 Prometheus 抓取时重写 instance 标签:

- source_labels: [__address__, __meta_kubernetes_pod_label_app]
  regex: (.+):.*;(.+)
  target_label: instance
  replacement: "$2"

上线效果:从慌乱到从容

这套系统上线两周后,我们又遇到了一次真实故障:

一个定时任务因为配置错误,每天凌晨1点向 Kafka 推送大量无效数据,导致下游服务处理阻塞。但这次……

不到5分钟我们就收到了告警:“kafka-topic 处理延迟超过阈值”。

我们很快打开 Grafana 定位到具体哪一组消费者出现瓶颈,并在几分钟内回滚配置解决问题。

老板问:“上次这种事是不是要1小时?”
我说:“现在我们还没睡醒就知道了。”


总结一下:这次重构带来的价值

  1. 响应速度显著提升:大多数线上问题在发生前就被预警,而不是被用户投诉才发现
  2. 定位效率提高:结合日志和指标可以快速判断根因,不需要翻半天日志
  3. 团队协作更顺畅:新来的同学也能通过Grafana看清系统运行状况,减少沟通成本
  4. 技术债有所缓解:标准化的监控入口让我们今后添加新服务更加容易

给读者的一些建议

如果你现在所在的团队还没有完整的监控体系,或者还在“裸跑”,那么以下建议或许对你有用:

  1. 不要一开始就想着搞一套“完美的”系统,先抓重点,比如核心业务服务、关键中间件
  2. 学会权衡监控粒度,不是指标越多越好,能表达问题即可
  3. 告警要有节制,否则就成了噪声,反而掩盖了真正的风险
  4. 文档和命名规范很重要,尤其是多个服务同时接入时
  5. 让监控成为“标配”,新服务上线时默认就要包含指标接入逻辑

另外,在如今微服务和容器化盛行的大背景下,OpenTelemetry(OTel) 也是一个值得探索的方向。它将 Metrics、Logs、Traces 三者统一管理,未来可能会逐步替代传统的割裂式监控方式。

不过,再先进的工具也需要“正确地用”,否则也可能变成另一个雷区。


最后说几句心里话

说实话,做监控这件事,短期内很难看出收益,也不像开发新功能那样能立马让用户感受到变化。但它就像房子的地基——平时你不会注意,但一旦塌了,后果严重。

通过这次项目我深深体会到,一个好的监控系统并不是冷冰冰的数据堆砌,而是一种安全感的建立。它让我们在面对未知时少一分焦虑,多一分底气。

所以,下次当你准备动新需求的时候,不妨花点时间想想:“如果这部分服务出问题了,我能第一时间察觉吗?”

别等到真的出问题了,才发现那时候的你说:“早知道就该早点做了。”


如果你也在做类似的改造,欢迎留言交流经验。毕竟,只有经历过的人才知道这其中有多少坑,又有多少收获。

评论 0

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