Skip to content
第 73 / 250 章后端⏱ 10 分钟阅读

第 73 章:监控与可观测性

学习目标

  • 理解 Metrics / Logs / Traces 三大可观测性支柱
  • 掌握 Spring Boot Actuator 与 Prometheus / Grafana 集成
  • 学会自定义指标与告警规则

一、什么是可观测性?

维度回答的问题工具
Metrics系统整体状态(QPS、RT、错误率)Prometheus + Grafana
Logs系统发生了什么(具体事件)ELK / Loki
Traces请求经过了哪些服务Jaeger / Zipkin / SkyWalking

二、Spring Boot Actuator

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus,env,loggers,mappings,threaddump
      base-path: /actuator
  endpoint:
    health:
      show-details: when-authorized           # 详情仅授权用户可见
    metrics:
      enabled: true
  metrics:
    tags:
      application: ${spring.application.name}  # 指标标签
    distribution:
      percentiles-histogram:
        http.server.requests: true             # 开启直方图
      percentiles:
        http.server.requests: 0.5,0.75,0.9,0.95,0.99
  prometheus:
    metrics:
      export:
        enabled: true

启动后访问:

  • /actuator/health ← 健康检查
  • /actuator/metrics ← 指标列表
  • /actuator/prometheus ← Prometheus 格式指标

三、自定义业务指标

java
@Service
@RequiredArgsConstructor
public class OrderService {

    private final Counter orderCreateCounter;          // ① 计数器
    private final Timer orderCreateTimer;              // ② 计时器
    private final MeterRegistry registry;

    public OrderService(MeterRegistry registry) {
        this.registry = registry;
        this.orderCreateCounter = Counter.builder("order.create.count")
                .description("订单创建总数")
                .tag("channel", "online")
                .register(registry);
        this.orderCreateTimer = Timer.builder("order.create.duration")
                .description("订单创建耗时")
                .publishPercentiles(0.5, 0.95, 0.99)
                .register(registry);
    }

    public Long createOrder(OrderDTO dto) {
        // ③ 计时
        return orderCreateTimer.record(() -> {
            Order order = new Order();
            orderMapper.insert(order);
            orderCreateCounter.increment();            // 计数 +1
            return order.getId();
        });
    }
}

关键指标类型

java
// ① Counter:只增不减的计数器(QPS、订单数)
Counter.builder("api.calls").register(registry).increment();

// ② Gauge:可增可减(在线人数、队列大小)
registry.gauge("active.users", onlineUsers);
// 在线人数变化时调用
Gauge.builder("active.users", onlineUsers, AtomicInteger::doubleValue).register(registry);

// ③ Timer:记录耗时和次数(接口 RT)
Timer.builder("api.duration").register(registry).record(() -> doSomething());

// ④ DistributionSummary:记录分布(订单金额、响应大小)
DistributionSummary.builder("order.amount")
    .publishPercentiles(0.5, 0.95, 0.99)
    .register(registry)
    .record(order.getAmount().doubleValue());

用注解埋点

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {
    String value() default "";
}

// AOP 实现
@Aspect
@Component
@RequiredArgsConstructor
public class TimedAspect {

    private final MeterRegistry registry;

    @Around("@annotation(timed)")
    public Object around(ProceedingJoinPoint pjp, Timed timed) throws Throwable {
        Timer timer = Timer.builder("method.duration")
                .tag("method", pjp.getSignature().toShortString())
                .register(registry);
        return timer.record(() -> {
            try {
                return pjp.proceed();
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        });
    }
}

// 用法
@Timed("createOrder")
public Long createOrder(OrderDTO dto) { ... }

四、Prometheus 集成

yaml
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['app1:8080', 'app2:8080', 'app3:8080']
    scrape_interval: 10s

五、Grafana 看板

json
// dashboard.json 片段
{
  "panels": [
    {
      "title": "QPS",
      "targets": [{
        "expr": "rate(http_server_requests_seconds_count[1m])"
      }],
      "type": "graph"
    },
    {
      "title": "P99 延迟",
      "targets": [{
        "expr": "histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[1m]))"
      }]
    },
    {
      "title": "JVM 堆内存",
      "targets": [{
        "expr": "jvm_memory_used_bytes{area='heap'}"
      }]
    }
  ]
}

看板布局建议

┌─────────────┬─────────────┬─────────────┐
│  QPS        │  RT (P50/P95/P99)  │  Error%   │
└─────────────┴────────────────────┴─────────────┘
┌─────────────────────────────┬─────────────────┐
│  JVM Heap / GC / Thread    │  HikariCP 池     │
└─────────────────────────────┴─────────────────┘
┌─────────────────────────────┬─────────────────┐
│  业务指标                   │  日志聚合        │
└─────────────────────────────┴─────────────────┘

六、告警规则

yaml
# Prometheus alert rules
groups:
  - name: app_alerts
    interval: 30s
    rules:
      # ① 错误率 > 1%
      - alert: HighErrorRate
        expr: |
          sum(rate(http_server_requests_seconds_count{status="5xx"}[5m]))
          /
          sum(rate(http_server_requests_seconds_count[5m])) > 0.01
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "错误率超过 1%"

      # ② P99 延迟 > 2 秒
      - alert: HighLatency
        expr: |
          histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m])) > 2
        for: 2m
        labels:
          severity: warning

      # ③ 堆内存使用率 > 80%
      - alert: HighMemoryUsage
        expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "JVM 堆内存使用率过高"

      # ④ 活跃线程数过多
      - alert: TooManyActiveThreads
        expr: jvm_threads_states_threads{state="runnable"} > 200
        for: 5m

七、链路追踪(Sleuth + Zipkin)

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
yaml
spring:
  zipkin:
    base-url: http://localhost:9411
    sender:
      type: web
  sleuth:
    sampler:
      probability: 1.0                    # 100% 采样(生产调低到 10%)
    propagation:
      type: B3                            # Zipkin 协议
java
// Sleuth 自动给每个请求生成 traceId
// 日志中会包含:
// [app,abc123,def456] INFO ...  ← traceId=abc123, spanId=def456

// RestTemplate 调用自动加 trace 头
// Feign 调用自动加 trace 头

SkyWalking(国产 APM,更强大)

bash
# SkyWalking OAP 服务端
docker run -d --name skywalking \
  -p 11800:11800 -p 12800:12800 \
  apache/skywalking-oap-server

# SkyWalking UI
docker run -d --name skywalking-ui \
  -p 8080:8080 \
  -e SW_OAP_ADDRESS=oap:12800 \
  apache/skywalking-ui
yaml
# agent/.../config/agent.config
agent.service_name=taskflow
collector.backend_service=localhost:11800

启动应用时挂载 agent:

bash
java -javaagent:/skywalking/agent/skywalking-agent.jar \
     -Dskywalking.agent.service_name=taskflow \
     -jar app.jar

八、日志聚合(ELK / Loki)

ELK 架构

Loki(更轻量)

yaml
# promtail config
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: spring-boot
    static_configs:
      - labels:
          job: spring-boot
          __path__: /var/log/app/*.log
    pipeline_stages:
      - regex:
          expression: '^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(?P<level>\w+)\s+...'

九、应用健康检查

健康检查端点

yaml
management:
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true
      group:
        liveness:
          include: livenessState
        readiness:
          include: readinessState,db,redis,diskSpace
yaml
# Kubernetes 探针配置
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  periodSeconds: 5

自定义健康检查

java
@Component
@RequiredArgsConstructor
public class PaymentServiceHealthIndicator implements HealthIndicator {

    private final PaymentServiceClient paymentClient;

    @Override
    public Health health() {
        try {
            // ① 检测支付服务是否可用
            String result = paymentClient.ping();
            if ("ok".equals(result)) {
                return Health.up()
                        .withDetail("service", "payment")
                        .build();
            }
            return Health.down()
                    .withDetail("error", "支付服务响应异常")
                    .build();
        } catch (Exception e) {
            return Health.down(e)
                    .withDetail("service", "payment")
                    .build();
        }
    }
}

十、本章小结

要点关键
三大支柱Metrics / Logs / Traces
Spring Actuator/actuator/{health,metrics,prometheus}
自定义指标Counter / Gauge / Timer / DistributionSummary
Prometheus抓取 /actuator/prometheus
Grafana可视化 + 告警规则
链路追踪Sleuth + Zipkin / SkyWalking
日志聚合ELK / Loki(轻量)
健康检查K8s liveness / readiness probe

动手练习

练习 1:基础题

接入 Prometheus + Grafana,给你的订单创建接口加上 QPS 和 P99 延迟监控,画一张看板。

练习 2:进阶题

实现自定义指标:每分钟统计订单金额分布(最小、最大、平均、P99),用 Grafana 展示。

练习 3:思考题

你的系统上线一周后出现了一次重大故障,但没人及时发现。如何设计一套告警体系?


下一章第 74 章:健康检查与 K8s 探针

本站基于 VitePress 构建 · 由 Codebook 团队维护