这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 优雅上线确保实例准备好才接流量;优雅下线确保实例停止前排空流量、处理完任务并释放资源。否则发布时会出现连接拒绝、502、请求中断和消息重复。
29.1 问题场景
上线:
Pod 启动完成
-> 端口已监听
-> 但缓存未预热 / 数据源未就绪
-> 被导入流量
-> 请求超时
下线:
Pod 收到 SIGTERM
-> 立即关闭连接
-> 在途请求失败
-> 消费位移未提交
-> 任务未完成
29.2 启动就绪
Spring Boot 就绪事件:
@Component
public class WarmUpRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
warmUpLocalCache();
}
}
要求:
- readiness 在预热完成后通过;
- 慢预热异步化或分批;
- 外部依赖检查不阻塞过久;
- 启动超时合理;
- 失败策略明确。
29.3 Kubernetes 探针
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 2
failureThreshold: 60
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 2
发布顺序:
1. 新 Pod 启动
2. startup 通过
3. readiness 通过
4. Service 导流量
5. 旧 Pod 缩容
maxUnavailable: 0 可以避免发布过程容量下降。
29.4 优雅停机配置
Spring Boot:
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
Kubernetes:
terminationGracePeriodSeconds: 45
流程:
SIGTERM
-> Pod Endpoint 被移除
-> readiness 变为 not ready
-> 停止接收新请求
-> 等待在途请求
-> 关闭 Bean 和线程池
-> 进程退出
应用总等待时间必须小于 terminationGracePeriodSeconds,留出余量。
29.5 preStop
由于 Endpoint 更新和负载均衡刷新有延迟,可增加:
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
作用:
- 等待服务发现摘流;
- 避免旧实例仍被调用;
- 给网关和客户端缓存同步时间。
sleep 只是简单方案,更完善方案由平台注入下线钩子。
29.6 关闭线程池
@PreDestroy
public void shutdown() throws InterruptedException {
executor.shutdown();
if (!executor.awaitTermination(20, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
}
任务要求:
- 支持中断;
- 有执行时长上限;
- 未完成任务持久化或记录;
- 幂等可恢复;
- 关闭日志记录剩余任务数。
29.7 消费者下线
流程:
1. 停止拉取新消息
2. 处理完当前批次
3. 提交 offset
4. 关闭 consumer
注意:
- 处理超时必须小于总停机时间;
- 位移提交失败会重复消费;
- 消费必须幂等;
- 不能在处理一半时 shutdownNow;
- 长事务要拆批。
29.8 定时任务下线
要求:
- 停止调度新任务;
- 正在执行的任务有最大时长;
- 分布式锁要能释放;
- 执行状态持久化;
- 未完成任务可恢复。
ShedLock 示例:
@Scheduled(cron = "0 */5 * * * *")
@SchedulerLock(name = "settlement", lockAtMostFor = "10m")
public void settle() {
}
停机时不要强杀,应等待当前分片完成或记录断点。
29.9 验证
发布压测:
1. 持续压测请求
2. 滚动发布服务
3. 观察错误率
4. 观察连接拒绝
5. 观察消息重复
6. 观察日志和 trace
验收标准:
- 发布期间 5xx 不增加;
- 无 Connection refused;
- 无未完成请求中断;
- 消息无丢失;
- 定时任务无重复执行;
- Pod 正常退出而不是 SIGKILL。
本章小结
优雅上下线是发布稳定性的基础。上线依赖 readiness 和预热策略,下线依赖 SIGTERM、摘流延迟、请求排空、线程池关闭、消息位移提交和任务状态持久化。总停机时间必须小于平台宽限期,并通过发布压测验证。
思考题
- 为什么端口可访问不等于应用可服务?
- preStop sleep 解决什么问题?
- 优雅停机时间如何设置?
- 消费者下线时为什么要先停止拉取?
- 如何验证发布过程零请求失败?