13. Java 监控诊断与可观测性

Java 系统监控诊断全栈方案:Spring Boot Actuator、Micrometer 指标、Arthas 实时诊断、JFR 飞行记录器与分布式链路追踪

系统上线后,监控与诊断能力决定了问题的发现速度与修复效率。Java 生态提供了 Actuator 端点暴露、Micrometer 统一指标、Arthas 实时诊断、JFR/JMC 离线分析等工具,配合 Prometheus/Grafana 实现可视化。本文构建完整的可观测性体系。

1. Spring Boot Actuator

1.1 核心端点

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus,loggers,env,heapdump,threaddump
      base-path: /actuator
  endpoint:
    health:
      show-details: when_authorized  # 授权后展示详情
      show-components: always
    metrics:
      enabled: true
    prometheus:
      enabled: true
  server:
    port: 8081  # 管理端口分离
端点描述常用场景
/health应用健康状态K8s 存活/就绪探针
/metrics运行时指标JVM/CPU/内存/GC
/prometheusPrometheus 格式指标采集器拉取
/threaddump线程快照死锁/热点分析
/heapdump堆内存转储OOM 分析
/loggers日志级别调整线上动态 DEBUG
/env环境变量与配置配置核查

1.2 自定义健康指示器

@Component
public class DatabaseHealthIndicator implements HealthIndicator {
    
    @Autowired
    private DataSource dataSource;
    
    @Override
    public Health health() {
        try (Connection conn = dataSource.getConnection()) {
            if (conn.isValid(3)) {
                return Health.up()
                    .withDetail("database", "MySQL")
                    .withDetail("validationQuery", "SELECT 1")
                    .build();
            }
        } catch (SQLException e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
        return Health.down().build();
    }
}

1.3 自定义信息端点

@Component
public class BuildInfoContributor implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("app", Map.of(
            "name", "order-service",
            "version", "1.2.3",
            "buildTime", Instant.now(),
            "commit", "a1b2c3d"
        ));
    }
}

2. Micrometer 指标体系

2.1 指标类型

类型Java 类说明示例
CounterCounter单调递增计数器请求总数、错误数
GaugeGauge瞬态值队列长度、连接数
TimerTimer时间分布请求耗时 P99
DistributionSummaryDistributionSummary数值分布请求体大小
LongTaskTimerLongTaskTimer长任务计时文件上传耗时

2.2 业务指标埋点

@Service
public class OrderService {
    
    private final Counter orderCreatedCounter;
    private final Timer orderProcessingTimer;
    private final Gauge orderQueueGauge;
    private final AtomicInteger queueSize = new AtomicInteger(0);
    
    public OrderService(MeterRegistry registry) {
        this.orderCreatedCounter = Counter.builder("orders.created")
            .description("Total orders created")
            .tag("service", "order-service")
            .register(registry);
        
        this.orderProcessingTimer = Timer.builder("orders.processing.time")
            .description("Order processing duration")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);
        
        this.orderQueueGauge = Gauge.builder("orders.queue.size", 
                queueSize, AtomicInteger::get)
            .description("Pending orders in queue")
            .register(registry);
    }
    
    public Order createOrder(OrderRequest request) {
        return orderProcessingTimer.record(() -> {
            // 业务逻辑
            orderCreatedCounter.increment();
            queueSize.incrementAndGet();
            try {
                return doCreate(request);
            } finally {
                queueSize.decrementAndGet();
            }
        });
    }
}

2.3 与 Prometheus 集成

// build.gradle
implementation 'io.micrometer:micrometer-registry-prometheus'

暴露格式示例(/actuator/prometheus):

# HELP orders_created_total Total orders created
# TYPE orders_created_total counter
orders_created_total{service="order-service",} 1523.0

# HELP orders_processing_time_seconds Order processing duration
# TYPE orders_processing_time_seconds summary
orders_processing_time_seconds{quantile="0.95",} 0.045
orders_processing_time_seconds{quantile="0.99",} 0.089
orders_processing_time_seconds_count 1523.0
orders_processing_time_seconds_sum 45.67

2.4 自定义 MeterBinder

@Component
public class CacheMetricsBinder implements MeterBinder {
    
    @Autowired
    private CacheManager cacheManager;
    
    @Override
    public void bindTo(MeterRegistry registry) {
        cacheManager.getCacheNames().forEach(name -> {
            Cache cache = cacheManager.getCache(name);
            Gauge.builder("cache.size", cache, c -> getEstimatedSize(c))
                .tag("name", name)
                .register(registry);
        });
    }
}

3. Arthas 实时诊断

3.1 快速安装与连接

# 下载并启动
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

# 选择目标 JVM 进程后进入交互式命令行

3.2 核心命令速查

命令功能典型用法
dashboard系统实时面板整体资源概览
thread线程分析thread -n 3 找出 CPU 最高的 3 个线程
jvmJVM 信息GC/内存/类加载器
sc / sm查看类/方法sc com.example.OrderService
jad反编译类jad com.example.OrderService 查看线上代码
watch方法入参与返回值watch com.example.OrderService createOrder '{params,returnObj}'
trace方法内部耗时trace com.example.OrderService createOrder '#cost>100'
stack方法调用栈stack com.example.OrderService createOrder
tt方法执行时空隧道录制方法调用,支持重放
heapdump堆转储heapdump /tmp/dump.hprof
vmtool强制 GC 或获取实例vmtool --action forceGc

3.3 watch 实战:监控方法入参和返回值

# 监控 createOrder 方法的入参和返回值,只打印慢于 100ms 的
watch com.example.service.OrderService createOrder \
    '{params[0],returnObj,throwExp}' \
    '#cost>100' \
    -x 2

# 输出示例
@ArrayList[
    @OrderRequest[id=123,amount=99.99],
    @Order[id=456,status=PAID,total=99.99],
    null,
]

3.4 trace 实战:分析方法耗时

# 追踪 createOrder 方法内部每一步的耗时
trace com.example.service.OrderService createOrder

# 输出(缩进表示调用层级)
`---ts=2024-01-15 10:23:45;thread_name=http-nio-8080-exec-5;id=48;
    `---[15.234ms] com.example.service.OrderService:createOrder()
        +---[2.134ms] com.example.repository.UserRepository:findById()
        +---[8.567ms] com.example.client.PaymentClient:charge()
        |   `---[7.890ms] com.example.client.PaymentClient$$FastClass:invoke()
        +---[1.234ms] com.example.repository.OrderRepository:save()
        `---[0.456ms] com.example.event.OrderEventPublisher:publish()

3.5 tt 时空隧道:录制并重放

# 录制所有 createOrder 调用
tt -t com.example.service.OrderService createOrder

# 查看录制列表,获取 INDEX
tt -l

# 重放第 1000 次调用
tt -p -i 1000

# 查看第 1000 次调用的入参和返回值
tt -w '{params,returnObj}' -i 1000 -x 2

3.6 线上热修复(谨慎使用)

# 反编译确认目标类
jad --source-only com.example.service.OrderService > /tmp/OrderService.java

# 修改 /tmp/OrderService.java 后编译
mc /tmp/OrderService.java -d /tmp

# 热替换(仅支持方法体修改,不修改类结构)
redefine /tmp/com/example/service/OrderService.class

4. JDK Flight Recorder (JFR) 与 Mission Control

4.1 JFR 录制

# 持续录制,发生 OOM 时自动转储
java -XX:StartFlightRecording=
    duration=0,
    filename=/tmp/recording.jfr,
    settings=profile,
    dumponexit=true

# JCMD 动态控制
jcmd <pid> JFR.start name=profile filename=/tmp/rec.jfr settings=profile
jcmd <pid> JFR.dump name=profile
jcmd <pid> JFR.stop name=profile

4.2 使用 JMC 分析

JDK Mission Control 提供可视化分析:

  • 线程:阻塞时间、锁竞争热点
  • 内存:对象分配率、存活年龄直方图
  • I/O:文件/网络读写耗时
  • 方法采样:CPU 热点方法(低开销,<1%)
  • 异常:每秒异常抛出频率

4.3 编程式 JFR 事件

@Category("Custom")
@Label("Order Processing")
@Name("com.example.OrderProcessing")
class OrderEvent extends Event {
    String orderId;
    double amount;
    String status;
}

// 使用
OrderEvent event = new OrderEvent();
event.begin();
event.orderId = "12345";
try {
    processOrder();
    event.status = "SUCCESS";
} catch (Exception e) {
    event.status = "FAILED";
} finally {
    event.end();
    event.commit();
}

5. 日志与链路追踪

5.1 结构化日志

<!-- logback-spring.xml -->
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <includeContext>true</includeContext>
    <includeMdc>true</includeMdc>
    <customFields>{"service":"${SERVICE_NAME}","version":"${VERSION}"}</customFields>
</encoder>

输出 JSON:

{
  "@timestamp": "2024-01-15T10:25:30.123+08:00",
  "level": "INFO",
  "logger_name": "c.e.service.OrderService",
  "message": "Order created",
  "traceId": "abc123def456",
  "spanId": "span789",
  "service": "order-service",
  "orderId": "12345"
}

5.2 MDC 传递 Trace ID

@Component
public class TraceIdFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String traceId = request.getHeader("X-Trace-Id");
        if (!StringUtils.hasText(traceId)) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }
        MDC.put("traceId", traceId);
        response.setHeader("X-Trace-Id", traceId);
        
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

5.3 Sleuth + Zipkin 链路追踪

spring:
  sleuth:
    enabled: true
    sampler:
      probability: 1.0  # 采样率 100%(开发);生产建议 0.1
  zipkin:
    base-url: http://zipkin:9411
    sender:
      type: kafka       # 或 rabbit/web

微服务间自动透传 trace context:

  • OpenFeign 自动注入 X-B3-TraceId 等头部
  • RestTemplate、WebClient 同理
  • 手动传递:Tracer.nextSpan().name("custom-span").start()

6. 告警规则

6.1 Prometheus Alertmanager 规则

groups:
  - name: jvm-alerts
    rules:
      - alert: JVMMemoryHigh
        expr: jvm_memory_used_bytes / jvm_memory_max_bytes > 0.85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "JVM 内存使用率过高"
          description: "{{ $labels.instance }} 内存使用率 {{ $value | humanizePercentage }}"

      - alert: GCCountHigh
        expr: rate(jvm_gc_pause_seconds_count[5m]) > 10
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "GC 频率过高"

      - alert: APIErrorRateHigh
        expr: rate(http_server_requests_seconds_count{status=~"5.."}[5m]) 
              / rate(http_server_requests_seconds_count[5m]) > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "API 错误率超过 5%"

      - alert: APILatencyHigh
        expr: histogram_quantile(0.99, 
              rate(http_server_requests_seconds_bucket[5m])) > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "API P99 延迟超过 1 秒"

7. 诊断工作流

问题类型工具/命令关键指标
CPU 飙高thread -n 3 + thread <tid>热点方法、死循环
内存泄漏heapdump + MAT/Eclipse 分析Dominator Tree、Leak Suspects
响应慢trace + JFR 方法采样慢方法、锁等待、I/O 阻塞
GC 频繁JFR GC 事件 + GC 日志GC 次数、停顿时间、晋升失败
线程死锁thread -b + /threaddump阻塞线程、锁持有链
连接池耗尽Micrometer 指标active/max/waiting connections
远程调用慢OpenFeign + Zipkinspan 耗时分解

总结

Java 监控诊断的三驾马车:

  1. Metrics(指标):Micrometer + Prometheus,回答 “系统现在怎么样”(QPS、延迟、错误率、资源使用率)
  2. Logging(日志):结构化 JSON + ELK/Loki,回答 “发生了什么”(请求详情、异常堆栈、业务事件)
  3. Tracing(链路):Sleuth + Zipkin,回答 “问题出在哪一步”(跨服务调用链路、耗时分解)

配合 Arthas 的实时诊断能力和 JFR 的低开销深度分析,可以覆盖从线上快速止血到线下根因分析的全场景。建议在所有服务中统一基线埋点(HTTP 指标、JVM 指标、业务金指标),并建立分级告警(warning → critical → page)。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java-enterprise」更多文章

  1. 限流算法深度解析:令牌桶、漏桶与滑动窗口计数
  2. Java 代码质量:SonarQube、Checkstyle 与 SpotBugs 工程化实践
  3. Spring IoC 容器与依赖注入原理深度剖析