SpringNotes

第 23 章:可观测性

zjc 于 2026-01-23 发布

这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 微服务排障依赖三大支柱:日志、指标和链路追踪。日志解释单次事件细节,指标发现趋势和告警,trace 把跨服务调用串成链路。

23.1 三大支柱

支柱 数据模型 典型问题
Logging 离散事件文本/JSON 这次请求为什么失败
Metrics 时间序列数值 系统现在是否异常
Tracing span 树 延迟花在哪里
Profiling CPU/内存采样 方法级热点

不要只收集而不治理。观测数据越多,成本和噪声越高。

23.2 日志

结构化字段:

timestamp
level
service
instance
traceId
spanId
userId
path
status
cost
message

日志规范:

  1. 统一 UTF-8;
  2. 异常必须打印堆栈;
  3. 避免多行普通文本;
  4. 不打印密码和 token;
  5. 手机号、身份证脱敏;
  6. traceId 必须贯穿;
  7. 日志级别可动态调整。

JSON 输出可结合日志平台字段提取,减少二次解析成本。

23.3 Actuator

依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置:

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics
  endpoint:
    health:
      show-details: never

常用端点:

端点 用途
/actuator/health 健康状态
/actuator/metrics 指标
/actuator/prometheus 抓取
/actuator/env 环境,敏感
/actuator/beans Bean,敏感
/actuator/configprops 配置,敏感
/actuator/threaddump 线程,敏感

生产默认只暴露最小集合。

23.4 Micrometer

计数器:

@Service
public class OrderMetrics {
    private final Counter createTotal;
    private final Timer createTimer;

    public OrderMetrics(MeterRegistry registry) {
        this.createTotal = Counter.builder("order_create_total")
                .description("Order create count")
                .register(registry);
        this.createTimer = Timer.builder("order_create_seconds")
                .description("Order create latency")
                .publishPercentiles(0.5, 0.95, 0.99)
                .register(registry);
    }
}

标签使用低基数字段:

method
path template
status
service
outcome

不要把 userId、orderId、URL 原文作为 label。

23.5 Prometheus 与 Grafana

暴露端点:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

Prometheus 抓取:

scrape_configs:
  - job_name: spring-boot
    metrics_path: /actuator/prometheus
    static_configs:
      - targets: ["localhost:8080"]

核心指标:

指标 方向
http_server_requests_seconds 请求延迟
jvm_memory_used_bytes 内存
jvm_gc_pause_seconds GC
hikaricp_connections_pending 数据库等待
executor_queued 线程池积压
resilience4j_circuitbreaker_state 熔断
process_cpu_usage CPU

23.6 Micrometer Tracing

依赖:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

链路结构:

traceId = 4bf92f3577b34da6a3ce929d0e0e4736

Gateway span
  -> Order Controller span
     -> Order Service span
        -> DB span
        -> Inventory Client span

传播 Header:

traceparent: 00-traceId-spanId-flags
b3: traceId-spanId
x-trace-id

自定义 span:

@Service
public class PricingService {
    private final Tracer tracer;

    public BigDecimal calculate(Order order) {
        Span span = tracer.nextSpan().name("pricing.calculate").start();
        try (Tracer.SpanInScope scope = tracer.withSpan(span)) {
            return doCalculate(order);
        } finally {
            span.end();
        }
    }
}

23.7 OpenTelemetry

OpenTelemetry 提供语言 SDK、Collector 和协议,适合统一日志、指标、trace。

App -> OTLP -> Collector -> Backend

Collector 能做:

  1. 协议转换;
  2. 采样;
  3. 过滤脱敏;
  4. 重试缓冲;
  5. 多后端分发。

采样策略:

策略 说明
全采样 成本高,排障方便
按比例采样 常见
尾部采样 根据结果保留慢/错误请求
强制保留 关键接口和 debug 请求

错误和慢请求应优先保留。

23.8 告警设计

告警模板:

名称:
严重级别:
触发条件:
持续时间:
影响:
排查入口:
负责人:
预案:

核心告警:

指标 条件
availability 5xx 或不可用
latency P99 超目标
error rate 突增
CPU throttling 持续
memory near limit 高水位
GC P99 超阈值
DB pool pending 大于 0 持续
consumer lag 持续增长
circuit breaker OPEN

告警必须可执行,否则只适合 dashboard。

23.9 日志脱敏

错误:

log.info("login success, request={}", request);

正确:

log.info("login success, userId={}, phone={}", request.userId(), mask(request.phone()));

常见脱敏:

数据 策略
手机号 前 3 后 4
身份证 前 1 后 1
银行卡 前 4 后 4
token 只记前缀和长度
密码 完全不记录

对第三方对象输出 toString 前,要确认没有敏感字段。

本章小结

可观测性要同时建设日志、指标和 trace,并通过 traceId 将三者关联。日志要结构化并脱敏,指标要低基数,trace 要传播到数据库、消息和下游调用。告警应面向可执行动作,避免只堆 dashboard。

思考题

  1. 日志、指标、trace 分别解决什么问题?
  2. 为什么不能用 userId 做指标标签?
  3. 如何让错误请求更容易被采样保留?
  4. Actuator 哪些端点不应暴露?
  5. 如何设计一条可执行告警?