日志能够还原具体事件,但逐行阅读日志很难回答「最近五分钟错误率是否升高」或「哪个接口的延迟正在恶化」。Actuator 已经引入 Micrometer,它会为 Web、JVM、连接池等组件收集可聚合指标,也能把一次操作表示为 Observation 并连接到链路追踪。
本章先区分日志、指标和 Trace,再读取 Spring Boot 自动采集的数据,为心跳检查增加自定义 Observation,最后说明 Prometheus 导出、OpenTelemetry Trace、采样和标签基数。
1. 三类信号
可观测性是从系统外部推断内部状态的能力,常用信号有三类:
| 信号 | 数据形态 | 适合回答的问题 |
|---|---|---|
| 日志 | 带时间和上下文的离散事件 | 某次失败具体发生了什么 |
| 指标 | 按名称与标签聚合的数值序列 | 请求率、错误率和延迟趋势如何 |
| Trace | 一次调用中的 Span 树 | 请求经过哪些组件,时间花在哪里 |
它们不是三份重复数据。一次请求可以产生一条 Trace、多个指标样本和若干日志:
1HTTP 请求2-> Trace:Controller -> Service -> PostgreSQL3-> Metrics:请求次数、错误次数、耗时分布4-> Logs:业务事件和异常详情
指标适合告警发现异常,Trace 帮助缩小慢点范围,日志再提供具体错误上下文。只有日志而没有聚合指标,通常要等用户报告后才开始排查;只有指标而没有日志和 Trace,又难以解释异常原因。
2. 内置指标
Actuator 自动配置 MeterRegistry,并为 classpath 中支持的组件注册指标。当前项目常见的名称包括:
| 指标前缀 | 内容 |
|---|---|
http.server.requests | HTTP 请求次数、状态和耗时 |
jvm.* | 堆内存、GC、线程和类加载 |
process.* | 进程 CPU 与运行时间 |
system.* | 系统 CPU 等运行环境数据 |
jdbc.connections.* | JDBC 连接状态 |
hikaricp.* | HikariCP 活跃、空闲与等待连接 |
先把 metrics 加入已有 Actuator 暴露白名单。下面只展示发生变化的配置路径,应合并进现有 management,不要在同一文件中重复创建根键:
1management:2endpoints:3web:4exposure:5include: "health,info,metrics"
查看已注册指标和某一项详情:
1curl -u monitor:change-me http://localhost:8080/actuator/metrics2curl -u monitor:change-me http://localhost:8080/actuator/metrics/http.server.requests
第二个响应会列出计数、总耗时、最大耗时和可用标签。还可以按标签进一步筛选:
1curl -u monitor:change-me \2'http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/api/v1/health&tag=status:200'
metrics 端点适合确认本地究竟注册了哪些指标,不是生产时序数据库。应用重启后内存数据会消失,多实例数据也不会自动聚合,生产环境需要把指标交给外部监控系统。
3. 自定义 Observation
Spring Boot 使用 Micrometer Observation 统一描述一段被观察的操作。给 HealthService 注入 ObservationRegistry:
01import io.micrometer.observation.Observation;02import io.micrometer.observation.ObservationRegistry;0304private final ObservationRegistry observationRegistry;0506public HealthService(07HealthProperties properties,08HealthStatusRepository repository,09HealthCheckHistoryRepository historyRepository,10ObservationRegistry observationRegistry11) {12this.properties = properties;13this.repository = repository;14this.historyRepository = historyRepository;15this.observationRegistry = observationRegistry;16}
两个公开检查入口都进入同一个私有观察方法,先得到规范化服务名,再观察真正的业务操作:
01@Transactional02public HealthStatus check() {03return observeCheck(properties.service());04}0506@Transactional07public HealthStatus check(String service) {08return observeCheck(service);09}1011private HealthStatus observeCheck(String service) {12String normalizedService = normalizeService(service);1314return Observation.createNotStarted("health.check", observationRegistry)15.lowCardinalityKeyValue("operation", "check")16.highCardinalityKeyValue("health.service", normalizedService)17.observe(() -> performCheck(normalizedService));18}1920private HealthStatus performCheck(String service) {21HealthStatus status = new HealthStatus(properties.status(), service);22HealthStatus savedStatus = repository.save(status);23historyRepository.append(savedStatus);24return savedStatus;25}
observe() 会启动 Observation,执行 Lambda,并在正常返回或抛出异常时正确结束。已有 @Transactional 仍然位于两个公开方法上;Controller 通过代理调用后先建立事务,再进入方法中的 Observation,因此两个入口都会记录相同名称和标签,当前耗时包含两次 Repository 操作。
operation=check 的值集合很小,可以成为指标标签。服务名称可能由请求输入并持续增加,只作为高基数 Trace 属性,不进入普通指标标签。如果把用户 ID、请求 ID、完整 URL 或任意服务名放进指标标签,每一种值组合都会产生新的时间序列,最终消耗大量内存与存储。
当指标与追踪处理器都存在时,同一个 Observation 可以同时产生指标和 Span。业务代码只描述被观察操作,不需要分别维护两套计时逻辑。
4. Prometheus
Prometheus 使用拉取方式定期读取每个应用实例。添加 Micrometer Prometheus Registry:
1<dependency>2<groupId>io.micrometer</groupId>3<artifactId>micrometer-registry-prometheus</artifactId>4</dependency>
再把 prometheus 加入生产 Profile 的暴露白名单。它与公共配置会按 Profile 规则合并,最终值需要包含前面仍要访问的端点:
1management:2endpoints:3web:4exposure:5include: "health,info,metrics,prometheus"
启动后确认抓取内容:
1curl -u monitor:change-me http://localhost:8080/actuator/prometheus
Prometheus 配置需要指向实际实例和管理端口:
1scrape_configs:2- job_name: spring3metrics_path: /actuator/prometheus4basic_auth:5username: monitor6password_file: /run/secrets/spring-monitor-password7static_configs:8- targets:9- localhost:8080
生产服务发现通常不会手写静态地址,但拉取路径和目标端口含义相同。这里从 Secret 文件读取监控密码,不把它直接写入 Prometheus 配置;应用侧凭据也应来自同一套受控 Secret 分发。管理端点仍需要网络访问控制,不能因为 Prometheus 要抓取就向公网开放所有 Actuator 端点。
5. Trace
Trace 由多个 Span 组成,每个 Span 表示一次有开始、结束和上下文的操作。HTTP 请求是根调用时,Spring MVC 会产生服务器 Span;自定义 health.check Observation 可以成为它的子 Span,受支持的数据库或客户端组件还会继续形成下游 Span。
使用 OpenTelemetry 并通过 OTLP 导出 Trace,可以添加 Spring Boot 4.1 提供的 Starter:
1<dependency>2<groupId>org.springframework.boot</groupId>3<artifactId>spring-boot-starter-opentelemetry</artifactId>4</dependency>
具体 OTLP 地址和认证信息应通过 management.opentelemetry.tracing.export.otlp.* 配置交给部署环境。采样比例可以单独设置:
1management:2tracing:3sampling:4probability: "${TRACING_SAMPLING_PROBABILITY:0.1}"
默认只采样部分请求可以限制生产开销。排查本地案例时可以临时设为 1.0,生产环境则应结合请求量、存储成本和错误保留策略调整,不能默认全量采集。
启用 Micrometer Tracing 后,Spring Boot 日志相关 ID 会包含 Trace 上下文。上一章的 requestId 仍可作为客户端可见的请求编号;Trace ID 则由追踪系统生成,用于连接 Span。两者可以同时存在,但不要在代码中把它们假设为同一个值。
以后调用其他 HTTP 服务时,应使用 Spring Boot 自动配置的 RestClient.Builder、WebClient.Builder 或 RestTemplateBuilder 创建客户端,自动传播 Trace 上下文。直接手动创建客户端可能绕过框架的传播定制。
6. 指标设计
仪表盘和告警应先围绕用户能感受到的信号,而不是展示所有可用数字。对于当前接口,可以从四组指标开始:
| 目标 | 指标 |
|---|---|
| 流量 | 每秒请求数、心跳检查次数 |
| 错误 | 5xx 比例、业务失败次数 |
| 延迟 | HTTP 与 health.check 的分位数 |
| 饱和度 | 活跃连接、等待连接、线程和堆内存 |
平均耗时会掩盖少量极慢请求,延迟告警通常还要观察分位数或直方图。错误率要与请求量一起判断,低流量下一个错误可能产生很高百分比。
指标标签必须来自有界集合,例如 HTTP 方法、归一化状态码和固定操作类型。日志与 Trace 可以承载请求 ID、异常消息和具体服务名称,但仍要考虑敏感数据与保留成本。
可观测性代码已经建立运行反馈,但应用还只以本地 JAR 运行。下一篇把同一个构建产物放进 Docker 镜像,说明构建阶段、运行阶段、镜像分层、非 root 用户和外部配置怎样配合。