ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从监控到可观测性:Metrics、Logs与Traces三支柱实战指南

2026/8/31 8:20:06 拓冰建站 浏览量
从监控到可观测性:Metrics、Logs与Traces三支柱实战指南 你有没有经历过这种排查之夜线上告警响了你打开监控面板CPU 才 60%内存曲线平稳平均响应时间也没有明显波动但业务方就是反馈“订单提交失败”。你盯着满屏的绿色曲线找不到任何异常最后只能靠猜或者一台台机器手动翻日志。如果只是“看指标”上面的问题很难解决。因为 CPU、内存、磁盘这些指标回答的是“系统有没有出问题”而不是“系统为什么出问题”。前者是监控Monitor后者才是可观测性Observability。这篇文章不打算把“可观测性”讲成一本百科书而是想从一个开发者的实际视角出发理清楚三件事可观测性和传统监控的本质区别是什么Metrics、Logs、Traces 这三根支柱分别解决什么问题以及从零开始怎么在你的项目里把监控升级为可观测性。文章后面会给出可以照着做的代码和配置示例也会聊到两个容易被忽略的场景设备端的 OSD 菜单监控以及 UE 引擎中的硬件监控插件。它们看起来和云原生没什么关系但本质上都是可观测性在不同终端上的落地形态。1. 为什么 Monitor 不等于可观测性“可观测性”这个词来自控制理论指的是系统内部状态可以从外部输出被推断出来的程度。放到软件工程里翻译成大白话就是当系统出问题时你手里的工具能不能帮你回答“为什么会这样”。传统 Monitor 的核心思路是“预设指标持续盯盘”。你提前想好哪些指标重要CPU、内存、磁盘、网络、QPS、错误率、延迟然后设置阈值超过就告警。这套模式在单体应用和规模不大的微服务时代非常有效因为系统边界清楚故障模式相对固定5 个指标基本能覆盖 80% 的情况。但云原生时代暴露了一个问题系统变成了网状结构。一个请求从网关进来经过认证服务、订单服务、支付服务还要读 Redis、写 MySQL、发 MQ 消息任何一环出了问题最终都会表现为“用户操作失败”但传统监控根本不知道问题出在哪一环。这时候你需要的不是更多曲线而是能够沿着一次请求的链路把每一跳的日志、指标、调用关系串起来看的能力。判断标准可以很简单Monitor 回答系统挂了吗Observable 回答系统为什么挂挂在哪一步影响范围多大所以可观测性不是监控的替代品而是监控的升级形态。它不抛弃 CPU、内存这些基础指标而是把这些指标和日志、链路结合起来让“看数据”变成“查答案”。1.1 一个直观的对比对比维度传统 Monitor可观测性核心问题系统是否正常系统为什么异常数据来源预先定义的指标指标、日志、链路全量数据排查方式看面板、找异常曲线沿链路逐步下钻未知问题基本无法发现可以通过探索发现代表技术Zabbix、传统监控脚本Prometheus / Grafana / OpenTelemetry / Loki / Tempo这里没有贬低 Monitor 的意思。恰恰相反Monitor 是可观测性的地基没有监控系统的数据采集可观测性就是空谈。正确理解是先用监控保证“发现问题”再引入日志、链路和更丰富的指标让系统具备“解释问题”的能力。2. 可观测性的三大支柱Metrics、Logs、Traces可观测性最常见的落地框架是“三支柱”Three Pillars指标、日志、链路追踪。这三个词很多人天天说但真到了实际排查里依然容易用错。下面逐个讲清楚并用一个开发场景来对比。2.1 Metrics指标系统的体检报告Metrics 是带时间戳的数值数据通常按固定间隔采集比如每秒请求数、当前内存使用量、错误率。它是可观测性里最接近传统监控的部分。指标和处理日志最大的不同在于指标是聚合好的体积小适合长期保存和快速查询。你问它“过去一小时 QPS 是多少”它直接给你一条折线。但指标有个天然缺陷它只能告诉你“发生了什么”无法告诉你“为什么”。QPS 掉了是上游不调了还是下游超时导致线程阻塞这些信息指标表达不了。适合用指标解决的场景容量规划、趋势告警、资源水位分析。2.2 Logs日志系统的手术记录日志是开发者最熟悉的数据形态。它记录一条条离散的事件比如“用户 ID 12345 下单失败原因是库存不足”。日志的优势是信息密度极高任何代码里显式打印的内容都能进日志。问题在于日志是分散的微服务架构下一个用户请求会生成几十上百条日志分散在不同机器的文件里。没有工具把它们关联起来你只能靠 grep 碰运气。所以日志要想发挥作用必须做两件事结构化JSON 格式而不是一长串字符串和带上关联 IDRequestId / TraceId。否则排查效率极低。2.3 Traces链路追踪系统的 GPS 导航Traces 记录一次请求从入口到每一个内部调用的完整路径包括每一层的耗时、状态、调用参数。它和日志的区别在于日志是“按事件记录”Traces 是“按请求串起来”。一次订单请求日志可能产生 10 条Traces 则是把网关、订单服务、支付服务、数据库调用全部串成一条链条每个节点标注耗时。Traces 真正解决了微服务排查的核心痛点那个拖垮整个链路的下游服务到底是谁。支柱数据形态回答什么问题典型工具Metrics聚合数值发生了什么PrometheusLogs离散事件为什么发生ELK / LokiTraces调用链路在哪一环发生Jaeger / Tempo实际工程里三个支柱必须配合使用缺一个都会让排查变得困难。只有指标没有链路你只知道系统慢了不知道慢在哪只有日志没有指标系统扩容时你连基本的水位都说不清。3. 从监控到可观测性的演进路径理解了三大支柱之后再看工具链就清楚了。可观测性不是某一个工具而是一条数据流水线采集、传输、存储、查询、展示、告警。早期方案是一套工具管一件事指标用 Zabbix 或 Prometheus日志用 ELK链路用 SkyWalking 或 Zipkin这套组合能用但维护成本高各系统数据割裂。排查一个问题时要在多个平台之间来回切换而且三个系统的数据格式不统一很难做关联分析。新一代方案的核心变化是统一数据采集层也就是 OpenTelemetry简称 OTel。OTel 做的事可以理解为提供一套统一的 SDK 和 Agent把你的应用产生的 Metrics、Logs、Traces 都采集出来用标准格式发给后端。后端仍然可以接 Prometheus、Jaeger、Loki但前端的接入方式统一了。实际项目的演进路径通常分为三个阶段第一阶段基础监控。把 CPU、内存、JVM、数据库连接池这些基础指标接入 Prometheus配好 Grafana 面板和告警。第二阶段业务可观测。在代码里埋点输出结构化日志接入链路追踪让每一次请求都能被跟踪。第三阶段统一平台。引入 OTel 统一采集建立指标、日志、链路之间的关联让排查从“多个系统切换”变成“从一个请求开始下钻”。对一个中小团队来说建议按这个顺序走不要一开始就上一整套大平台。先把 Prometheus Grafana 跑起来再逐服务接入日志和链路效果最好。4. 容易被忽略的两类 Monitor 场景聊完云原生场景回到文章开头提到的两个热词MStar 方案的 OSD 菜单制作以及 UE 插件的 Hardware Monitor。它们恰好说明一件事监控和可观测性不只是后端的课题设备端和客户端同样在反复实践。4.1 设备端OSD 菜单也是一种监控展示MStar晨星半导体是电视、机顶盒、车载显示等设备主控芯片里非常常见的方案。很多做嵌入式显示开发的人会接触到 OSDOn Screen Display菜单制作也就是屏幕上叠加显示的那些菜单音量条、输入源切换、亮度调节、信号状态。OSD 的特殊之处在于它是在显示画面上叠加一层 UI不影响主画面内容。硬件上一般通过专门的 Blending混音叠加模块完成软件上则是把 OSD 内容渲染到独立的图层或 Framebuffer再和视频层叠加输出。为什么说 OSD 和可观测性有关因为设备端也需要“监控内部状态并展示给用户”。比如电视机的信号锁定状态、当前分辨率、HDMI 接入状态、芯片温度、音量输出值这些信息最终都要通过 OSD 呈现出来。从开发角度看OSD 菜单本质上就是一个“硬件状态的可视化面板”只是它跑在嵌入式 UI 系统里而不是 Web 页面上。在做 OSD 菜单时有一个容易踩坑的点显示通道的优先级和刷新机制。OSD 层必须在视频层之上否则菜单会被画面盖住同时因为嵌入式系统性能有限OSD 的刷新率通常不高如果状态值变化非常频繁比如音量连续增减就需要做局部刷新而不要整屏重绘否则会明显掉帧。这类场景给我们的启发是监控的最后一公里不只是“后端面板展示”还包括设备端直接可见的交互界面。在设计嵌入式系统时建议一开始就保留一个“状态 OSD 页”把关键信号和温度显示出来调试和量产阶段都能省大量时间。4.2 客户端UE 引擎中的 Hardware Monitor 插件另一个相关场景是 Unreal EngineUE中的硬件监控插件。做游戏或实时渲染应用时开发者经常需要实时查看当前机器的 CPU、GPU、内存占用、帧率、温度等信息用来判断性能瓶颈。这类插件通常分两层底层读取系统硬件数据比如系统内存 API、GPU 性能计数器、CPU 使用率上层在编辑器中或游戏运行界面上展示。在 UE 中做硬件监控最常被问到的问题是编辑器里数值正常打包后数值不对。原因往往是打包版本默认去掉了调试接口或者当前平台对硬件计数器的访问权限有限制。打包测试前需要确认插件在 Shipping 或 Development 包中是否保持启用并且检查目标平台的具体权限要求。从可观测性角度看游戏客户端的硬件监控也是一种轻量级 APM。它和后端监控的目标一致在性能出问题之前发现问题在问题出现后快速定位。不同的是客户端的运行环境不受控制硬件监控数据通常只用于开发自测或用户反馈辅助分析很少全量上报。5. 可观测性环境搭建与基础配置下面进入可以动手操作的部分。以一个典型的 Web 服务为例搭建一套最小可用的可观测性体系Prometheus 负责采集指标Grafana 负责展示和告警OpenTelemetry 负责链路追踪和日志关联业务服务暴露 /metrics 端点、输出结构化日志、生成 TraceId环境要求Linux 或 macOS 均可需要 Docker 和 Docker ComposeJava 8 以上示例使用 Java 11Node.js 16 以上用于第二个示例。版本号没有写死实际部署时以官方最新稳定版为准本文演示的是通用接入思路。5.1 用 Docker Compose 启动监控后端先创建一个监控栈的 docker-compose.ymlversion: 3.8 services: prometheus: image: prom/prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml grafana: image: grafana/grafana ports: - 3000:3000 environment: - GF_SECURITY_ADMIN_PASSWORDadmin volumes: - grafana-data:/var/lib/grafana tempo: image: grafana/tempo ports: - 3200:3200 - 4317:4317 volumes: grafana-data:这里加了一个 Tempo用来接收 OpenTelemetry 的 Trace 数据。它的端口 4317 是 OTLP gRPC 接收端口Java 应用的 Agent 默认往这里上报。Prometheus 的抓取配置放在同目录的 prometheus.yml 里global: scrape_interval: 15s scrape_configs: - job_name: demo-service metrics_path: /actuator/prometheus static_configs: - targets: [host.docker.internal:8080]说明如果你的服务跑在 Docker 外面targets 要写 host.docker.internal否则容器访问不到宿主机。如果服务也在容器里直接写服务名和端口即可。5.2 Spring Boot 服务暴露指标假设你有一个 Spring Boot 项目接入 Prometheus 指标最直接的方式是添加 Actuator 和 Micrometer 依赖。在 pom.xml 中添加dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency然后在 application.yml 中暴露 Prometheus 端点management: endpoints: web: exposure: include: health,info,prometheus endpoint: prometheus: enabled: true启动项目后访问 http://localhost:8080/actuator/prometheus会看到类似下面的输出# HELP jvm_memory_used_bytes Used heap bytes # TYPE jvm_memory_used_bytes gauge jvm_memory_used_bytes{areaheap,idPS Old Gen} 1.23456789E8 jvm_memory_used_bytes{areaheap,idPS Survivor Space} 1.234567E7这些就是 JVM 的实时内存指标Prometheus 会每隔 15 秒拉取一次。如果这个端点打不开最常见的原因是端点没有暴露检查 application.yml 中 management.endpoints.web.exposure.include 是否包含 prometheus。5.3 Node.js 服务输出自定义业务指标如果项目不是 Java而是 Node.js可以用 prom-client 库输出指标。先安装依赖npm install prom-client express然后创建一个最小服务// 文件路径app.js const express require(express); const client require(prom-client); const app express(); // 创建自定义计数器 const orderCounter new client.Counter({ name: app_orders_total, help: Total number of orders created }); // 创建自定义直方图 const orderDuration new client.Histogram({ name: app_order_duration_seconds, help: Order processing duration in seconds, buckets: [0.1, 0.3, 0.5, 1, 2, 5] }); app.get(/metrics, async (req, res) { res.set(Content-Type, client.register.contentType); res.end(await client.register.metrics()); }); app.post(/order, (req, res) { const start process.hrtime(); // 模拟业务处理耗时 const duration Math.random() * 2; setTimeout(() { orderCounter.inc(); orderDuration.observe(duration); res.json({ code: 0, message: success }); }, duration * 1000); }); app.listen(8080, () { console.log(demo service listening on 8080); });这个示例里包含了两个核心埋点计数器Counter用来统计订单总量直方图Histogram用来记录订单处理耗时分布。在 Prometheus 面板里你可以用 rate(app_orders_total[5m]) 计算每秒订单速率用 histogram_quantile(0.95, sum(rate(app_order_duration_seconds_bucket[5m])) by (le)) 计算 P95 耗时。5.4 接入 OpenTelemetry 链路追踪链路追踪接入比指标更容易因为 OTel 提供了 Java Agent不需要改代码。只需要下载 otel-javaagent.jar然后在启动命令里加上参数java -javaagent:opentelemetry-javaagent.jar \ -Dotel.service.namedemo-service \ -Dotel.traces.exporterotlp \ -Dotel.exporter.otlp.endpointhttp://localhost:4317 \ -jar demo-service.jar启动后Agent 会自动拦截 HTTP 请求、数据库调用、消息队列发送等常见操作并生成 Trace 数据上报到 Tempo。在 Grafana 里配置 Tempo 数据源后就可以按服务名和标签查询链路。如果不想用 Java Agent也可以用 SDK 手动埋点但一般情况下推荐优先使用 Agent接入成本低覆盖范围广。只有对自定义异步逻辑或消息队列的上下文传递有特殊要求时才需要手动埋点。6. 核心链路构建指标、日志、链路如何关联采集到数据只是第一步真正能提升排查效率的是把三类数据关联起来。没有关联的三支柱依然是三个数据孤岛。6.1 通过 TraceId 关联日志最经典的做法是在日志中打印 TraceId 和 SpanId。OTel Java Agent 会自动在日志上下文里注入这些字段你只需要在日志配置中输出出来。以 Logback 为例在 logback-spring.xml 中把 traceId 和 spanId 加到 patternappender nameCONSOLE classch.qos.logback.core.ConsoleAppender encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{trace_id}] [%X{span_id}] - %msg%n/pattern /encoder /appender这样每条日志都会带上 trace_id 和 span_id。排查问题时从异常日志里拿到 trace_id再把它输入到 Tempo 查询就能看到这个事务对应的完整调用链路包括每一步的耗时和状态。6.2 通过标签关联指标和链路OTel 里的资源属性Resource Attributes和指标标签是关联的关键。比如在 Spring Boot 中Agent 会自动注入 service.name、host.name 等资源属性。在实际项目中更推荐在业务指标上也加上环境标签比如 env、instance、version。这样在 Grafana 面板上可以快速区分生产环境和预发布环境也能按服务版本对比指标差异。一个常见的工程痛点是指标和链路里的服务名不一致。比如 Prometheus 的 job_name 叫 demo-serviceOTel 的 service.name 叫 order-service两者对不上就没法在面板之间跳转。建议提前约定命名规则让 Prometheus job、OTel service.name、日志中的 app 字段保持一致从源头避免这个问题。6.3 用 Grafana 串联监控视图Grafana 支持把 Prometheus 指标和 Tempo 链路放在同一个 Dashboard 里。最实用的场景是在指标面板上点某个服务直接筛选出该服务最近的高延迟 Trace。配置方式不复杂在 Grafana 中添加 Prometheus 数据源地址填 http://prometheus:9090添加 Tempo 数据源地址填 http://tempo:3200在 Panel 的 数据源 选项中添加 Trace to logs 或 关联数据源这样的一条完整排查路径是Grafana 面板显示订单接口 P95 延迟升高点击对应时间点的散点图直接跳到 Tempo 查看慢 Trace在慢 Trace 里找到耗时最长的 Span点击该 Span 关联的日志查看具体错误信息整个过程从“发现异常”到“定位根因”理论上只需要几次点击。7. 运行结果与效果验证搭建完之后怎么判断这套体系真正生效了下面给出一套可以照着做的验证流程。7.1 验证 Prometheus 指标采集启动 Spring Boot 服务后访问 Prometheus 的查询页面http://localhost:9090在输入框里执行jvm_memory_used_bytes{areaheap}如果返回了数值列表说明 Prometheus 已经成功抓取到 JVM 指标。如果一直查不到数据先检查 prometheus.yml 的 targets 地址是否正确再到 http://localhost:9090/targets 看抓取任务的状态是 UP 还是 DOWN。7.2 验证 OTel 链路数据请求一次业务接口比如curl -X POST http://localhost:8080/order然后打开 Grafana进入 Tempo 数据源的 Explore 页面按服务名 demo-service 搜索。如果能看到一条或多条 Trace说明链路采集成功。注意如果刚刚接入Trace 可能需要几十秒才会在 Tempo 中可查询因为 Agent 是异步上报的不要刚请求完就去刷新页面。7.3 验证日志关联确认日志中是否输出了 trace_id 和 span_iddocker logs demo-service --tail 50 | grep trace_id如果日志里没有这两个字段检查 logback-spring.xml 的 pattern 是否配置正确以及 Agent 是否成功注入上下文。这里有个很容易忽略的点OTel Java Agent 默认在日志上下文里注入的是 trace_id 和 span_id而不是 traceId注意大小写。7.4 模拟故障验证排查链路为了测试整套体系的真实效果可以做一个最简单的实验把 Node.js 服务里的模拟业务耗时改成固定值 5 秒然后请求几次接口再看 Grafana 的延迟面板确认能够明显看到上涨。这一步的关键在于验证链路是否真的能帮你定位问题。如果延迟面板上涨后你能通过 Trace 很快找到耗时集中在哪个服务、哪个操作说明可观测性体系已经建立了闭环如果只能看到延迟上涨却无法下钻说明链路接入还有缺口。8. 常见问题与排查思路实践过程中最容易出问题的是接入环节而不是使用环节。下表列出了几个高频问题。问题现象可能原因排查方式解决方案Prometheus 页面显示 Target DOWNtargets 地址写错或网络不通访问 /targets 页查看错误信息如果服务在宿主机写 host.docker.internal/actuator/prometheus 返回 404端点未暴露检查 management.endpoints.web.exposure.include添加 prometheus 到 include 列表Grafana 里查不到 TraceOTel Agent 未启动或上报地址错误查看服务启动日志中 Agent 相关输出确认 javaagent 路径和 endpoint 配置正确日志里没有 trace_idlog pattern 未包含上下文字段检查 logback 配置在 pattern 中添加 [%X{trace_id}] [%X{span_id}]指标中有重复标签命名不规范多个来源重复上报检查各采集任务的 labels统一标签规范避免 job 和 service.name 不一致Grafana 面板时间不同步容器和宿主机时钟偏差比较容器与本机时间为容器配置时区并确保 NTP 同步如果启动 OTel Agent 时报错 “Could not find or load main class”大概率是 java -javaagent 参数后面跟了 -jar但顺序不对。正确写法是 java -javaagent:xxx.jar -jar app.jarAgent 参数要放在 -jar 前面。9. 最佳实践与工程建议可观测性建设是个长期工程比选型更重要的事情是制定一套稳定的规范和流程。这里给出几条经过实际项目验证的建议。9.1 统一命名规范和标签规范从接入第一天就统一服务命名。建议格式为 应用名-环境-实例比如 order-service-prod-01。不要在指标里塞太多无区分的 tag否则高基数问题会让 Prometheus 内存爆炸。高基数指的是一组标签组合数量非常多的场景比如把用户 ID 直接当标签这种设计要尽量避免。9.2 日志必须结构化不建议再输出无格式的字符串日志。至少使用 JSON 格式包含 level、timestamp、logger、message、trace_id、userId 等常用字段。结构化日志的查询效率远高于文本 grep而且能直接被日志平台解析。9.3 链路采样策略要提前规划全量采样在流量大的系统里成本很高。更务实的做法是默认按照 10% 左右的比例采样同时对慢请求和错误请求做到全量采样。OTel 支持基于尾部的采样策略项目上可以先从头部采样开始跑起来后续再逐步精细化。9.4 告警要分级不要什么都报警告警疲劳会毁掉一套监控体系。建议把告警分为三级P0 级业务不可用比如错误率超过 5% 持续 5 分钟P1 级系统受影响但可用比如 P95 延迟超过阈值P2 级容量风险比如 CPU 持续超过 80%每一类告警都要有明确的负责人和响应动作不要设置“邮件通知所有人”这种没有责任人的告警。9.5 排查路径要文档化可观测性平台建好之后团队里最需要沉淀的是一份“典型故障排查手册”。比如订单超时先看哪个面板再按什么顺序查链路和日志。这份文档的价值会随着系统复杂度提升越来越大。9.6 客户端监控要区分场景回到 UE 插件和 OSD 菜单的话题客户端和嵌入式场景的监控数据不建议全部上报到服务端。原因是数据量大且多数只对当前设备有意义。更合理的做法是在本机实时展示 按需导出关键数据。也就是说性能问题先用本机面板定位只有需要跨设备对比时才上传匿名聚合数据。这样既保证了效率也避免触发数据合规问题。10. 总结与后续学习方向从标题里的“可观测性和 Monitor”出发这篇文章讲了几个核心判断Monitor 是发现“系统病了”可观测性是回答“为什么病了”Metrics、Logs、Traces 三者必须配合单靠任何一个都不足以支撑快速排查接入顺序建议先指标、再日志、最后链路不要一开始就把摊子铺得太大。如果要从零开始实践建议照着下面的路径走第一步用 Prometheus Grafana 把基础指标跑起来能用 Grafana 看图、能收告警。 第二步服务日志全部改为结构化输出并统一在日志里带上 trace_id。 第三步给关键服务接入 OpenTelemetry先把链路跑通再逐步完善采样策略和面板关联。 第四步梳理一份团队内部的排查手册把常见故障的定位路径固化下来。可观测性更大的想象空间在于 Metrics、Logs、Traces 不再是三套独立系统而是一个可以互相跳转、统一查询的数据空间。推荐继续关注 OpenTelemetry 的演进它是目前最有希望统一数据采集标准的方向。后续的实践文章里我会重点写如何在 Kafka 消息链路、多语言微服务架构里做上下文透传这两块是最容易出问题、也最值得深入的地方。