Spring Boot 生产监控与线上问题排查
线上排查不是“登录服务器看日志”,而是围绕一次异常建立完整证据链:什么时候开始、影响谁、哪一层先异常、资源是否饱和、最近发生了什么变更、止损动作是否有效。Spring Boot 提供 Actuator 和 Micrometer,但它们只是观测入口;完整体系还需要日志平台、指标平台、链路追踪、告警、操作审计和故障复盘。
本文提供可以直接用于生产和值班面试的 Runbook。命令只是示例,执行前必须确认环境、权限、版本和影响范围。
一、先记住排查主线
flowchart TD
A["收到告警或用户反馈"] --> B["确认影响范围和开始时间"]
B --> C["冻结变更并保存现场"]
C --> D["看流量、错误率、耗时、饱和度"]
D --> E{"单实例还是全实例"}
E -- "单实例" --> F["JVM、线程、容器、宿主机"]
E -- "全实例" --> G["数据库、Redis、MQ、下游、网络"]
F --> H["日志 + 指标 + Trace + Dump交叉验证"]
G --> H
H --> I["限流、降级、摘流量、回滚或扩容"]
I --> J["验证恢复并持续观察"]
J --> K["定位根因、修复、补监控和复盘"]面试时可以浓缩成一句话:
先定影响面和时间窗,再用 RED/USE 指标判断应用层还是资源层;用 traceId 把网关、应用、数据库和下游串起来;必要时采集线程栈、堆和 GC 证据;先做可回退止损,再验证恢复,最后完成根因修复与复盘。
二、监控对象和四类信号
2.1 RED:面向请求
| 信号 | 含义 | Spring Boot 常见来源 | 重点拆分维度 |
|---|---|---|---|
| Rate | 每秒请求量、业务吞吐 | http.server.requests、网关指标 | 服务、路由、实例 |
| Errors | 失败数和失败率 | HTTP 状态码、业务错误码、异常日志 | URI 模板、异常类型、下游 |
| Duration | P50/P95/P99 延迟 | Timer、Trace Span | URI 模板、实例、下游调用 |
不要只盯平均耗时。少量极慢请求可能被平均值掩盖,而用户体验通常更接近 P95/P99。
2.2 USE:面向资源
| 信号 | 含义 | 示例 |
|---|---|---|
| Utilization | 使用率 | CPU 使用率、连接池 active/maximum |
| Saturation | 饱和与排队 | CPU run queue、线程池队列、Hikari pending |
| Errors | 资源错误 | OOM、磁盘 IO 错误、连接创建失败 |
CPU 80% 不一定有问题;CPU 60% 但运行队列持续很长、容器被限流,同样可能导致严重延迟。
2.3 JVM 四组核心指标
| 维度 | 要看什么 | 异常信号 |
|---|---|---|
| Heap | used、committed、max、老年代趋势 | Full GC 后基线仍持续抬升 |
| GC | 次数、暂停时间、分配速率 | 停顿突增、GC 后回收很少 |
| Thread | live、daemon、peak、blocked | 线程持续增长、大量 BLOCKED/WAITING |
| Class | loaded、unloaded | 动态生成类异常增长、Metaspace 压力 |
2.4 业务信号
技术指标正常不代表业务正常。至少监控:
| 系统 | 业务指标示例 |
|---|---|
| 订单 | 创建成功率、支付回调成功率、超时关闭积压 |
| 库存 | 扣减失败率、库存不一致数、补偿任务积压 |
| 采集 | 采集成功率、医院/设备维度失败率、队列长度 |
| 结算 | 对账差异数、结算延迟、重复处理数 |
业务指标 Tag 必须低基数。订单号、用户 ID、手机号等应进入日志或 Trace 属性,不能成为 Prometheus Label。
三、生产可观测架构
flowchart LR
A["Spring Boot应用"] --> B["Actuator + Micrometer"]
A --> C["结构化日志"]
A --> D["Observation / Trace"]
B --> E["Prometheus"]
E --> F["Grafana"]
E --> G["Alertmanager"]
C --> H["ELK或Loki"]
D --> I["OTel Collector"]
I --> J["Tempo或Jaeger"]
G --> K["值班通知"]
F --> L["看趋势和实例差异"]
H --> M["按traceId检索事件"]
J --> N["还原跨服务耗时"]三类数据职责不同:
| 数据 | 擅长回答 | 不擅长回答 |
|---|---|---|
| Metrics | 是否异常、何时开始、趋势、哪些实例异常 | 单个请求具体经历了什么 |
| Logs | 发生了什么事件、参数摘要、异常栈 | 全局趋势和分位数成本较高 |
| Traces | 一次请求经过哪些服务、哪段最慢 | 长期聚合和所有离散业务事件 |
正确方法是先用指标发现异常时间窗和实例,再用 Trace 找慢 Span,最后用 traceId 到日志平台读取上下文。
四、Spring Boot 接入基线
4.1 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>Spring Boot 3 的统一观测主线是 Micrometer Observation 和 Micrometer Tracing。Boot 2.x 项目常见 Spring Cloud Sleuth;迁移到 Boot 3 时不能继续照搬旧 Sleuth 配置,要按 Boot、Spring Cloud 和 Tracing Bridge 的精确版本验证。
4.2 最小安全配置
management:
server:
port: 9090
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
probes:
enabled: true
show-details: never
metrics:
tags:
application: ${spring.application.name}生产原则:
- 管理端口只允许管理网络或 Sidecar 访问。
- 公网网关不能转发 Actuator。
/env、/configprops、/beans、/loggers、/threaddump、/heapdump按需临时开放并鉴权。show-details不向匿名用户暴露依赖地址和异常信息。- Heap Dump 优先通过受审计的运维通道采集,不通过 Web 端点下载。
4.3 端点排查表
| 端点 | 能回答什么 | 生产注意事项 |
|---|---|---|
/actuator/health | 实例和依赖是否健康 | 区分 liveness/readiness,不做重查询 |
/actuator/prometheus | Prometheus 格式指标 | 仅监控网络访问 |
/actuator/metrics | 指标名称和具体 Meter | 适合临时定位,不替代时序平台 |
/actuator/conditions | 自动配置为何匹配或不匹配 | 可能暴露内部类和配置结构 |
/actuator/env | 最终属性和值来源 | 必须脱敏、鉴权 |
/actuator/configprops | 配置对象绑定结果 | 必须脱敏、鉴权 |
/actuator/threaddump | 当前线程状态和栈 | 一次快照不能证明趋势 |
/actuator/loggers | 查询或临时修改日志级别 | 修改要审计并定时恢复 |
/actuator/startup | 启动步骤耗时 | 需要应用启动过程支持对应缓冲记录 |
更完整的端点、自定义 HealthIndicator 和 Meter 示例见 Actuator监控。
五、日志必须做到可检索、可关联
5.1 一条请求日志至少包含
| 字段 | 作用 |
|---|---|
timestamp | 对齐故障时间线,必须明确时区 |
level | 控制检索和告警 |
service、instance | 判断服务和单实例问题 |
traceId、spanId | 关联跨服务 Trace |
requestId | 网关或调用方请求标识 |
uriTemplate、method | 聚合同类接口,避免真实 ID 造成高基数 |
durationMs、status | 识别慢请求和错误 |
errorCode、exception | 区分业务失败和系统异常 |
version | 关联发布版本 |
不要记录密码、Token、Cookie、身份证、手机号、银行卡、医疗隐私或完整请求体。确需排查时也应按字段白名单脱敏。
5.2 traceId 传播
- HTTP:使用标准追踪 Header 或公司统一 Header,由框架自动传播优先。
- MQ:在消息 Header 携带追踪上下文,消费端创建新的 Span。
- 异步线程:使用框架提供的上下文传播能力;只把 MDC 放进主线程会在异步任务中丢失。
- 定时任务:没有上游请求,应为每次任务创建新的观测上下文。
不要在业务代码中到处手写随机 traceId;否则无法正确形成父子 Span,也容易在线程复用时忘记清理 MDC。
5.3 临时提高日志级别
curl -X POST http://127.0.0.1:9090/actuator/loggers/com.example.order \
-H 'Content-Type: application/json' \
-d '{"configuredLevel":"DEBUG"}'排查结束恢复:
curl -X POST http://127.0.0.1:9090/actuator/loggers/com.example.order \
-H 'Content-Type: application/json' \
-d '{"configuredLevel":"INFO"}'动态 DEBUG 可能显著增加 CPU、磁盘和敏感信息风险。必须限制包范围、持续时间和实例数量,并记录操作人。
六、看板和告警怎么设计
6.1 最小服务看板
| 区域 | 图表 |
|---|---|
| 流量 | QPS、入口/出口流量、实例数 |
| 质量 | 5xx、业务失败率、超时率 |
| 延迟 | P50、P95、P99,按 URI 模板和下游拆分 |
| JVM | Heap、GC 暂停、线程数、类加载 |
| 容器 | CPU、CPU throttling、内存 working set、重启数 |
| 线程池 | active、pool size、queue、reject |
| 数据库 | Hikari active/idle/pending、获取连接耗时、慢 SQL |
| Redis | 命中率、连接数、耗时、超时 |
| MQ | 生产/消费速率、失败、重试、积压、最老消息年龄 |
| 业务 | 核心交易成功率、任务积压和处理延迟 |
6.2 告警不要只写“超过阈值”
一条可执行告警至少包含:服务、环境、实例、当前值、阈值、持续时间、影响、看板链接、日志链接、Runbook 链接、最近发布版本。
| 告警 | 示例思路 | 避免的问题 |
|---|---|---|
| 错误率 | 5xx 比例持续升高且请求量达到最低门槛 | 低流量时一次失败造成 100% 噪声 |
| 延迟 | P99 连续多个窗口超阈值 | 单个瞬时尖峰反复报警 |
| 连接池 | pending > 0 且 active 接近 maximum | 只看 active 造成误报 |
| JVM | Full GC 频繁且 GC 后使用量持续高 | 仅凭 Heap 80% 报警 |
| MQ | 最老消息年龄和积压同时增长 | 业务低峰固定积压误报 |
| 实例 | readiness 失败或重启次数增加 | 只看进程是否存在 |
阈值必须基于容量测试、历史基线和 SLO 调整,不能把示例数字直接照搬到生产。
七、线上排查标准 Runbook
7.1 第一步:确认现象
先回答:
- 是用户反馈、业务监控还是基础设施告警?
- 哪个环境、地域、租户、接口、业务功能受影响?
- 从什么时候开始,持续还是间歇?
- 全量用户还是少量用户?单实例还是全部实例?
- 错误、变慢、数据错误还是完全不可用?
不要在影响面未知时直接重启全部实例。重启可能销毁线程栈、临时日志、现场连接和内存证据。
7.2 第二步:建立时间线
| 时间 | 事件 | 证据 |
|---|---|---|
| T-30m | 新版本发布 | 发布平台、镜像版本 |
| T-10m | QPS 开始上涨 | Prometheus/Grafana |
| T0 | P99 和连接等待同时升高 | HTTP、Hikari 指标 |
| T+5m | 出现数据库超时 | 应用日志、Trace |
| T+10m | 限流后恢复 | 网关指标、业务成功率 |
重点对齐:发布、配置变更、扩缩容、流量变化、定时任务、数据库 DDL、下游故障和证书过期。
7.3 第三步:四层定位
| 层次 | 先看什么 | 典型问题 |
|---|---|---|
| 入口 | DNS、CDN、LB、网关状态码和耗时 | 502/504、路由、限流、证书 |
| 应用 | RED、日志、Trace、线程池、JVM | 异常、锁竞争、队列堆积、GC |
| 依赖 | DB、Redis、MQ、第三方接口 | 慢 SQL、连接耗尽、积压、超时 |
| 资源 | 容器、宿主机、网络、磁盘 | CPU throttling、OOMKill、IO 满、丢包 |
7.4 第四步:止损
按风险从低到高考虑:
- 暂停非核心任务、批处理或异常流量入口。
- 限流、熔断、降级、返回缓存或只读结果。
- 摘除单个异常实例,保留现场副本。
- 扩容,但先确认瓶颈不是共享数据库或下游。
- 回滚最近发布或配置。
- 最后才考虑重启;重启前尽可能采集线程栈、GC、日志和必要 Dump。
所有动作都要有回退方案,写明负责人、时间和验证指标。
7.5 第五步:验证恢复
不能只看“接口能访问”。至少确认:
- 错误率、P95/P99 回到基线。
- 核心业务成功率恢复。
- 线程池、连接池、队列不再持续积压。
- GC、CPU、内存没有继续恶化。
- 补偿、重试没有制造第二波流量。
- 持续观察至少覆盖一个有意义的业务窗口。
八、Linux 和进程级证据
以下命令需要相应工具和权限;容器环境应结合 cgroup 指标判断,不能只看宿主机总量。
# 系统负载、内存、运行队列
uptime
free -m
vmstat 1 10
# 进程 CPU、线程和上下文切换
top -H -p <pid>
pidstat -p <pid> 1 10
pidstat -w -p <pid> 1 10
# 磁盘和网络
iostat -xz 1 10
ss -s
ss -antp
lsof -p <pid> | wc -l
# 文件系统
df -h
df -i
du -x -h --max-depth=1 /path/to/check关键解释:
| 现象 | 可能方向 |
|---|---|
| load 高、CPU 高 | 计算热点、死循环、大量 GC |
| load 高、CPU 不高 | IO 等待、不可中断任务、运行队列 |
wa 高、磁盘延迟高 | 磁盘瓶颈、日志洪峰、云盘异常 |
| 文件描述符接近上限 | 连接泄漏、文件未关闭、上限过低 |
大量 TIME_WAIT | 短连接多;先判断是否真造成端口压力 |
大量 CLOSE_WAIT | 本端收到关闭但未正确释放 Socket |
九、JVM 排查工具箱
优先使用与目标 JVM 同版本、同用户权限的工具。JDK 版本和容器 PID 命名空间不同会导致 attach 失败。
9.1 低风险信息
jcmd <pid> VM.version
jcmd <pid> VM.flags
jcmd <pid> VM.command_line
jcmd <pid> GC.heap_info
jcmd <pid> Thread.print -l
jstat -gcutil <pid> 1000 109.2 连续线程栈
jcmd <pid> Thread.print -l > threads-1.txt
# 间隔数秒后再次采集,至少三次
jcmd <pid> Thread.print -l > threads-2.txt
jcmd <pid> Thread.print -l > threads-3.txt一次线程栈只是一张照片。相同线程在多次快照中停留在同一业务栈,才更能说明阻塞点或热点。
9.3 Dump 和录制的风险
| 操作 | 用途 | 风险与要求 |
|---|---|---|
| Thread Dump | 锁、阻塞、热点栈 | 通常风险较低,但高频采集仍有开销 |
| Heap Histogram | 对象数量和大对象初筛 | 可能触发较重统计,繁忙实例谨慎 |
| Heap Dump | 内存泄漏、对象引用链 | 可能暂停应用、产生接近 Heap 大小的文件 |
| JFR | CPU、分配、锁、IO 综合分析 | 开销相对可控,仍需按版本和参数评估 |
Heap Dump 前必须检查剩余磁盘空间、Pod 临时盘限制和敏感数据合规。不要在磁盘即将写满时直接 Dump。
9.4 Arthas 常用路径
dashboard
thread
thread -n 5
thread --state BLOCKED
jvm
memory
sc -d com.example.OrderService
watch com.example.OrderService createOrder '{params,returnObj,throwExp}' -x 2
trace com.example.OrderService createOrder '#cost > 100'
tt -t com.example.OrderService createOrderwatch、trace、tt 会增强字节码并采集运行数据。线上必须限制类、方法、条件、展开深度和持续时间;参数或返回值可能包含敏感数据。排查后撤销增强并退出,不能把无条件 trace 长时间挂在高 QPS 方法上。
十、典型故障一:接口突然变慢
排查顺序:
- 看 QPS 是否变化,P95/P99 从何时升高。
- 按 URI 模板、实例、状态码拆分,判断单接口还是全局。
- 看 Trace 的服务端、数据库、Redis、HTTP 客户端 Span。
- 看 Tomcat/Jetty 线程、业务线程池、Hikari pending。
- 看 CPU、GC、容器 throttling 和网络。
- 查慢 SQL、锁等待、下游超时和重试放大。
| 指标组合 | 更可能的原因 |
|---|---|
| QPS 不变,P99 高,Hikari pending 高 | 慢 SQL、长事务、连接池不足或泄漏 |
| QPS 高,线程池队列持续增长 | 容量不足或下游变慢导致排队 |
| 单实例 P99 高,其他正常 | 单实例 GC、热点、网络或坏节点 |
| 所有服务同时变慢 | 共享 DB、Redis、网络、网关或基础设施 |
| 客户端 504,应用仍在执行 | 超时层级不一致,请求超时后未被取消 |
十一、典型故障二:CPU 飙高
flowchart TD
A["CPU持续高"] --> B{"GC CPU是否同时高"}
B -- "是" --> C["看分配速率、老年代、GC日志"]
B -- "否" --> D["定位高CPU线程"]
D --> E["top -H -p PID"]
E --> F["线程ID转十六进制"]
F --> G["在线程栈中匹配nid"]
G --> H["多次快照确认热点调用栈"]Linux 线程 ID 转十六进制:
printf '%x\n' <tid>然后在线程栈中搜索对应 nid=0x...。常见根因包括死循环、低效正则、超大 JSON、加解密、日志格式化、频繁 GC、自旋锁和异常风暴。
容器中还要检查 CPU limit 和 throttling。应用使用率看似不高,也可能因为额度过小被频繁节流。
十二、典型故障三:内存上涨和 OOM
先区分:
| 内存 | 可能来源 |
|---|---|
| Java Heap | 缓存无界、集合持有、消息积压、大对象 |
| Metaspace | 类加载器泄漏、动态生成类过多 |
| Direct Memory | NIO/Netty 直接内存、Buffer 未及时释放 |
| Thread Stack | 线程过多、每线程栈较大 |
| Native/Agent | JNI、监控 Agent、压缩库等 |
| Page Cache | 文件 IO,由 OS 管理,不等同于 Heap 泄漏 |
判断 Heap 泄漏不能只看 used 上升,应观察多次 Full GC 后的存活基线是否持续抬升。推荐证据链:
- 内存和 GC 趋势。
- GC 日志或 JFR 的分配热点。
- Class Histogram 对比。
- 必要时 Heap Dump,用 MAT 等分析 Dominator Tree、Retained Size 和到 GC Root 的路径。
- 验证是合理缓存、流量增长还是不可释放引用。
发生 OOMKill 但没有 Java OutOfMemoryError 时,优先检查容器内存上限、工作集、退出码和平台事件;进程可能被操作系统直接杀死,来不及打印 Java 异常。
十三、典型故障四:线程池耗尽、死锁和请求堆积
13.1 线程状态不是根因结论
| 状态 | 含义 | 常见场景 |
|---|---|---|
| RUNNABLE | 可运行,也可能在本地 IO | 计算、Socket 读写 |
| BLOCKED | 等待进入 synchronized | 锁竞争 |
| WAITING | 无限等待另一个动作 | park、队列获取、join |
| TIMED_WAITING | 有超时等待 | sleep、带超时的 park/IO |
大量 WAITING 线程可能只是正常线程池空闲;必须结合栈、队列长度、active 数和请求延迟判断。
13.2 线程池指标
| 指标 | 说明 |
|---|---|
| core/max/pool size | 配置与当前线程数 |
| active | 正在执行任务数 |
| queue size/capacity | 排队和容量 |
| completed | 完成速率 |
| rejected | 拒绝次数 |
无界队列会把压力转化为内存和延迟,过大的线程池会增加上下文切换并压垮下游。容量需要根据任务的 CPU/IO 比例、下游容量和压测结果确定。
13.3 死锁
jcmd <pid> Thread.print -l 或 jstack -l <pid> 通常会报告 Java 监视器死锁。修复方向包括统一加锁顺序、缩小锁范围、避免持锁调用远程接口、使用超时锁并设计失败处理。
十四、典型故障五:数据库连接池耗尽
关注 Hikari:
- active、idle、max。
- pending threads。
- connection acquire time。
- timeout 次数。
- 数据库端连接数、运行 SQL、锁等待和慢查询。
| 证据 | 可能原因 | 处理方向 |
|---|---|---|
| active 接近 max,pending 增长,慢 SQL 增多 | SQL 或数据库变慢 | 索引、执行计划、锁、数据库资源 |
| active 长期不下降,线程栈停在业务事务 | 长事务 | 缩小事务边界,禁止事务内慢远程调用 |
| 泄漏检测提示连接未归还 | 连接泄漏 | 检查手工 JDBC、流式查询和异常路径 |
| 扩大池后 DB 更慢 | 数据库已饱和 | 恢复池大小、限流、优化 DB |
| 获取连接慢但 SQL 快 | 池太小或请求并发过高 | 结合 DB 容量调整池和入口并发 |
连接池不是越大越好。所有实例的池上限之和必须小于数据库可承受连接数,并为管理、迁移和其他服务留余量。
十五、典型故障六:慢 SQL、锁等待和事务问题
应用侧先拿到:SQL 模板、耗时、调用入口、事务范围、连接等待时间。数据库侧再确认:
- 是否使用预期索引,估算行数与实际行数差多少。
- 是否出现全表扫描、临时表、排序和回表过多。
- 是否存在行锁、间隙锁、元数据锁等待。
- 事务是否长期未提交,是否包含远程调用。
- 数据量、参数分布、统计信息是否变化。
不要在线上日志输出拼接后的完整敏感 SQL;使用 SQL 模板、耗时、影响行数和安全的参数摘要。EXPLAIN ANALYZE 会实际执行查询,不能不评估成本就在生产对写语句或大查询使用。
十六、典型故障七:Redis、MQ 和下游接口
16.1 Redis
| 现象 | 排查 |
|---|---|
| 超时升高 | Redis CPU、慢命令、网络、连接池等待、大 Key |
| 命中率下降 | Key 过期、缓存穿透、版本或前缀变化 |
| 应用线程堆积 | 客户端连接池、超时和重试配置 |
| 内存突增 | Key 数量、大 Key、淘汰策略、TTL |
禁止在大实例生产环境随意执行阻塞式全量命令。Key 分析优先使用渐进扫描、采样和平台工具。
16.2 MQ
| 指标 | 解释 |
|---|---|
| 生产/消费速率 | 判断入口增长还是消费下降 |
| Lag/积压量 | 当前欠账 |
| 最老消息年龄 | 用户实际等待时间,通常比单看数量更重要 |
| 重试和死信 | 业务失败或毒消息 |
| 消费耗时 | DB、下游或业务处理变慢 |
扩容消费者前确认分区/队列并行度和下游容量。否则消费者增加也没有并行度,或会把数据库进一步压垮。
16.3 HTTP 下游
同时看连接建立、连接池等待、TLS、首字节、读取耗时、状态码和重试。超时必须分层设计:连接超时、读取超时、整体调用超时、入口超时之间要留出处理和降级时间。
重试只适合可安全重试的操作,并必须有次数、退避、抖动和总时限;无界重试会制造流量放大。
十七、典型故障八:502、504、404 和连接拒绝
| 状态/现象 | 常见含义 | 排查方向 |
|---|---|---|
| 502 | 网关无法获得有效上游响应 | 实例退出、连接重置、协议错误、路由 |
| 504 | 网关等待上游超时 | 应用慢、下游慢、超时层级不一致 |
| 404 | 路由或应用映射不存在 | context-path、网关 Rewrite、版本发布 |
| Connection refused | 目标端口无监听或被拒绝 | 进程、端口、readiness、网络策略 |
| Connection reset | 对端或中间设备关闭连接 | 重启、超时、协议、负载均衡 |
确认错误由哪一层生成非常重要。网关返回的 504 不等于 Spring Boot 返回了 504;应用可能仍在后台执行并最终写数据库,引发客户端重试和重复业务。
十八、Kubernetes 和容器排查
kubectl get pod -n <ns> -o wide
kubectl describe pod <pod> -n <ns>
kubectl logs <pod> -n <ns> --since=30m
kubectl logs <pod> -n <ns> --previous
kubectl top pod <pod> -n <ns> --containers
kubectl get events -n <ns> --sort-by=.lastTimestamp
kubectl get deploy <deploy> -n <ns> -o yaml
kubectl rollout history deploy/<deploy> -n <ns>重点判断:
| 现象 | 证据 |
|---|---|
| OOMKilled | Pod last state、退出码、容器内存曲线 |
| CrashLoopBackOff | 当前日志、--previous、探针和启动命令 |
| readiness 失败 | 探针响应、应用启动阶段、依赖健康 |
| liveness 反复重启 | 探针是否过重、超时过短、GC 停顿 |
| CPU throttling | CPU limit 与 throttled 指标 |
| Evicted | 节点内存/磁盘压力和事件 |
不要把 liveness 配成数据库深度检查。数据库短暂异常若触发所有应用重启,可能形成重连风暴;依赖不可用通常更适合影响 readiness,让实例摘流量但保留诊断现场。
十九、启动失败和启动慢
19.1 启动失败
| 最后阶段 | 排查入口 |
|---|---|
| 配置加载前 | JVM 参数、命令行、Jar、Class 版本 |
| Environment | Profile、配置中心、占位符、解密 |
| Bean 创建 | 最内层 Caused by、循环依赖、条件配置 |
| WebServer | 端口占用、证书、Connector 配置 |
| Runner | 外部连接、初始化任务、阻塞逻辑 |
自动配置问题查看 Condition Evaluation Report、/conditions、/env 和 /configprops。不要只搜索异常第一行,要找到最内层根因和第一个业务栈。
19.2 启动慢
检查配置中心和 DNS、数据库连接、类路径扫描、Bean 初始化、@PostConstruct、数据迁移、Runner、远程预热和 ApplicationStartup 记录。初始化逻辑应设置超时,非关键预热不要无限阻塞 readiness。
二十、发布后异常怎么判断
发布前后对比:
- 应用版本、配置版本、JDK、基础镜像。
- 流量和实例数量是否一致。
- 错误率、延迟、CPU、内存和 GC。
- 数据库连接、SQL 模板和下游调用。
- 新旧版本是否同时存在,异常是否只出现在新版本。
金丝雀实例异常而旧实例正常,是非常强的版本相关证据,但仍要排除节点、流量标签和数据分片差异。回滚后恢复说明变更与故障相关,不自动证明具体代码根因。
二十一、线上操作红线
| 高风险动作 | 风险 | 安全做法 |
|---|---|---|
| 全量重启 | 丢现场、流量雪崩 | 单实例摘流量、留现场、滚动处理 |
| 全实例 DEBUG | CPU 和磁盘暴涨、泄密 | 单包、单实例、短时、有审计 |
| 随意 Heap Dump | STW、磁盘写满 | 查空间、评估暂停、走安全通道 |
| 无条件 Arthas trace | 高 QPS 下开销巨大 | 加方法和耗时条件,短时观察 |
| 盲目扩大连接池 | 压垮数据库 | 先确认 DB 容量和慢 SQL |
| 盲目扩容应用 | 放大共享依赖压力 | 确认瓶颈层后再扩 |
| 直接改生产数据 | 数据不可逆 | 审批、备份、影响行预览、事务和回滚方案 |
| 删除日志腾空间 | 丢失证据 | 先归档,修复日志洪峰和轮转策略 |
二十二、故障复盘模板
| 部分 | 必须回答 |
|---|---|
| 摘要 | 何时发生、持续多久、影响什么 |
| 影响 | 用户数、订单数、SLO、数据一致性 |
| 时间线 | 告警、响应、判断、操作、恢复时间 |
| 根因 | 技术根因和触发条件,证据是什么 |
| 放大因素 | 重试、缺少限流、告警延迟、操作失误 |
| 止损评价 | 哪个动作有效,哪个动作无效或有副作用 |
| 修复 | 代码、配置、容量、架构和数据修复 |
| 防复发 | 测试、监控、告警、Runbook、演练 |
| 负责人 | 每项行动、截止时间和验收方式 |
复盘不以“某个人写错代码”为结论,而要追问为什么测试、评审、灰度、监控和保护机制都没有更早阻断。
二十三、面试高频题与回答主线
23.1 线上接口突然变慢怎么排查
先确认时间窗、影响接口和实例范围;看 QPS、错误率、P95/P99 判断是否流量变化。再按实例拆分 CPU、GC、线程池和连接池,通过 Trace 判断耗时在应用、SQL、Redis 还是下游。若线程池排队或 Hikari pending 升高,结合连续线程栈、慢 SQL和数据库锁等待验证。止损优先限流、降级、摘异常实例或回滚,恢复后继续观察核心业务和资源指标。
23.2 CPU 100% 怎么排查
先判断进程 CPU、容器 throttling 和 GC CPU。若不是 GC,用 top -H -p 找高 CPU 线程,将线程 ID 转十六进制后匹配连续线程栈;也可用 JFR 或受控 Arthas 定位热点方法。常见原因是死循环、低效计算、异常风暴、日志和高分配率。不能只凭一次线程栈下结论。
23.3 内存持续上涨就是泄漏吗
不一定。可能是流量导致 Heap 扩容、合理缓存、直接内存、线程栈或 Page Cache。Heap 泄漏要看 Full GC 后存活基线是否持续上涨,再结合 GC 日志、Histogram、JFR 分配热点和必要的 Heap Dump 引用链证明。
23.4 为什么 Actuator 不是完整监控系统
Actuator 提供健康、指标和诊断端点,Micrometer 提供观测门面,但不负责长期存储、可视化、聚合告警和跨服务检索。生产还需要 Prometheus/Grafana、日志平台、Trace 后端和告警值班体系。
23.5 Metrics、Logs、Traces 如何配合
Metrics 用于发现异常趋势和范围;Trace 还原单次跨服务调用并定位慢 Span;Logs 提供异常栈和业务事件上下文。通常先由告警确定时间窗和实例,再查 Trace,最后通过 traceId 定位日志。
23.6 为什么不能看到服务异常就重启
重启可能暂时恢复,但会丢失线程、连接和内存现场,掩盖根因;全量重启还可能造成流量抖动和依赖重连风暴。应先确认影响并采集低风险证据,优先摘除单实例、限流、降级或回滚,确需重启时滚动执行并验证。
二十四、一页纸值班清单
收到告警
- [ ] 确认环境、服务、接口、实例、开始时间和影响范围。
- [ ] 查看最近发布、配置、扩缩容、DDL 和定时任务。
- [ ] 保存告警、看板、日志和 Trace 链接。
- [ ] 指定现场负责人、操作人和同步渠道。
定位
- [ ] 看 Rate、Errors、Duration 和资源 Saturation。
- [ ] 判断单实例、单接口还是共享依赖。
- [ ] 用 traceId 关联网关、应用和下游。
- [ ] 必要时采集三次线程栈、GC/JFR 或受控 Dump。
- [ ] 所有判断写出支持证据和反证。
止损与恢复
- [ ] 选择可回退的限流、降级、摘流量、扩容或回滚。
- [ ] 记录操作时间、对象、参数和执行人。
- [ ] 验证错误率、P99、业务成功率和积压恢复。
- [ ] 防止重试、补偿任务产生第二波冲击。
事后
- [ ] 修复根因,不把重启当修复。
- [ ] 补测试、容量基线、监控、告警和 Runbook。
- [ ] 完成数据补偿和影响确认。
- [ ] 复盘行动项有负责人、期限和验收标准。
二十五、关联学习
| 专题 | 继续学习什么 |
|---|---|
| Actuator监控 | 端点、HealthIndicator、Micrometer 和 Prometheus 接入代码 |
| Spring Boot Admin | 多实例Actuator集中展示、注册发现、认证和通知 |
| 从零到生产级掌握 | Spring Boot 完整工程主线 |
| 配置体系全过程 | Profile、属性来源、绑定失败和最终值排查 |
| 自动配置与扩展链 | conditions、Bean 和自动装配诊断 |
| Java 17+与Boot 3 | Observation、Tracing 和版本迁移 |
| JVM性能调优 | GC、线程、内存和 JVM 参数 |
| MySQL监控与故障排查 | 慢 SQL、锁、连接和数据库资源 |
本章小结
生产监控的目标不是堆工具,而是缩短发现、定位和恢复时间。一个成熟的 Spring Boot 服务应该能够用指标发现异常、用 Trace 确定慢在哪一段、用日志解释发生了什么,并通过线程栈、GC、数据库和容器证据完成验证。真正专业的线上排查必须同时做到三点:保留证据、控制操作风险、用业务指标证明恢复。
