背景:
服务更新上线或重启,网站会出现请求接口错误,单纯的靠k8s自带的健康检测是无法实现平滑上线,以下为平滑上线的解决方案
一、优雅停机的好处
- 保障业务连续性 :确保正在进行的请求能顺利完成,避免业务中断。
- 优化资源利用 :优雅停机时可及时释放数据库连接池、消息队列连接等资源,提高资源利用率。
- 提升系统稳定性 :平稳过渡到新状态,减少因强制停止引发的异常,降低服务雪崩等故障风险。
- 减少错误与提高容错能力 :通过特定终止逻辑处理未完成任务,减少错误发生,增强系统容错性。
- 便于运维管理 :为运维人员提供控制和灵活性,利于部署新版本、维护等操作,提高运维效率。
- 提高用户体验 :避免服务突然中断,保障用户操作顺利进行,提升整体体验。
- 便于故障排查 :停机前记录日志和状态信息,为后续故障排查提供重要依据。
二、实现原理
服务默认不上线
:通过-Dspring.cloud.nacos.discovery.instanceEnabled=false参数设置服务注册到NACOS默认不上线,启动不接收服务发现流量。startupProbe
:配置容器启动后过一段时间进行请求健康检测接口,健康检测通过后把该服务Nacos的状态设置为上线,接收服务发现流量。liveness
和readiness
探针配合 :liveness 探针判断应用是否存活,及时发现停机过程中的异常情况并重启容器。readiness 探针判断应用是否准备好接收ingress流量。preStop
:配置preStop 钩子在容器停止前发送 HTTP 请求把该服务Nacos的状态设置为下线,不接收服务发现流量。
三、实现步骤
设置服务默认不上线
配置JAVA_OPTS的-Dspring.cloud.nacos.discovery.instanceEnabled
参数为false,即可实现服务启动注册后是下线状态
spec: |
配置startupProbe探针
startupProbe: |
脚本逻辑:
- 请求服务健康检测接口,判断接口返回的状态码是否为200,如果为200请求服务接口把Nacos状态改为上线,因为上线接口为异步接口,睡眠2s后,请求接口查看服务状态是否为上线,如果是上线状态返回为成功,如果不是上线状态退出重启
配置 liveness 和 readiness 探针
配置 liveness 探针,检查应用健康状态的端点,若探测失败则重启容器
livenessProbe: |
配置 readiness 探针,判断应用是否准备好接收流量
readinessProbe: |
配置preStop钩子
设置 preStop 钩子,在容器关闭前执行服务下线
lifecycle: |
可在 Pod 定义中添加 terminationGracePeriodSeconds: 30(可根据实际情况调整时间),确保给 Java 应用足够的时间完成优雅停机逻辑。当30s还未处理完commoand,容器会直接kill掉
⚠️自建Nacos,服务上下线会做主动推送,云服务因安全问题,Nacos SDK 1.X版本不会做主动推送。服务会轮询去Nacos拉取服务状态(时间间隔默认是30s),可观察服务代码和日志确认时间间隔,设置preStop钩子的时间大于该间隔,terminationGracePeriodSeconds时间大于preStop处理时间
通过以上步骤,即可在 Kubernetes 中实现 Java 应用的优雅停机,充分发挥其带来的种种优势,保障应用的稳定运行和良好的用户体验。
四、扩展
Nacos服务上下线的两种方式
1. 请求Nacos的API接口
使用 curl 请求 Nacos 接口无需额外配置,其默认支持,但需确保以下参数正确无误:
- 必要参数 :nacos 的 IP 和端口,服务注册的命名空间、服务名、集群名、分组名,以及服务的实例 IP 和端口。
服务下线
curl -X PUT "http://nacosip:port/nacos/v1/ns/instance?serviceName=myservice&groupName=travel-pro&ip=service_ip&port=service_port&namespaceId=namespace-pro&clusterName=cluster-pro&enabled=false" |
服务上线
curl -X PUT "http://nacosip:port/nacos/v1/ns/instance?serviceName=myservice&groupName=travel-pro&ip=service_ip&port=service_port&namespaceId=namespace-pro&clusterName=cluster-pro&enabled=true" |
2. 基于Spring Cloud的service-registry端点
在配置文件中开启service-registry端点:
management: |
通过curl命令来进行服务状态的修改:上线是UP,下线是DOWN
curl -X POST "http://localhost:$port/actuator/serviceregistry" --header 'Content-Type: application/json' -d '{"status": "DOWN"}' |
⚠️AI查询serviceregistry端口,PUT请求可更改上下线状态,GET请求可获取服务状态,实测GET请求无任何返回,后续由开发新写接口service-registry-status来查询服务状态是否上下线。
springcloud引入actuator的版本的版本不同,端点也会有差异,需与开发确认端点是serviceregistry还是service-registry
优雅停机清理资源
结合service-registry端点与Shutdown Hook实现资源的完美清理
- service-registry端点是Spring Cloud提供的一个强大工具,用于管理和监控服务注册状态。通过该端点,我们可以方便地查看和修改服务实例在服务注册中心的状态。例如,当服务需要下线进行维护或更新时,我们可以通过发送HTTP请求将服务状态设置为DOWN或OUT_OF_SERVICE,从而通知其他服务该实例暂时不可用。
- Shutdown Hook是Java提供的一种机制,允许我们在应用停止时执行清理工作。通过Runtime.getRuntime().addShutdownHook()添加Shutdown Hook线程我们可以在其中实现诸如关闭数据库连接池、关闭线程池以及等待正在处理的请求完成等操作。这些操作确保了应用在停止时不会出现资源泄漏或任务中断的问题。
优雅停机的协同流程
服务下线流程
- 设置服务状态:通过调用service-registry端点,将服务状态设置为DOWN,使服务不再接收新的请求。
- 执行清理工作:在应用的Shutdown Hook中,执行内部资源的清理逻辑,如关闭数据库连接池、关闭线程池等。
- 停止服务实例:完成上述步骤后,服务实例可以安全地停止。
服务重启或更新流程
- 设置服务状态:在服务重启或更新前,通过service-registry端点将服务状态设置为DOWN。
- 执行清理工作:触发Shutdown Hook中的清理逻辑,确保资源被正确释放。
- 重启服务:重新启动服务实例。
- 恢复服务状态:通过service-registry端点将服务状态重新设置为UP,使服务恢复对外提供服务。
以下是一个简单的示例,展示如何在Spring Boot应用中添加Shutdown Hook以实现优雅停机:
import org.springframework.boot.SpringApplication; |
参考文档: