引:对于一个鲁棒的Java应用来说,优雅停机 必不可少的。下面我将先介绍Java优雅停机的实现方式,然后介绍在Dubbo中的服务是如何实现优雅停机的。
信号
在Linux中,信号是进程间通讯的一种方式,它采用的是异步机制。当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。
信号的响应动作
每个信号都有自己的响应动作,当接收到信号时,进程会根据信号的响应动作执行相应的操作,信号的响应动作有以下几种:
- 中止进程(Term)
- 忽略信号(Ign)
- 中止进程并保存内存信息(Core)
- 停止进程(Stop)
- 继续运行进程(Cont)
停机信号
在linux上,我们停机主要是使用 kill 的方式。关于停机对应的信号主要有下面两个,也是我们平时经常使用的两种:
信号 | 值 | 动作 | 说明 |
---|---|---|---|
SIGTERM | 15 | Term | 结束程序(可以被捕获、阻塞或忽略) kill的默认信号 |
SIGKILL | 9 | Term | 无条件结束程序(不能被捕获、阻塞或忽略) |
Java停机方式
优雅停机:指的是在应用关闭时能够处理一下“善后”的逻辑,比如
- 关闭 socket 链接
- 清理临时文件
- 发送消息通知给订阅方,告知自己下线
- 将自己将要被销毁的消息通知给子进程
- 各种资源的释放
我们知道在执行kill -9 pid
时是无条件结束程序的,所以在这种情况下我们无法优雅停机,只有在执行kill -15 pid
或者kill pid
时才能实现。但是如果发现:kill -15 pid
无法关闭应用,则可以考虑使用kill -9 pid
,但请事后务必排查出是什么原因导致kill -15 pid
无法关闭。所以在编写停机脚本时也要先kill -15 pid
,如果关闭失败,再执行kill -9 pid
。
Shutdown Hook
先看代码:
1 | public class Main { |
当我们执行kill pid
输出:
1 | JVM 已启动 |
Shutdown Hook 会保证 JVM 一直运行,直到 hook 终止。
SignalHandler
先看代码:
1 | public class Main1 { |
SignalHandler是在sun.misc
下的,就是JDK提供的各种后面啦。
看了上面两种方法,其实实质都是一样的,都是利用信用,在捕获终止信号的时候做一些操作来实现优雅停机。
Dubbo优雅停机
先引入官方文档:
1 | # 原理 |
我们看到了熟悉的ShutdownHook,所以下面我们需要去找ShutdownHook是在哪添加的?
ShutdownHook
Dubbo 的优雅停机 ShutdownHook 在 AbstractConfig
的静态代码块初始化:
1 | static { |
我们看到DubboShutdownHook
继承于Thread
,所以我们需要去看的run方法,就能知道在停机时需要干什么:
1 | public void run() { |
RegistryDestroy
上面我们看到停机之后主要做了两件事情,我们先来看第一件事情,关闭注册中心连接,,取消服务中的服务提供者和消费者的订阅与注册。 我们以Zookeeper
为例:
1 | // org.apache.dubbo.registry.zookeeper.ZookeeperRegistry |
ProtocolDestroy
现在我们来看第二件事:标记为不接收新请求,同时不再发起新的调用请求,即销毁所有通信 ExchangeClient 和 ExchangeServer,其实最终就是关闭NettyServer和Client,这里以Dubbo协议为例:
1 | private void destroyProtocols() { |
ExecutorUtil
其实我们在分析中会看到ExecutorUtil#gracefulShutdown()
这样一个方法,它其实对应的是检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。文档说它用的是Java自带的线程池关闭策略:
1 | public static void gracefulShutdown(Executor executor, int timeout) { |
参考
- 精尽 Dubbo 源码解析 —— 优雅停机