优雅停机:让请求体面地结束
默认情况下,SpringBoot收到停止信号会立即关闭容器,正在处理的请求直接被中断。用户看到的是502或连接重置。优雅停机的思路是:收到停止信号后,先拒绝新请求,给正在处理的请求留出完成时间,再关闭应用。
配置只需两行。在application.yml中:
server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s
server.shutdown: graceful开启优雅停机。timeout-per-shutdown-phase设定最大等待时间,超时则强制关闭。当应用收到SIGTERM,Web服务器会停止接受新连接,等待活跃请求结束。如果30秒内没处理完,才会强制退出。
这个机制在Kubernetes里尤其重要。Pod被删除时,K8s先发送SIGTERM,等待terminationGracePeriodSeconds后发送SIGKILL。如果SpringBoot的优雅停机超时时间小于K8s的宽限期,就能保证请求完整处理。否则,K8s会在请求处理到一半时杀掉进程。建议将timeout-per-shutdown-phase设为25秒,K8s的terminationGracePeriodSeconds设为30秒,留出缓冲。
健康检查:让编排系统准确判断状态
SpringBoot Actuator提供了开箱即用的健康检查端点。引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
默认暴露/actuator/health,返回{"status":"UP"}。但生产环境需要更细粒度的探针。Kubernetes区分liveness和readiness:liveness判断容器是否活着,失败则重启;readiness判断容器是否准备好接收流量,失败则从Service端点摘除。
SpringBoot 2.3+支持探针分组。配置:
management: endpoint: health: probes: enabled: true show-details: always health: livenessstate: enabled: true readinessstate: enabled: true
这样会暴露/actuator/health/liveness和/actuator/health/readiness。liveness检查应用内部状态,比如是否死锁;readiness检查依赖的外部资源,比如数据库、Redis是否可用。如果数据库暂时不可用,readiness应返回DOWN,K8s会停止向该Pod发流量,但不会重启它,等依赖恢复后自动重新加入。
自定义健康指示器
内置检查覆盖了数据库、磁盘、消息队列等常见依赖。如果有自定义组件,实现HealthIndicator接口即可:
@Component public class CustomHealthIndicator implements HealthIndicator { @Override public Health health() { if (isHealthy()) { return Health.up().withDetail("custom", "ok").build(); } return Health.down().withDetail("custom", "failed").build(); } }这样/actuator/health会包含你的自定义状态。注意,liveness探针不应包含外部依赖,否则数据库抖动会导致Pod被反复重启,反而放大故障。liveness只检查应用自身,readiness才检查外部依赖。
生产实践建议
优雅停机和健康检查要配合使用。K8s的preStop钩子可以加一个短暂sleep,等待Service端点更新完成,再发送SIGTERM。否则,Pod从端点摘除和收到停止信号几乎同时发生,可能有少量请求仍被路由过来。加5秒preStop延迟,能消除这个窗口。
另外,健康检查端点不要暴露敏感信息。show-details: always方便调试,但生产环境建议设为when-authorized,只对内部网络或特定角色开放。Actuator端点也应通过management.server.port独立端口暴露,避免和业务端口混用。
优雅停机和健康检查不是银弹,但它们是生产环境的底线。配置得当,发版时用户无感知,故障时编排系统能准确判断,避免雪崩。花十分钟配好这两项,比事后半夜爬起来处理告警划算得多。