第 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-uiyaml
# 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,diskSpaceyaml
# 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 探针 →