springboot + nacos + k8s 优雅停机

2023年 9月 23日 40.7k 0

概念

优雅停机是什么?
网上说的优雅下线、无损下线,都是一个意思。

优雅停机,通常是指在设备、系统或应用程序中止运作前,先执行一定的流程或动作,以确保数据的安全、预防错误并保证系统的整体稳定。  
  
一般来说,优雅停机可以参考以下步骤以实现:    
1. **备份数据**:立即将内存中的所有未保存的修改、缓存等数据保存到数据库或磁盘中。    
2. **停止接收新的请求**
3. **处理未完成的请求**
5. **通知其他依赖组件**
6. **等待所有要素安全退出后,关闭系统**    
在具体实施时,不同的设备、不同的系统、不同的应用,所需要的优雅停机步骤也不尽相同,甚至需要根据不同的场景来选择不同的方法。    
例如,在某些情况下,你可能需要让用户知道,系统即将关闭,并告诉他们应当保存所有的工作并退出系统;而在另一些情况下,你可能需要设计一种策略,能够让系统在无用户介入的情况下,自动保存所有的状态,并在下次启动时恢复之。    
但是,无论在哪种情况下,优雅停机的目标都是保护数据,避免错误,并尽量减少到访用户或使用者的不便。

上面的步骤,其实还缺了不少基础的内容,比如,停止请求外,还要停止接收定时任务、停止接收mq消息,等待他们的完成,这2项都是我们微服务中必不可缺的能力。
因此,我希望通过本文,能够更清晰,更详细的讲解,在我已知的真实业务场景下,如何做优雅停机。
文中,很多内容不会讲得太详细,需要大家有一定的搜索能力或者经验!

参考资料

  • Java 技术栈中间件优雅停机方案设计与实现全景图
  • Spring——项目优雅停机
  • Graceful shutdown and zero downtime deployments in Kubernetes (learnk8s.io)
  • Graceful Shutdown Services in Kubernetes | Thoughtworks
  • Nacos-服务发现
  • k8s容器生命周期回调
    其他就不一一列举了

用案例说话

随着微服务的兴起,运维方式由docker -> k8s 变化,优雅停机涉及到的点就越来越多!
下面,我们用一个案例,说明优雅停机中的问题和问题解决方案。

案例前:k8s 停机流程

当程序员执行 kubectl delete pod 命令时,两个过程开始:

网络规则即将生效: 

  • Kube-apiserver 收到 pod 删除请求,并将 pod 的状态更新为 Extinating at Etcd;
  • 终结点控制器从终结点对象中删除 Pod 的 IP;
  • Kuber-proxy 根据 Endpoint 对象的更改更新 iptables 的规则,并且不再将流量路由到已删除的 pod。
  • 删除容器:

  • Kube-apiserver 收到 pod 删除请求,并将 pod 的状态更新为 Extinating at Etcd;
  • Kubelet 清理节点处的容器相关资源,如存储、网络;
  • 添加 Prestop hook 钩子,等待流量不再发给pod;
  • Kubelet 将SIGTERM发送到容器;
  • 如果容器在默认的 30 秒内没有退出,Kubelet 将发送 SIGKILL 并强制其退出。
  • image.png

    k8s + springboot + nacos 案例

    k8s+springboot+nacos优雅下线-第 2 页.png

  • PreStopHook 做了2件事情:
    a. nacos反注册
    b. 休眠35秒
  • 通过信号量关闭springboot程序;
  • 其中,k8s的 terminationGracePeriodSeconds(宽限期)设置为35s。

    问题

  • springBoot程序关闭时间只有2s, 那么该程序就无法处理完一些线程任务、异步消息、定时任务等。为什么呢?
    宽限期设置了35s,PreStop休眠了35s + 一个请求的时间,超过了宽限期,那么 kubelet 就会给与 pod 增加一次性2s的宽限时间。Pod 的生命周期,2s不管程序是否正常结束,都会被Kill -9。

  • 为什么反注册之后需要休眠35s?
    这里涉及到nacos服务发现原理,nacos服务变更响应时间:实时;ribbon 默认缓存刷新时间30s;因此,一开始是设置30s的,发现还有feign请求失败的情况,所以设置成了35s以解决这个问题!

  • nacos服务变更响应时间真的是实时吗?
    其实并不一定,nacos服务发现是通过http和udp实现的,udp是实时的,http最大等待时间是10s,但是,udp端口生产环境可能没有开放!所以,案例中的nacos服务发现仅通过http定时轮询实现。

  • 案例优化

    上面的案例可以优化的点

  • nacos 反注册后休眠35s,是否可以减少;
  • terminationGracePeriodSeconds 设置多少合理?
  • 优化点1

    反注册后休眠的35s时候受到nacos服务发现 + ribbon 缓存刷新时间影响,正常应该是 服务发现时间 + 缓存刷新时间 40s才能在极端情况下保证服务停机时,不会再有feign 请求进入。
    如果想要缩短这个时间

  • 启用udp,这个需要和运维同学商量,否则10s等待少不了;
  • 监听nacos服务变更通知,发现服务下线后,及时刷新ribbon缓存;
  • /**
    * 订阅 nacos 实例变更通知
    * 手动刷新 ribbon 服务实例缓存
    * nacos client 1.4.0+
    */
    @Component
    @Slf4j
    public class NacosInstancesChangeEventListener extends Subscriber {

    @Resource
    private SpringClientFactory springClientFactory;

    @PostConstruct
    public void registerToNotifyCenter(){
    NotifyCenter.registerSubscriber(this);
    }
    @Override
    public void onEvent(InstancesChangeEvent event) {
    String service = event.getServiceName();
    // service: DEFAULT_GROUP@@demo ribbonService: demo
    String ribbonService = service.substring(service.indexOf("@@") + 2);
    log.info("##### 接收到微服务nacos实例变更事件:{} ribbonServiceName: {}", event.getServiceName(), ribbonService);
    ILoadBalancer loadBalancer = springClientFactory.getLoadBalancer(ribbonService);
    if(loadBalancer != null){
    ((ZoneAwareLoadBalancer) loadBalancer).updateListOfServers();
    log.info("刷新 ribbon 服务实例:{} 缓存成功", ribbonService);
    }
    }

    @Override
    public Class

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论