别再用 kill -9了,这才是微服务上 下线的正确姿势

对于微服务来说,服务的优雅上下线是必要的 。
就上线来说,如果组件或者容器没有启动成功,就不应该对外暴露服务,对于下线来说,如果机器已经停机了,就应该保证服务已下线,如此可避免上游流量进入不健康的机器 。
优雅下线基础下线(Spring/SpringBoot/内置容器)首先JVM本身是支持通过shutdownHook的方式优雅停机的 。

别再用 kill -9了,这才是微服务上 下线的正确姿势

文章插图
 
此方式支持在以下几种场景下优雅停机:
  1. 程序正常退出
  2. 使用System.exit()
  3. 终端使用Ctrl+C
  4. 使用Kill pid干掉进程
那么如果你偏偏要kill -9 程序肯定是不知所措的 。
而在Springboot中,其实已经帮你实现好了一个shutdownHook,支持响应Ctrl+c或者kill -15 TERM信号 。
随便启动一个应用,然后Ctrl+c一下,观察日志就可知,它在 AnnotationConfigEmbeddedWebApplicationContext 这个类中打印出了疑似Closing...的日志,真正的实现逻辑在其父类 AbstractApplicationContext 中(这个其实是spring中的类,意味着什么呢,在spring中就支持了对优雅停机的扩展) 。
别再用 kill -9了,这才是微服务上 下线的正确姿势

文章插图
 
我们能对它做些什么呢,其实很明显,在doClose方法中它发布了一个ContextClosedEvent的方法,不就是给我们扩展用的么 。
于是我们可以写个监听器监听ContextClosedEvent,在发生事件的时候做下线逻辑,对微服务来说即是从注册中心中注销掉服务 。
别再用 kill -9了,这才是微服务上 下线的正确姿势

文章插图
 
可能会有疑问的是,微服务中一般来说,注销服务往往是优雅下线的第一步,接着才会执行停机操作,那么这个时候流量进来怎么办呢?
个人会建议是,在注销服务之后就可开启请求挡板拒绝流量了,通过微服务框架本身的故障转移功能去处理被拒绝的流量即可 。另外,关注公众号JAVA技术栈,在后台回复:面试,可以获取我整理的 Java、Spring Boot 系列面试题和答案,非常齐全 。
Docker中的下线好有人说了,我用docker部署服务,支不支持优雅下线 。
那来看看docker的一些停止命令都会干些啥:
一般来说,正常人可能会用docker stop或者docker kill 命令去关闭容器(当然如果上一步注册了USR2自定义信息,可能会通过docker exec kill -12去关闭) 。
对于docker stop来说,它会发一个SIGTERM(kill -15 term信息)给容器的PID1进程,并且默认会等待10s,再发送一个SIGKILL(kill -9 信息)给PID1 。
那么很明显,docker stop允许程序有个默认10s的反应时间去做一下优雅停机的操作,程序只要能对kill -15 信号做些反应就好了,如上一步描述 。那么这是比较良好的方式 。
当然如果shutdownHook方法执行了个50s,那肯定不优雅了 。可以通过docker stop -t 加上等待时间 。
外置容器的shutdown脚本(Jetty)如果非要用外置容器方式部署(个人认为浪费资源并提升复杂度) 。那么能不能优雅停机呢 。
可以当然也是可以的,这里有两种方式:
首先RPC框架本身提供优雅上下线接口,以供调用来结束整个应用的生命周期,并且提供扩展点供开发者自定义服务下线自身的停机逻辑 。同时调用该接口的操作会封装成一个preStop操作固化在jetty或者其他容器的shutdown脚本中,保证在容器停止之前先调用下线接口结束掉整个应用的生命周期 。shutdown脚本中执行类发起下线服务 -> 关闭端口 -> 检查下线服务直至完成 -> 关闭容器的流程 。
而更简单的另一种方法是直接在脚本中加入kill -15命令 。
别再用 kill -9了,这才是微服务上 下线的正确姿势

文章插图
 
优雅上线优雅上线相对来说可能会更加困难一些,因为没有什么默认的实现方式,但是总之呢,一个原则就是确保端口存在之后才上线服务 。
springboot内置容器优雅上线这个就很简单了,并且业界在应用层面的优雅上线均是在内置容器的前提下实现的,并且还可以配合一些列健康检查做文章 。Spring Boot 优雅关闭新姿势,看看这篇 。
参看sofa-boot的健康检查的源码,它会在程序启动的时候先对springboot的组件做一些健康检查,然后再对它自己搞得sofa的一些中间件做健康检查,整个健康检查的流程完毕之后(sofaboot 目前是没法对自身应用层面做健康检查的,它有写相关接口,但是写死了port is ready...)才会暴露服务或者说优雅上线,那么它健康检查的时机是什么时候呢:


推荐阅读