Skip to main content

如何实现项目优雅停机?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。

回答话术

在项目进行发布升级时,通常需要终止旧项目并启动新项目,这个过程中可能会导致服务暂时不可用。为了尽量优雅地发布新版本,并尽可能减少对线上业务的影响,我们需要实现服务的优雅停机和主动下线。

  • 优雅停机:指的是服务接收到停机信号后,不再接收新请求,同时处理完正在执行的请求再关闭服务。
  • 主动下线:指的是微服务部署时,接到服务停机信号后,可以让注册中心(Nacos、Zookeeper 等)主动的剔除自己,并通过注册中心机制通知服务消费者不再进行下线节点调用。

微服务主动下线文章参考:✅ 微服务如何实现主动下线?

在单体项目中,我们只需要保证节点停机的时候,不丢失正在运行的请求以及不会拒绝新的请求即可。也就是说,保证优雅停机即刻,单体项目没有主动下线的场景。

实现优雅停机的话,一般使用 Nginx 直接切换新旧负载,即可实现。SpringBoot 2.3 版本之后,就已经实现了优雅停机功能,如果较低版本则需要额外扩展。流程如下图:

image.png

问题详解

1. Linux 如何发送停机信号给服务

服务在进行停机时,不管是部署在虚拟机还是 K8S 内,都需要基于底层操作系统的 Kill 命令来实现停止操作,关于 Kill 命令的几个信号参数如下:

  • SIGINT kill -2:就像你按下 Ctrl+C 时发出的信号,它会被程序及其所有子程序接收到,一般在测试命令的时候会使用到。
  • SIGKILL kill -9:这个信号非常强硬,用来立即结束进程。程序自己无法拦截或忽略它,一般不会在生产环境中使用,除非一些顽固进程杀不掉才会用。
  • SIGTERM kill -15:这个信号发给程序本身,让它自己结束自己。JVM 收到信号后会通知框架进行优雅结束服务,从而实现“优雅停机”。

通常,要终止一个进程,我们应该首选使用 kill pid 而不是 kill -9 pid。这样做的好处是,如果程序设计有优雅的关闭流程,它可以在彻底停止前进行一些必要的收尾工作。

2. Java 底层对停机信号的支持

Java 语言可以用编程式方式创建一个特殊线程,在程序结束时自动运行。

Runtime.getRuntime().addShutdownHook()

这个线程有助于优雅地关闭程序,比如处理一些收尾工作。但要注意几点:这些 Hooks 并行运行且顺序不固定,它应该简短且避免死锁,以免阻止程序正常结束。注意不要在 Hook 线程里用 System.exit(),否则可能会导致程序卡住。

ShutdownHook 主要分为两个阶段,第一个阶段是注册,第二个阶段是执行,如下图:

image.png

举个代码例子:

Thread hook = new Thread(() -> {
System.out.println("自定义停机钩子");
});
Runtime.getRuntime().addShutdownHook(hook);

3. 低版本的框架如何实现

一些应用程序仍在运行早于 SpringBoot 2.3 的版本,其中一些甚至使用 1.x 版本。针对这些应用程序,需要自行开发优雅停机功能。这一功能的关键在于,在系统关闭时通过 ShutdownHook 阻塞 Web 容器的线程池,直至所有在处理中的请求均完成。需要注意的是,不同的 Web 容器具有各自的优雅停机实现方式。

这里参考 GitHub 给出一些参考实现:优雅停机示例 sourl.cn/ubfk2G