若依微服务优雅启停机实战:K8s环境下完美启停策略
1. 实际应用场景背景
在企业级微服务架构中,若依系统在Kubernetes集群运行时,经常面临版本升级、节点维护等场景。实现服务的优雅启停可以避免请求中断和数据丢失,保障业务连续性。
2. 优雅启停的重要性与挑战
2.1 为什么需要优雅启停
在传统的应用部署中,服务的启动和停止往往比较粗暴,可能导致以下问题:
-
正在处理的请求被中断:在服务停止时,如果还有正在处理的请求,这些请求会直接失败,影响用户体验。
-
数据一致性问题:如果服务在处理事务时被强制终止,可能导致数据不一致。
-
资源泄露:未正确关闭的资源(如数据库连接、文件句柄)可能导致资源泄露。
-
连接异常:客户端可能收到连接异常,需要额外的重试逻辑。
2.2 Kubernetes中的启停机制
在Kubernetes中,Pod的生命周期管理通过以下机制实现:
- Readiness Probe:就绪探针,用于控制服务是否接收流量。
- PreStop Hook:在容器终止前执行的钩子,可以用来执行清理工作。
- terminationGracePeriodSeconds:优雅终止宽限期,控制Pod在被强制终止前等待的时间。
- SIGTERM 和 SIGKILL:Kubernetes通过发送SIGTERM信号通知应用准备关闭,等待一段时间后发送SIGKILL强制终止。
3. 若依微服务架构特点
若依微服务系统包含多个核心组件:
- ruoyi-gateway:API网关,负责请求路由和权限控制
- ruoyi-auth:认证授权服务,处理用户登录和Token验证
- ruoyi-system:系统管理服务,包含用户、角色、菜单等基础功能
- ruoyi-web:前端Web服务,提供用户界面
这些服务均基于Spring Boot框架,具有标准的生命周期管理机制。
4. 优雅启动详解
4.1 优雅启动的核心机制
优雅启动的目标是确保服务在完全准备好处理请求之前不接收任何流量。主要通过以下机制实现:
- 就绪探针(Readiness Probe):控制服务何时开始接收流量
- 启动延迟配置:给应用足够的时间完成初始化
- Actuator健康检查:使用Spring Boot Actuator暴露应用状态供外部检查
4.2 Spring Boot Actuator配置
在Spring Boot应用中启用Actuator并暴露健康检查端点,同时配置独立的管理端口:
management:
server:
port: 18080
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
probes:
enabled: true
show-details: always
配置说明:
management.server.port=18080
:将Actuator管理端点暴露在18080端口management.endpoints.web.exposure.include=*
:暴露所有监控端点management.endpoint.health.probes.enabled=true
:启用Kubernetes探针支持management.endpoint.health.show-details=always
:显示详细的健康检查信息
4.3 Kubernetes就绪探针配置
通过合理配置就绪探针实现优雅启动:
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 18080
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
注意:只有在
management.endpoint.health.probes.enabled=true
时,/actuator/health/readiness
和/actuator/health/liveness
才会生效,否则请使用/actuator/health
。
配置说明:
initialDelaySeconds
: 应用启动后等待30秒再开始检查就绪状态periodSeconds
: 每5秒检查一次就绪状态timeoutSeconds
: 每次检查的超时时间为5秒failureThreshold
: 允许连续失败3次后才认为不就绪
4.4 验证优雅启动
# 观察Pod启动过程
kubectl get pods -w -n <namespace>
# 查看Pod日志
kubectl logs -f <pod-name> -n <namespace>
# 检查就绪状态
kubectl get endpoints <service-name> -n <namespace>
5. 优雅停机详解
5.1 优雅停机的核心机制
优雅停机的目标是确保服务在关闭前完成正在处理的请求并正确释放资源。主要通过以下机制实现:
- SIGTERM信号处理:应用接收到终止信号后开始优雅关闭流程
- PreStop Hook:在容器终止前执行清理工作
- terminationGracePeriodSeconds:设置优雅终止的宽限时间
- Spring Boot优雅关闭:确保Web服务器和应用资源正确关闭
5.2 Spring Boot优雅停机配置
在Spring Boot 2.3及以上版本中,可以通过配置启用优雅停机:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
配置说明:
server.shutdown=graceful
:启用Web服务器的优雅关闭spring.lifecycle.timeout-per-shutdown-phase
:设置每个关闭阶段的超时时间为30秒
5.3 Kubernetes部署配置优化
PreStop Hook配置
通过PreStop Hook确保在容器终止前有足够的时间处理未完成的请求:
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
terminationGracePeriodSeconds配置
设置合适的终止宽限期:
terminationGracePeriodSeconds: 60
完整的Deployment配置示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: ruoyi-system
spec:
template:
spec:
containers:
- name: ruoyi-system
image: ruoyi-system:latest
ports:
- containerPort: 8080
name: http
- containerPort: 18080
name: management
env:
- name: SERVER_PORT
value: "8080"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 18080
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
terminationGracePeriodSeconds: 60
5.4 自定义优雅停机处理
实现DisposableBean接口
@Component
public class CustomShutdownHandler implements DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(CustomShutdownHandler.class);
@Override
public void destroy() throws Exception {
logger.info("开始执行自定义关闭逻辑");
// 执行自定义关闭逻辑,如关闭线程池、连接池等
// ...
logger.info("自定义关闭逻辑执行完成");
}
}
使用@PreDestroy注解
@Component
public class ResourceCleanupService {
private static final Logger logger = LoggerFactory.getLogger(ResourceCleanupService.class);
@PreDestroy
public void cleanup() {
logger.info("清理资源");
// 执行资源清理工作
// ...
}
}
监听ContextClosedEvent事件
@Component
public class ApplicationContextCloseListener {
private static final Logger logger = LoggerFactory.getLogger(ApplicationContextCloseListener.class);
@EventListener
public void handleContextClosedEvent(ContextClosedEvent event) {
logger.info("接收到应用上下文关闭事件");
// 处理应用关闭事件
// ...
}
}
5.5 服务注册与发现的优雅处理
Nacos服务注销
在应用关闭时,需要从Nacos注销服务:
@Component
public class NacosServiceRegistryCleaner implements DisposableBean {
@Autowired
private NacosRegistration registration;
@Autowired
private NacosServiceRegistry serviceRegistry;
@Override
public void destroy() throws Exception {
// 从Nacos注销服务
serviceRegistry.deregister(registration);
}
}
注:正常情况下 Spring Cloud Alibaba 会在容器关闭时自动从 Nacos 注销服务,只有在自定义注册流程或手动控制注册行为时才需显式调用
deregister()
。
服务下线前的流量排空
在服务下线前,需要确保现有请求处理完成:
@Component
public class TrafficDrainService {
private volatile boolean shuttingDown = false;
public boolean isShuttingDown() {
return shuttingDown;
}
@PreDestroy
public void prepareShutdown() {
shuttingDown = true;
// 等待现有请求处理完成
try {
Thread.sleep(10000); // 等待10秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public boolean acceptNewRequest() {
return !shuttingDown;
}
}
5.6 Pod删除时序分析
当在Kubernetes中删除一个Pod(例如ruoyi-gateway)时,整个过程
遵循特定的时序以确保优雅停机。以下是以我们配置的ruoyi-gateway
为例的删除过程时序表:
时间点 | 操作主体 | 操作内容 | 控制参数 | 说明 |
---|---|---|---|---|
T+0s | 用户 | 执行删除命令 | - | kubectl delete pod ruoyi-gateway-xxx |
T+0s | Kubernetes API Server | 将Pod状态更新为Terminating | - | Pod不再接收新流量 |
T+0s | Kubelet | 发送SIGTERM信号给应用 | - | 通知应用准备关闭 |
T+0s | Kubelet | 执行PreStop Hook | lifecycle.preStop.exec.command | 执行配置的预停止钩子命令 |
T+0s-T+10s | ruoyi-gateway应用 | 执行优雅关闭流程 | server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=30s | Spring Boot开始优雅关闭,处理完未完成的请求 |
T+10s | PreStop Hook | 执行完成 | lifecycle.preStop.exec.command | sleep 10s命令执行完毕 |
T+10s-T+30s | ruoyi-gateway应用 | 资源清理阶段 | spring.lifecycle.timeout-per-shutdown-phase=30s | 注销Nacos服务、关闭线程池等 |
T+30s | Kubelet | 发送SIGKILL信号 | terminationGracePeriodSeconds | 强制终止容器(达到宽限期上限) |
T+30s | 容器运行时 | 终止容器 | terminationGracePeriodSeconds | 容器被强制关闭 |
T+30s | Kubelet | 从节点上移除Pod | terminationGracePeriodSeconds | Pod从节点上彻底删除 |
T+30s | Kubernetes API Server | 从etcd中删除Pod记录 | terminationGracePeriodSeconds | Pod对象从集群中完全消失 |
整个删除过程的关键控制参数:
-
PreStop Hook中的sleep时间:
- 配置位置:Deployment中的
lifecycle.preStop.exec.command
- 默认值:
["sh", "-c", "sleep 10"]
(10秒) - 可自定义:可以根据应用关闭所需时间调整该值
- 作用:确保在容器真正关闭前有足够时间处理完现有请求
- 配置位置:Deployment中的
-
terminationGracePeriodSeconds(宽限期):
- 配置位置:Pod spec中的
terminationGracePeriodSeconds
- 默认值:30秒(在示例中显式设置为60秒)
- 可自定义:根据应用完全关闭所需的时间调整
- 作用:控制Pod从Terminating状态到被强制终止的总时间
- 配置位置:Pod spec中的
-
Spring Boot优雅关闭配置:
server.shutdown=graceful
:启用Web服务器的优雅关闭spring.lifecycle.timeout-per-shutdown-phase=30s
:设置每个关闭阶段的超时时间
需要注意的是,PreStop Hook的执行是包含在宽限期时间内的。也就是说,
如果宽限期设置为30秒,而PreStop Hook需要10秒执行完成,那么应用实际
进行优雅关闭的时间只有20秒。
在我们的示例配置中:
- PreStop Hook中的sleep时间为10秒(可自定义)
- terminationGracePeriodSeconds设置为30秒(可自定义)
- Spring Boot优雅关闭阶段超时时间为30秒
- 应用实际进行优雅关闭的时间约为20秒
PreStop Hook 的执行时间算入 Kubernetes 的宽限期(terminationGracePeriodSeconds)中,应用的 Spring Boot 关闭超时时间应小于该宽限期,否则可能会被 SIGKILL 强制终止。举例:若 terminationGracePeriodSeconds=60
,PreStop sleep 10
,则 Spring Boot 的关闭超时时间应控制在 50 秒以内,以确保有足够的时间完成优雅关闭。
这些参数应该根据应用的实际需求进行调整:
- 对于处理大量长时间请求的应用,应该增加宽限期时间和Spring Boot关闭超时时间
- 对于需要执行复杂清理工作的应用,应该增加PreStop Hook执行时间
- 对于简单应用,可以适当减少这些时间以加快发布速度
5.7 验证优雅停止
# 执行滚动更新
kubectl rollout restart deployment/<deployment-name> -n <namespace>
# 观察Pod终止过程
kubectl get pods -w -n <namespace>
# 查看Pod终止日志
kubectl logs <pod-name> -n <namespace> --previous
6. 最佳实践建议
6.1 配置建议
- 合理设置超时时间:根据应用处理请求的平均时间设置合适的超时时间
- 使用PreStop Hook:在PreStop Hook中添加适当的延迟,确保流量切换完成
- 监控关键指标:监控服务启动和停止过程中的关键指标
6.2 开发建议
- 避免阻塞操作:在关闭过程中避免执行长时间阻塞操作
- 资源清理:确保所有资源(线程池、连接池等)都能正确关闭
- 日志记录:详细记录启停过程中的关键步骤,便于问题排查
6.3 运维建议
- 灰度发布:通过灰度发布逐步验证优雅启停配置
- 监控告警:建立相关监控和告警机制
- 定期演练:定期进行滚动更新演练,验证配置有效性
7. 故障排查
7.1 常见问题
-
服务停止时仍有请求失败:
- 检查terminationGracePeriodSeconds是否足够长
- 检查PreStop Hook是否正确配置
- 检查负载均衡器的健康检查配置
-
服务启动时接收流量过早:
- 检查就绪探针配置是否合理
- 检查应用启动时间与initialDelaySeconds是否匹配
7.2 排查命令
# 查看Pod详细信息
kubectl describe pod <pod-name> -n <namespace>
# 查看事件信息
kubectl get events -n <namespace>
# 查看日志
kubectl logs <pod-name> -n <namespace>
8. 总结
通过合理的配置和开发实践,可以在Kubernetes环境中实现若依微服务的优雅启停机。关键点包括:
- 优雅启动:通过合理的就绪探针配置确保服务在完全准备好后再接收流量
- 优雅停机:通过SIGTERM信号处理、PreStop Hook和Spring Boot优雅关闭机制确保服务在关闭前完成正在处理的请求并正确释放资源
这些措施可以显著提升系统的稳定性和用户体验,减少因服务启停导致的请求失败和数据不一致问题。
以ruoyi-gateway为例,当执行删除Pod操作时,整个过程会按照预设的时序进行:
- 首先触发PreStop Hook执行10秒的sleep
- 应用在接收到SIGTERM信号后开始执行优雅关闭流程
- 在60秒的宽限期内完成所有清理工作
- 如果超过60秒还未完成,系统会发送SIGKILL信号强制终止
这种机制确保了即使在Pod被删除的情况下,正在处理的请求也能得到妥善处理,服务能够优雅地退出。
评论区