Spring Boot 运维实战
从运维视角掌握 Spring Boot 应用的 Actuator 监控、外部化配置、内嵌容器调优和优雅上下线
Spring Boot 运维实战
Actuator — 运维的瑞士军刀
启用方式
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml — 生产配置
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus,loggers,env,threaddump,heapdump"
endpoint:
health:
show-details: when-authorized # 不要 always(暴露敏感信息)
probes:
enabled: true # K8s liveness/readiness
metrics:
export:
prometheus:
enabled: true
核心端点速查
| 端点 | 用途 | 运维场景 |
|---|---|---|
/actuator/health | 健康检查 | K8s liveness/readiness probe |
/actuator/health/liveness | 存活探针 | Pod 是否活着 |
/actuator/health/readiness | 就绪探针 | 是否准备好接收流量 |
/actuator/metrics | 指标列表 | 查看所有可用指标 |
/actuator/prometheus | Prometheus 格式 | 对接 Prometheus 采集 |
/actuator/env | 环境变量 | 确认运行时配置 |
/actuator/loggers | 日志级别 | 动态调日志级别(无需重启) |
/actuator/threaddump | 线程 dump | 线上排查死锁/线程池 |
/actuator/heapdump | 堆 dump | 内存泄漏分析 |
/actuator/mappings | 路由映射 | 确认所有 API 路径 |
动态改日志级别
# 临时调高某个包的日志级别,排查完再调回去(无需重启!)
curl -X POST http://localhost:8080/actuator/loggers/com.example \
-H "Content-Type: application/json" \
-d '{"configuredLevel": "DEBUG"}'
# 查看当前日志级别
curl http://localhost:8080/actuator/loggers/com.example
K8s 探针配置
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60 # Spring Boot 启动需要时间
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
外部化配置
配置优先级(从高到低)
1. 命令行参数(--server.port=9090)
2. 操作系统环境变量(SERVER_PORT=9090)
3. application-{profile}.yml
4. application.yml
5. 配置中心(Nacos/Apollo/Spring Cloud Config)
运维常用配置
server:
port: 8080
shutdown: graceful # 优雅关闭(Spring Boot 2.3+)
tomcat:
threads:
max: 200 # 最大工作线程
min-spare: 10 # 最小空闲线程
accept-count: 100 # 等待队列长度
max-connections: 10000 # 最大连接数
connection-timeout: 5000 # 连接超时 ms
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 优雅关闭超时
logging:
level:
root: INFO
com.example: DEBUG # 自己的包可以 DEBUG
file:
path: /opt/logs # 日志文件路径
环境变量覆盖
# 运维最爱——不改代码和配置,直接改环境变量
export SERVER_PORT=9090
export SPRING_DATASOURCE_URL=jdbc:mysql://prod-db:3306/mydb
export LOGGING_LEVEL_COM_EXAMPLE=DEBUG
# 规则:全大写 + 点号→下划线 + 横线→下划线
# spring.datasource.url → SPRING_DATASOURCE_URL
# server.tomcat.threads.max → SERVER_TOMCAT_THREADS_MAX
内嵌 Tomcat 调优
线程池模型
请求 → Acceptor 线程 → 等待队列 → Worker 线程池 → 处理
│ │
accept-count threads.max
100 200
关键公式:最大并发 = max-connections(排队) > accept-count(等线程) > threads.max(处理中)
常见调优
| 场景 | 调整 | 原因 |
|---|---|---|
| 高并发请求 | 调大 threads.max | 更多线程处理请求 |
| 响应慢但 CPU 不高 | 检查 accept-count 是否打满 | 请求在排队等线程 |
| 内存不够 | 调小 threads.max | 每个线程占用 ~1MB 栈空间 |
| 启动后立刻 OOM | 检查 max-connections | 连接数过多占内存 |
连接泄漏排查
# 看 Tomcat 线程状态
curl http://localhost:8080/actuator/metrics/tomcat.threads.busy
curl http://localhost:8080/actuator/metrics/tomcat.threads.current
# 如果 busy 持续等于 current → 线程池打满
# 配合 threaddump 看线程都在干什么
优雅上下线
优雅关闭(Graceful Shutdown)
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
流程:
- 收到 SIGTERM → 停止接收新请求
- 等待进行中的请求处理完(最多等 30s)
- 超时后强制关闭
K8s 环境配置
spec:
terminationGracePeriodSeconds: 40 # 比 spring 配置多 10s 余量
containers:
- lifecycle:
preStop:
exec:
command: ["/bin/sh","-c","sleep 15"]
# 等 15s 让 Service 摘除该 Pod + 处理完存量请求
terminationGracePeriodSeconds>preStop sleep>spring graceful timeout,三者形成时间差保证不丢请求。
就绪探针的正确姿势
// 不要在启动时就标记 ready——等所有初始化完成
@Component
public class ReadinessIndicator implements ApplicationListener<ApplicationReadyEvent> {
private final AtomicBoolean ready = new AtomicBoolean(false);
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
ready.set(true);
}
}
日志管理
Logback 配置要点
<!-- logback-spring.xml -->
<configuration>
<!-- 控制台输出(容器环境) -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出(按天滚动,保留 30 天) -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/opt/logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/opt/logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
容器环境最佳实践
# 日志打到 stdout/stderr,让容器平台采集
ENV LOGGING_FILE_PATH=
# 不写日志文件,全部输出到控制台
# Docker/K8s 日志驱动会采集 stdout
Prometheus 监控接入
management:
metrics:
export:
prometheus:
enabled: true
endpoints:
web:
exposure:
include: prometheus,health,metrics
# K8s PodMonitor
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: spring-app
spec:
selector:
matchLabels:
app: my-app
podMetricsEndpoints:
- port: http
path: /actuator/prometheus
关键 Spring Boot 指标:
jvm_memory_used_bytes— JVM 内存jvm_gc_pause_seconds— GC 暂停时间http_server_requests_seconds— HTTP 请求延迟tomcat_threads_busy_threads— Tomcat 繁忙线程hikaricp_connections_active— 数据库连接池
面试问答
Q1: Actuator 有哪些常用端点?K8s 怎么配探针?
“常用:health(健康检查)、metrics/prometheus(监控指标)、loggers(动态改日志级别)、threaddump(线程 dump)、env(环境变量)。K8s 探针用 health/liveness 做存活探针、health/readiness 做就绪探针,initialDelaySeconds 设 60s——Spring Boot 启动不快的。”
Q2: 怎么在线改日志级别不用重启?
“用 Actuator 的 loggers 端点——POST /actuator/loggers/包名 带 configuredLevel,即刻生效。排查完记得调回去。这招在生产环境临时开 DEBUG 排查问题特别实用——不用重新部署。”
Q3: 优雅上下线怎么做?
“Spring Boot 2.3+ 内置 server.shutdown=graceful——收到终止信号后不再接收新请求,等存量请求处理完再退出。K8s 里配合 preStop hook 延迟摘除 Pod,确保 Service 已经不再转发流量。时间链:terminationGracePeriod(40s) > preStop sleep(15s) > graceful timeout(30s),这样不会丢请求。”
Q4: 怎么排查接口响应慢?
“三步:一看 Actuator metrics 确认是不是所有接口都慢 → 如果是,看 GC(jstat -gc)+ CPU(top -H)+ 线程池(tomcat.threads.busy);二看具体慢接口的数据库查询(慢 SQL 日志)+ 下游调用(Sleuth/Zipkin trace);三看日志有没有异常堆栈或 retry 风暴。”
Q5: 环境变量怎么覆盖 application.yml 的配置?
“Spring Boot 的配置优先级是命令行 > 环境变量 > application.yml。环境变量的规则是全大写 + 点号改下划线——spring.datasource.url 对应 SPRING_DATASOURCE_URL。这对容器化部署特别友好——同一份镜像,不同环境通过 K8s ConfigMap/Secret 注入不同的环境变量即可。”