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/prometheusPrometheus 格式对接 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

流程:

  1. 收到 SIGTERM → 停止接收新请求
  2. 等待进行中的请求处理完(最多等 30s)
  3. 超时后强制关闭

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 注入不同的环境变量即可。”