线上项目需求不停迭代,在版本升级过程中我们要避免请求的失败,整体微服务平滑的升级。我认为一个比较优雅的停机过程应该是,服务在注册中心下线->容器避免接受新请求->等待当前请求持续处理完毕->销毁bean等其余收尾工作。下面我将阐述下较为合理的优雅关机的实现方案。
Kill命令
日常我们停止一个进程的方法无非是采用kill的方式,我们来看下kill的解释。
1 | KILL(1) User Commands KILL(1) |
其实在面试的时候经常被问到进程间的通信方式有哪些,烂熟于心的 信号量、信号、管道、Socket等,那么kill的方式就是给进程传递信号来实现通信。默认情况我们直接执行kill -pid 即可,它会向进程传递 SIGTERM(15)信号。kill -l 可以查看传递的所有信号:
1 | 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP |
但是进程是可以不响应SIGTERM,当进程不处理SIGTERM的信号时,这个时候我们可能就会采用kill -9的方式强制杀死进程了。
Spring 关闭流程
优雅关机指的就是,当系统发送了SIGTERM信号后,应用程序就开始进行收尾,不影响业务的情况下完成应用的关闭。
那么我们如何得知并利用SIGTERM信息呢,1、利用底层JVM Runtime.getRuntime().addShutdownHook() 添加自己的钩子函数。2、利用Spring 现有机制,Spring会在关机时发送ContextClosedEvent给到监听器,我们只需要把关闭函数放在监听器里即可。
Spring机制实现在AbstractApplicationContext中,代码如下:
1 | public void registerShutdownHook() { |
1 | protected void doClose() { |
可以看到Spring在registerShutdownHook() 注册了一个调用doClose()方法的钩子,这个钩子首先会CAS的方式去更改Context的状态,然后发布ContextClosedEvent 事件 ,事件发布通过调用 publishEvent 方法:
1 | protected void publishEvent(Object event, @Nullable ResolvableType eventType) { |
这里会采取getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType) 方法,可以看到实现类是SimpleApplicationEventMulticaster
1 |
|
这里我们看到调用Listener的过程是可以并行的,Executor executor = getTaskExecutor() 不过我在debug过程中发现我们现在的配置情况下 executor == null, 就没去深究这个配置是哪个项产生的影响。为null就相当于是单线程顺序调度执行,那这里有个问题是是Listener的排序规则 后面会说。整体的Spring boot的流程说清楚了,看看我们怎么利用吧。
Spring Cloud服务优雅关机实现方案
Spring 官方有个长年的Issue 一直没关闭,里面提供了一种方式。我们来看下:
1 | private static class GracefulShutdown implements TomcatConnectorCustomizer, |
具体的思想就是通过 实现TomcatConnectorCustomizer接口获取到tomcat Connector(知识点:Tomcat的体系结构),在ContextClosedEvent产生时,首先暂停connector,然后获取tomcat线程池,执行Shutdown方法(知识点:线程池Shutdown和ShutdownNow的区别),等待线程任务的结束。
我认为,优雅关机的过程应该是通知注册中心->暂停接收新请求->旧请求执行完毕->其余的清理动作。所以我觉得上面的case在spring cloud的场景下可能有点问题。我做了改写,主要变化点 1、GracefulShutdown Listerner排序在Spring Listener 首位,第一个去执行。2、接收到ContextClosedEvent后 首先去通知注册中心下线。
1 | public class GracefulShutdown implements TomcatConnectorCustomizer, |
Spring boot 2.x 的Configuration这么配即可:
1 | /** |
以上。
上面遗留的小问题、知识点,有兴趣的话可以查查看~