Spring 可观测性
这一课你将能做到
- 为判题 backend 配置 Actuator,暴露
health、prometheus、metrics 端点,并说明生产为何不能 include: *
- 理解 Micrometer 自动注册的 HTTP、JVM、数据源、Kafka 指标,并会加一个判题业务的低基数 Timer/Counter
- 把 Actuator 与 K8s 探针、Prometheus scrape、结构化日志中的
traceId、OTel agent 的分工关系理清楚
为什么要学
判题 backend 基于 Spring Boot:可观测性主路径是 Actuator + Micrometer(健康检查、指标),链路常接 OTel agent 或 Spring Cloud Sleuth。但生产环境的 Actuator 配置错误是高频安全事件来源——有人把 management.endpoints.web.exposure.include: "*" 直接上线,heapdump、env、beans 端点全裸奔,攻击者可以直接下载堆 dump 拿到所有密钥。
面试里「Actuator 生产环境怎么配」「health 指示器怎么设计」「Micrometer 和 Prometheus 什么关系」是高频题。本课对照 prom-spring 的 registry 视角,但焦点是 Spring 应用侧如何暴露与埋点;Actuator 安全基础见 sb-actuator。
先建立图像
| 端点 | 用途 | 生产注意 |
|---|
/actuator/health | 存活/就绪;可含 DB、Kafka 等组件状态 | 细节不对公网裸奔 |
/actuator/prometheus | Prometheus 文本格式指标 | 内网或鉴权 |
/actuator/metrics | 单指标 JSON 查看(排障用) | 内网或鉴权 |
/actuator/env、/actuator/heapdump | 环境变量、堆 dump | 禁止生产暴露 |
一次判题提交的 Spring 侧观测面:
POST /api/judge/submissions
│
├─ Micrometer Timer(http.server.requests,含 uri/method/status)
├─ Actuator /prometheus ←── Prometheus scrape(15s 间隔)
├─ health indicators ←── K8s liveness/readiness 探针
├─ 日志 MDC(traceId) ←── OTel agent 过滤器
├─ 手动 Counter(judge_submissions_total{language="java"})
└─ DataSource/Kafka 自动指标 ←── Micrometer 插桩
跟着做
步骤 1:加入依赖
<!-- 将 Micrometer 指标以 Prometheus 格式暴露 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
应得到:classpath 具备 Prometheus registry;尚需配置暴露端点。
步骤 2:配置暴露面与公共标签(生产底线)
management:
endpoints:
web:
exposure:
# 生产显式白名单;绝对禁止 include: "*"
include: health,prometheus,metrics,info
base-path: /actuator
endpoint:
health:
show-details: when_authorized # 细节只对认证用户展示
metrics:
tags:
application: coderoadmap-backend # 聚合用低基数公共标签
region: cn-hangzhou
- 白名单暴露:只开
health,prometheus,metrics,info
- 端口隔离:生产可配
management.server.port: 9091,与业务端口分开,用网络策略限制访问
- 鉴权:Spring Security 保护
/actuator/**,或只允许内网 IP
步骤 3:本地验证 prometheus 端点
# 查 JVM 内存指标(Windows 用 findstr,Linux 用 grep)
curl -s http://localhost:8080/actuator/prometheus | findstr jvm_memory_used_bytes
# 查健康状态
curl -s http://localhost:8080/actuator/health
# 查单个指标(如 HTTP 请求计数)
curl -s "http://localhost:8080/actuator/metrics/http.server.requests"
应看到:JVM 内存指标行;health 返回 UP/DOWN JSON;metrics 返回具体数值。scrape 配置细节见 prom-scrape。
步骤 4:给判题接口加业务埋点
@RestController
@RequestMapping("/api/judge")
public class JudgeController {
private final JudgeService judgeService;
private final MeterRegistry registry;
public JudgeController(JudgeService judgeService, MeterRegistry registry) {
this.judgeService = judgeService;
this.registry = registry;
}
@PostMapping("/submissions")
public SubmissionDto submit(@RequestBody SubmitRequest req) {
// 业务计数:标签只用有界枚举
registry.counter("judge_submissions_total",
"language", req.getLanguage()) // java/python/cpp,3~5 种
.increment();
return judgeService.submit(req);
}
}
在 JudgeService 给判题创建耗时加 Timer:
public SubmissionDto submit(SubmitRequest req) {
return Timer.builder("judge_submission_create")
.description("判题创建耗时")
.register(registry)
.record(() -> doSubmit(req)); // 写库 + 投递 Kafka
}
启动后访问 /actuator/prometheus,搜索 judge_submissions_total 和 judge_submission_create_seconds 应能看到对应行。
步骤 5:设计 health 指示器——判题关键依赖
判题 backend 的健康不只看「进程活着」,还要看关键依赖是否真的可用:
management:
health:
db:
enabled: true
redis:
enabled: true
kafka:
enabled: true
# 关键:readiness 要包含所有关键依赖
probes:
enabled: true
group:
readiness:
include: db,redis,kafka,diskSpace
liveness:
include: ping # 存活只看进程本身,避免因依赖抖动被误杀
- Liveness:只检查进程本身(
ping),DB 挂了不该让 K8s 重启容器——重启解决不了 DB 问题,反而导致 cascading failure
- Readiness:检查所有关键依赖(DB、Redis、Kafka),任何一个挂了 K8s 摘流,防止请求打到一个「活着但已残废」的实例
步骤 6:日志关联 traceId
确认 OTel agent 或 Sleuth 把 traceId 放入 MDC,JSON 日志能搜到:
{
"timestamp": "2026-10-07T10:30:15.123",
"level": "INFO",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"spanId": "00f067aa0ba902b7",
"logger": "com.coderoadmap.judge.JudgeService",
"message": "提交创建成功 submissionId=12345 language=java"
}
排障时:Grafana 面板发现 P99 突增 → 复制 traceId → Loki 查日志 → 定位到具体 submission 和慢 SQL。见 obs-logs。
步骤 7:验证 Kafka 和 DataSource 自动指标
# Kafka producer 指标(投递耗时、失败率)
curl -s http://localhost:8080/actuator/prometheus | findstr kafka_producer
# DataSource 连接池指标(活跃连接、等待线程)
curl -s http://localhost:8080/actuator/prometheus | findstr hikaricp
应看到:kafka_producer_record_send_total、hikaricp_connections_active、hikaricp_connections_pending 等。这些是排障时判断「是应用慢还是依赖慢」的关键线索。
讲清楚要点
- Actuator 是管理门面,所以生产必须限制暴露与鉴权——
* 等于把内部状态大开,heapdump 可以拿到所有密钥和数据库密码。
- Micrometer 是指标引擎,所以业务代码依赖 MeterRegistry 而不是直接拼 Prometheus 文本——解耦后端格式,未来切 OTLP 也不动业务代码。
- 公共标签
application、region 利于聚合,所以要在配置层统一打——避免每个指标手写不一致,多副本环境下按标签 sum 聚合。
- 自动 HTTP 指标已覆盖入口,所以手动埋点应补充业务语义,而非重复每一个 controller——见 obs-instrumentation。
- health 影响流量与信任,所以指示器要真实——假 UP 比假 DOWN 更危险;liveness 和 readiness 要分开设计,依赖挂了只影响 readiness。
- scrape 与 OTLP 可并存,所以 Spring 指标出口要与整体遥测架构一致——见 obs-telemetry。
- 面板在 Grafana,所以命名与标签要稳定——对照 prom-grafana;改名等于断掉所有历史面板。
你可能会懵的地方
-
metrics 和 prometheus 端点有何不同?
metrics 便于人工 JSON 查看,排障时 curl 一下看单个指标;Prometheus 拉取用 prometheus 文本格式,批量导出所有指标。数据源都是同一个 MeterRegistry,只是序列化格式不同。
-
为何不能 include: *?
会暴露 env(环境变量,含数据库密码)、heapdump(堆 dump,含内存里所有对象)、beans(所有 Spring Bean)、mappings(所有 URL 映射)等敏感能力。攻击面和误操作面都放大,安全事件高发源。
-
health 细节对谁可见?
公网通常只给汇总 {"status":"UP"};细节(如 DB 连接数、磁盘剩余)when_authorized 或仅内网。K8s 探针只需要 200/503 状态码,不需要细节。
-
和 prom-spring 课如何分工?
本课:Spring 应用侧暴露与埋点;那课:Prometheus 如何 scrape、怎么和 Grafana 配。一句话:本课「给信号」,那课「收信号」。
-
OTel agent 和 Micrometer 会冲突吗?
不冲突,分工不同:Micrometer 管 metrics(/actuator/prometheus),OTel agent 管 traces(OTLP → Jaeger)。日志的 traceId 由 OTel agent 注入 MDC。同一信号只允许一个生产者,别双计。
错法对照
| 错法 | 问题 | 正解 |
|---|
exposure.include: "*" | 暴露 env/heapdump 等敏感端点 | 显式白名单 + 鉴权/网络隔离 |
| health 永远 UP,忽略 DB | K8s 探针认为健康,流量打到残废实例 | 关键依赖纳入 readiness 指示器 |
| liveness 和 readiness 不区分 | DB 抖动导致 K8s 反复重启容器 | liveness 只看 ping,readiness 看依赖 |
| 业务标签用 userId/submissionId | 时序爆炸拖垮 Prometheus | 低基数枚举;详情进日志和 Span 属性 |
| 只暴露指标,不做 traceId 关联 | 排障时指标异常但无法定位具体请求 | 日志/链路同步建设(见 obs-logs) |
| Actuator 端口暴露公网无防护 | 攻击者直接访问管理端点 | 内网、网关鉴权或 management 独立端口 |
卡点急救
/actuator/prometheus 404:检查 micrometer-registry-prometheus 依赖是否在 classpath;检查 management.endpoints.web.exposure.include 是否包含 prometheus;检查 Spring Security 是否拦截了 /actuator/**。
- K8s 探针反复重启 Pod:先看
kubectl describe pod 里 liveness 和 readiness 的失败原因;再确认 liveness 是否误把 DB/Kafka 等依赖包含进去了——依赖挂了不该重启容器,只该摘流。
- 指标在
/actuator/prometheus 有,Prometheus 里没有:先查 Status → Targets 是否 UP;再查 Micrometer 导出名是否加了 _total 或 _seconds 后缀(如 judge_submissions_total 实际导出为 judge_submissions_total);最后确认 Prometheus scrape 配置的 metrics_path 是否为 /actuator/prometheus。
面试常问
-
Actuator 生产环境怎么配?安全底线是什么?
背这个版本:三底线——白名单暴露(只开 health/prometheus/metrics/info,禁止 *)、端口隔离(management 端口与业务端口分开,网络策略限制访问)、鉴权(Spring Security 保护或只允许内网 IP)。原因:heapdump 端点可以下载堆 dump 拿到所有密钥,env 端点泄露环境变量,beans 端点暴露内部结构。生产环境只暴露排障必需的最小集合。
-
Actuator health 的 liveness 和 readiness 怎么设计?
分工原则:liveness 只检查进程本身(ping),DB 挂了不该让 K8s 重启容器——重启解决不了 DB 问题,反而导致 cascading failure。readiness 检查所有关键依赖(DB、Redis、Kafka),任何一个挂了 K8s 摘流,防止请求打到一个「活着但已残废」的实例。判题系统例子:MySQL 挂了,liveness 仍 UP(进程活着),readiness DOWN(摘流),K8s 不再转发请求,等 MySQL 恢复后 readiness 自动 UP 重新接流量。
-
Micrometer 和 Prometheus 是什么关系?
Micrometer 是 Java 应用的指标门面(类似 SLF4J 之于日志),提供统一的 MeterRegistry API;Prometheus 是后端时序数据库。Micrometer 通过 micrometer-registry-prometheus 把指标序列化成 Prometheus 文本格式,暴露 /actuator/prometheus 端点;Prometheus 定期 scrape 这个端点。好处:业务代码只依赖 Micrometer API,未来切 OTLP 或其他后端不动业务代码。
自测三问
- 生产 Actuator 暴露策略的三条底线是什么?为什么不能
include: *?
curl /actuator/prometheus 成功后,Prometheus 端下一步要做什么配置?
- 判题 backend 的 liveness 和 readiness 指示器该怎么分组?MySQL 挂了应该影响哪个?
小结一句话
Actuator 是门面,Micrometer 是指标引擎——白名单暴露、真实 health、低基数埋点、liveness/readiness 分离,构成 Spring 侧可观测主路径。
延伸