系统上线后,监控与诊断能力决定了问题的发现速度与修复效率。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 |
/prometheus | Prometheus 格式指标 | 采集器拉取 |
/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 类 | 说明 | 示例 |
|---|---|---|---|
| Counter | Counter | 单调递增计数器 | 请求总数、错误数 |
| Gauge | Gauge | 瞬态值 | 队列长度、连接数 |
| Timer | Timer | 时间分布 | 请求耗时 P99 |
| DistributionSummary | DistributionSummary | 数值分布 | 请求体大小 |
| LongTaskTimer | LongTaskTimer | 长任务计时 | 文件上传耗时 |
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 个线程 |
jvm | JVM 信息 | 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 + Zipkin | span 耗时分解 |
总结
Java 监控诊断的三驾马车:
- Metrics(指标):Micrometer + Prometheus,回答 “系统现在怎么样”(QPS、延迟、错误率、资源使用率)
- Logging(日志):结构化 JSON + ELK/Loki,回答 “发生了什么”(请求详情、异常堆栈、业务事件)
- Tracing(链路):Sleuth + Zipkin,回答 “问题出在哪一步”(跨服务调用链路、耗时分解)
配合 Arthas 的实时诊断能力和 JFR 的低开销深度分析,可以覆盖从线上快速止血到线下根因分析的全场景。建议在所有服务中统一基线埋点(HTTP 指标、JVM 指标、业务金指标),并建立分级告警(warning → critical → page)。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。