如果你在技术社区问“很懂表的人都会戴些什么表?”,得到的答案大概率不是劳力士、百达翡丽或欧米茄,而是一串你从未听过的缩写:Prometheus、Grafana、Jaeger、VictoriaMetrics……
没错,在程序员的世界里,“懂表”意味着精通监控指标表和链路追踪表,而不是手腕上的机械表。当系统半夜告警、线上流量暴跌、用户投诉页面白屏时,真正能救火的不是名贵腕表,而是你能否在30秒内从海量监控数据中定位到根因。
今天这篇文章,我们就来彻底拆解这个“技术人的腕表收藏夹”——现代可观测性技术栈。我不会只给你罗列一堆工具名词,而是要讲清楚:为什么监控体系是后端工程师的“第二双眼睛”;从零搭建一套生产可用的监控系统,核心要解决哪三个问题;以及,当你真正“懂表”之后,该如何像老手一样,优雅地利用这些数据驱动决策。
1. 这篇文章真正要解决的问题:从“救火队员”到“系统医生”的蜕变
很多开发团队对监控的认知还停留在“有总比没有强”的阶段:部署个Prometheus抓点基础指标,装个Grafana配几个看板,就觉得高枕无忧了。直到某天线上发生复杂故障,你会发现:
- 问题一:告警轰炸,噪音淹没信号。CPU使用率、内存使用率、磁盘IO……几十个指标同时告警,你根本分不清谁是因、谁是果。
- 问题二:数据孤岛,排查路径断裂。你知道应用响应慢了,但不知道是数据库慢、缓存慢、还是下游服务慢。指标、日志、链路追踪数据各自为政,你需要像侦探一样在三个系统间反复横跳。
- 问题三:只有现象,没有根因。监控告诉你“接口错误率飙升”,但不会告诉你是因为最新一次发布引入了有问题的SQL查询,还是因为某个依赖的第三方服务挂了。
这篇文章要解决的,正是如何构建一个关联的、可行动的、以服务为中心的可观测性体系。让你不仅能“看到”系统异常,更能快速“理解”异常背后的故事,并“执行”有效的修复动作。这就像从只会看体温计的护士,升级为能通过CT、血液报告综合诊断的医生。
2. 基础概念:指标(Metrics)、日志(Logs)、追踪(Traces)——可观测性的三大支柱
在深入实践前,必须统一语言。可观测性(Observability)的核心是通过系统外部输出来推断其内部状态,它主要依赖三类数据:
| 数据维度 | 是什么 | 典型内容 | 核心工具举例 | 擅长回答的问题 |
|---|---|---|---|---|
| 指标 (Metrics) | 随时间变化的数值度量,通常是聚合后的。 | 请求QPS、错误率、响应时间P99、CPU使用率。 | Prometheus, VictoriaMetrics | “系统整体健康度如何?” “流量是否异常?” |
| 日志 (Logs) | 离散的、带时间戳的文本记录,记录特定事件。 | ERROR [2023-10-27 14:30:01] UserController - Failed to query user id=123, SQLException: ... | ELK Stack (Elasticsearch, Logstash, Kibana), Loki | “在错误发生的时刻,系统执行了哪些操作?” “具体的错误堆栈是什么?” |
| 追踪 (Traces) | 记录单个请求在分布式系统中流转的完整路径。 | 一个用户登录请求,经过了网关 -> 用户服务 -> 数据库 -> 缓存 -> 积分服务。 | Jaeger, Zipkin, SkyWalking | “这个慢请求到底时间耗在了哪个服务、哪个方法上?” |
它们的关系是什么?想象一下侦探破案:
- 指标告诉你“犯罪率在凌晨2点突然飙升”(宏观趋势)。
- 日志提供了几个案发现场的详细笔录(具体事件)。
- 追踪则画出了罪犯从入室到逃离的完整行动路线图(请求全链路)。
一个成熟的监控体系,必须让这三者能够相互关联。这是从“监控”走向“可观测性”的关键一步。
3. 环境准备:搭建你的本地可观测性沙箱
我们使用 Docker Compose 来快速搭建一个包含核心组件的实验环境。这避免了污染本地环境,也最贴近生产部署方式。
前置条件:
- 操作系统:Linux, macOS 或 WSL2 (Windows)
- 已安装 Docker 和 Docker Compose
- 至少 4GB 可用内存
创建一个项目目录,并编写docker-compose.yml文件:
# docker-compose.yml version: '3.8' services: # 指标收集与存储:Prometheus prometheus: image: prom/prometheus:latest container_name: prometheus volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/etc/prometheus/console_libraries' - '--web.console.templates=/etc/prometheus/consoles' - '--storage.tsdb.retention.time=200h' - '--web.enable-lifecycle' ports: - "9090:9090" networks: - observability-net restart: unless-stopped # 指标可视化:Grafana grafana: image: grafana/grafana:latest container_name: grafana volumes: - grafana_data:/var/lib/grafana - ./grafana/provisioning:/etc/grafana/provisioning environment: - GF_SECURITY_ADMIN_PASSWORD=admin123 - GF_INSTALL_PLUGINS=grafana-timestream-datasource ports: - "3000:3000" networks: - observability-net restart: unless-stopped depends_on: - prometheus # 链路追踪:Jaeger (all-in-one 模式,适合演示) jaeger: image: jaegertracing/all-in-one:latest container_name: jaeger ports: - "16686:16686" # Jaeger UI - "14268:14268" # 接收客户端上报的端口 - "14250:14250" # 接收 gRPC 格式的追踪数据 environment: - COLLECTOR_OTLP_ENABLED=true networks: - observability-net restart: unless-stopped # 日志聚合:Loki + Promtail (轻量级方案) loki: image: grafana/loki:latest container_name: loki ports: - "3100:3100" command: -config.file=/etc/loki/local-config.yaml networks: - observability-net restart: unless-stopped promtail: image: grafana/promtail:latest container_name: promtail volumes: - /var/log:/var/log:ro # 挂载宿主机日志目录,按需调整 - ./promtail/promtail-config.yaml:/etc/promtail/config.yaml command: -config.file=/etc/promtail/config.yaml networks: - observability-net restart: unless-stopped depends_on: - loki # 一个示例应用:用于生成指标、日志和追踪 demo-app: image: ghcr.io/open-telemetry/opentelemetry-demo/otel-demo-app:latest container_name: demo-app ports: - "8080:8080" environment: - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317 - OTEL_SERVICE_NAME=demo-app networks: - observability-net restart: unless-stopped networks: observability-net: driver: bridge volumes: prometheus_data: grafana_data:接下来,创建 Prometheus 的配置文件:
# prometheus/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] - job_name: 'demo-app' static_configs: - targets: ['demo-app:8080'] # 抓取示例应用的指标 - job_name: 'node-exporter' static_configs: - targets: ['node-exporter:9100'] # 如果需要主机监控,可额外部署 node-exporter创建 Promtail 的配置文件(用于收集日志并发送给 Loki):
# promtail/promtail-config.yaml server: http_listen_port: 9080 grpc_listen_port: 0 positions: filename: /tmp/positions.yaml clients: - url: http://loki:3100/loki/api/v1/push scrape_configs: - job_name: system static_configs: - targets: - localhost labels: job: varlogs __path__: /var/log/*.log # 收集宿主机系统日志,可根据需要调整路径最后,启动整个栈:
# 在项目根目录执行 docker-compose up -d启动后,你可以访问以下服务:
- Grafana: http://localhost:3000 (用户名
admin, 密码admin123) - Prometheus: http://localhost:9090
- Jaeger UI: http://localhost:16686
- Loki: http://localhost:3100 (API)
4. 核心流程拆解:如何让数据说话
环境跑起来了,但一堆数字和曲线本身没有意义。关键在于建立数据之间的关联,并设置有效的告警规则。流程可以拆解为四步:
第一步:指标埋点与暴露应用需要暴露指标。对于Spring Boot应用,最简单的方式是集成micrometer-registry-prometheus。
第二步:数据采集与存储Prometheus 会定期(scrape_interval)从应用暴露的 HTTP 端点(如/actuator/prometheus)拉取指标数据,并存入其内置的时序数据库。
第三步:可视化与探索Grafana 配置 Prometheus 作为数据源,通过编写查询语句(PromQL)将指标数据绘制成图表,并组装成业务/技术看板。
第四步:告警与联动在 Prometheus 或 Grafana 中定义告警规则(Alerting Rules),当指标达到阈值时,通过 Alertmanager 将告警信息路由到钉钉、企业微信、Slack 或 PagerDuty 等渠道。
其中最容易被忽视、也最关键的一环是:从告警到行动的闭环。一个高级的实践是,在告警信息中直接附上相关链路的 TraceID 或关键错误的 LogID,让接收者能一键跳转到 Jaeger 或 Loki 查看详情,极大缩短排查路径。
5. 完整示例:为Spring Boot应用注入可观测性
让我们从一个具体的微服务开始。假设我们有一个用户查询服务user-service。
5.1 添加依赖在pom.xml中添加以下依赖:
<!-- Spring Boot Actuator:提供健康检查、指标暴露端点 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Micrometer Prometheus 注册表:将指标转换为Prometheus格式 --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> <!-- OpenTelemetry Starter:自动生成链路追踪 --> <dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-spring-boot-starter</artifactId> <version>2.5.0</version> <!-- 请使用最新稳定版本 --> </dependency>5.2 配置应用属性在application.yml中配置:
# application.yml spring: application: name: user-service management: endpoints: web: exposure: include: health, info, prometheus # 暴露 prometheus 端点 metrics: tags: application: ${spring.application.name} # 为所有指标添加统一标签 tracing: sampling: probability: 1.0 # 采样率,生产环境可调低,如0.1 # OpenTelemetry 配置:将追踪数据发送到Jaeger otel: traces: exporter: otlp metrics: exporter: none # 指标我们还是用Prometheus,这里关掉OTLP的指标 logs: exporter: none service: name: ${spring.application.name} exporters: otlp: endpoint: http://localhost:14250 # Jaeger 的 OTLP gRPC 接收端点5.3 添加自定义业务指标在业务代码中,我们可以使用 Micrometer 的MeterRegistry记录自定义指标,例如统计查询用户详情的次数和耗时。
// UserController.java import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/users") public class UserController { private final UserService userService; private final Counter userQueryCounter; private final Timer userQueryTimer; // 通过构造器注入 MeterRegistry public UserController(UserService userService, MeterRegistry registry) { this.userService = userService; // 定义一个计数器,标签为 method="getUserById" this.userQueryCounter = Counter.builder("user.query.requests") .tag("method", "getUserById") .description("The number of user query requests") .register(registry); // 定义一个计时器 this.userQueryTimer = Timer.builder("user.query.duration") .tag("method", "getUserById") .description("Time taken to handle user query") .register(registry); } @GetMapping("/{id}") public ResponseEntity<User> getUserById(@PathVariable Long id) { // 每次请求,计数器+1 userQueryCounter.increment(); // 使用计时器记录方法执行时间 return userQueryTimer.record(() -> { // 模拟业务逻辑 try { Thread.sleep(new Random().nextInt(100)); // 随机延迟模拟处理时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } User user = userService.findById(id); return ResponseEntity.ok(user); }); } }5.4 配置日志关联(MDC)为了将链路追踪ID(TraceID)自动打入日志,方便在 Loki 中通过 TraceID 查询相关日志,我们需要配置日志模式。
# logback-spring.xml 或 application.yml 中的日志配置 logging: pattern: level: "%5p [${spring.application.name:},%X{trace_id:-},%X{span_id:-}]" # 这个模式会在日志中输出应用名、TraceID和SpanID。启动应用后,访问http://localhost:8080/actuator/prometheus,你应该能看到类似下面的指标输出:
# HELP user_query_requests_total The number of user query requests # TYPE user_query_requests_total counter user_query_requests_total{application="user-service", method="getUserById",} 42.0 # HELP user_query_duration_seconds Time taken to handle user query # TYPE user_query_duration_seconds histogram user_query_duration_seconds_bucket{application="user-service", method="getUserById", le="0.005",} 10.0 user_query_duration_seconds_bucket{application="user-service", method="getUserById", le="0.01",} 35.0 ...6. 运行结果与效果验证
6.1 验证指标采集
- 访问 Prometheus UI (
http://localhost:9090)。 - 在 Graph 页面的查询框中输入
user_query_requests_total并执行。 - 你应该能看到一条名为
user_query_requests_total{application="user-service", ...}的指标曲线。访问几次你的应用接口,曲线会随之上升。
6.2 配置 Grafana 看板
- 登录 Grafana (
http://localhost:3000)。 - 点击
Configuration->Data Sources->Add data source,选择Prometheus。 - URL 填写
http://prometheus:9090,点击Save & Test,应显示Data source is working。 - 点击
Create->Dashboard->Add new panel。 - 在 Query 编辑框中,输入 PromQL 查询语句,例如:
- 请求率:
rate(user_query_requests_total[5m]) - P95 响应时间:
histogram_quantile(0.95, rate(user_query_duration_seconds_bucket[5m]))
- 请求率:
- 配置图表标题、坐标轴等,点击
Apply保存面板。
6.3 验证链路追踪
- 通过 Jaeger UI (
http://localhost:16686) 查看追踪数据。 - 在 Service 下拉框中,你应该能看到
user-service。 - 点击
Find Traces,会列出最近的请求追踪。点击任意一条,可以看到该请求的完整调用链、各阶段的耗时以及携带的标签(如 HTTP 方法、路径、状态码)。
6.4 验证日志关联
- 在 Grafana 中再添加一个
Loki数据源,URL 为http://loki:3100。 - 新建一个面板,数据源选择 Loki,查询语句可以输入
{job="varlogs"} |= "trace_id"来查找包含 trace_id 的日志。 - 更强大的用法是,在 Jaeger 的 Trace 详情页,如果日志中正确打印了 TraceID,你可以直接点击一个按钮(如果配置了 Grafana-Loki 数据源链接)跳转到 Loki,并自动过滤出该 Trace 的所有相关日志。
7. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
Prometheus 抓取目标状态为DOWN | 网络不通、目标应用未启动、端点路径错误 | 1. 在 Prometheus 容器内curl目标端点。2. 检查 Prometheus 配置文件的 targets地址和端口。3. 检查目标应用是否暴露了 /actuator/prometheus端点。 | 确保网络互通,修正配置,检查应用健康状态。 |
| Grafana 中查询不到数据 | 数据源配置错误、PromQL 写错、指标名称不对 | 1. 在 Grafana 数据源配置页点击Save & Test。2. 前往 Prometheus UI 的 Graph页,用相同 PromQL 查询,验证是否有数据。3. 在 Prometheus 的 Status->Targets页查看抓取状态。 | 修正数据源 URL 或 PromQL,确保 Prometheus 正常抓取。 |
| Jaeger 中看不到追踪数据 | 应用未集成 SDK、采样率过低、导出地址错误 | 1. 检查应用日志,看是否有 OpenTelemetry 相关的错误。 2. 确认 otel.traces.sampler.probability配置大于0。3. 确认 otel.exporter.otlp.endpoint指向正确的 Jaeger 地址。 | 添加依赖,调整配置,检查网络连通性。 |
| 自定义指标在 Prometheus 中看不到 | 指标名称不符合规范、注册方式错误 | 1. 访问应用的/actuator/prometheus端点,确认指标已暴露。2. 检查指标名称是否包含非法字符(只允许 [a-zA-Z_:][a-zA-Z0-9_:]*)。 | 使用 Micrometer 的 API 规范注册指标。 |
| 告警无法触发或无法送达 | 告警规则表达式条件永远不满足、Alertmanager 未配置或路由错误 | 1. 在 Prometheus 的Alerts页查看告警规则状态。2. 检查 Alertmanager 的日志和配置( route,receivers)。3. 测试告警的静默(Silence)功能是否开启。 | 调试 PromQL,修正 Alertmanager 配置,检查接收端(如钉钉机器人)配置。 |
8. 最佳实践与工程建议
1. 指标设计遵循“USE”和“RED”方法
- USE(Utilization, Saturation, Errors):适用于基础设施(如CPU、内存、磁盘)。关注使用率、饱和度和错误数。
- RED(Rate, Errors, Duration):适用于服务(如微服务、API)。关注请求速率、错误率和耗时。 为你的核心服务定义清晰的 RED 指标看板,是监控的起点。
2. 告警分级与降噪
- P0(致命):影响核心业务,需立即响应。如:核心接口成功率<99%,数据库主节点宕机。
- P1(严重):影响部分用户或功能,需尽快处理。如:非核心接口成功率<95%,响应时间P99>2s。
- P2(警告):潜在风险或需要关注。如:磁盘使用率>80%,内存使用率持续增长。 为不同级别配置不同的通知渠道和响应SLA。避免“告警疲劳”,确保每一条告警都是可行动的。
3. 建立“黄金信号”看板为每个微服务创建一个统一的概览看板,至少包含:
- 请求量(QPS/RPS)趋势图
- 错误率(4xx, 5xx)趋势图
- 响应时间(平均,P50, P90, P99)趋势图
- 服务依赖健康状态(上游/下游) 这个看板应作为团队每日站会或线上值班的第一屏。
4. 追踪与日志的协同
- 强制在日志中注入 TraceID 和 SpanID。这是打通追踪和日志的桥梁。
- 在 Jaeger/Grafana Tempo 中配置 Loki 数据源链接,实现从 Trace 一键跳转到关联日志。
- 结构化日志(JSON格式)比纯文本日志更利于分析和过滤。
5. 生产环境部署考量
- Prometheus:考虑使用 Thanos 或 Cortex 实现长期存储和高可用。
- 存储:评估数据保留周期,监控存储容量。指标数据通常可聚合后长期保存,原始链路数据保留期较短(如7天)。
- 安全:为监控组件(尤其是Grafana、Alertmanager)配置认证和授权。避免将监控端点暴露在公网。
- 资源隔离:为监控栈单独规划服务器或K8s命名空间,避免与业务争抢资源。
9. 总结:从“拥有工具”到“善用数据”
搭建一套包含 Prometheus、Grafana、Jaeger、Loki 的监控栈,只是“懂表”的第一步,相当于你拥有了一个装满名表的表盒。真正的功力,体现在你如何解读这些“表盘”上的数据。
- 初级阶段:看仪表盘,知道系统现在是否健康。
- 中级阶段:通过对比历史曲线、关联不同指标,能判断问题的类型和影响范围。
- 高级阶段:在故障发生前,通过趋势预测风险(如容量预警);在故障发生时,能通过预设的关联分析(指标+日志+追踪)在1分钟内定位到代码行或配置项。
建议你以本文的本地沙箱为起点,将这套模式逐步推广到你的测试和生产环境。先从最重要的一个服务开始,定义好它的 RED 指标,配置好关键告警,打通日志和追踪。当你和你的团队习惯了基于数据做决策,而不是凭感觉猜问题时,你会发现自己对系统的掌控力达到了一个全新的层次。
技术人的“腕表”,不是为了炫耀,而是为了在关键时刻,能精准、冷静地诊断问题。现在,你的表盒已经备好,是时候开始你的“读表”训练了。