强制进程退出的副作用:
- 缓存中的数据尚未持久化到磁盘中,导致数据丢失;
- 正在进行文件的write操作,没有更新完成,突然退出,导致文件损坏;
- 线程池的任务队列中尚有接收到的任务还没来得及处理,导致任务丢失;
- 数据库操作已经完成,例如账户余额更新,准备返回应答消息给客户端时,消息尚在通信线程的发送队列中排队等待发送,进程强制退出导致应答消息没有返回给客户端,客户端发起超时重试,会带来重复更新问题;
- 其它问题等…
解决方法:
- 使用ShutdownHook 钩子程序来完成善后工作
- 使用基于信号的进程通知机制
针对线程池的停机:
使用 DisposableBean 接口
public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
implements DisposableBean{
@Override
public void destroy() {
shutdown();
}
/**
* Perform a shutdown on the underlying ExecutorService.
* @see java.util.concurrent.ExecutorService#shutdown()
* @see java.util.concurrent.ExecutorService#shutdownNow()
* @see #awaitTerminationIfNecessary()
*/
public void shutdown() {
if (this.waitForTasksToCompleteOnShutdown) {
this.executor.shutdown();
}
else {
this.executor.shutdownNow();
}
awaitTerminationIfNecessary();
}
/**
* Wait for the executor to terminate, according to the value of the
* {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property.
*/
private void awaitTerminationIfNecessary() {
if (this.awaitTerminationSeconds > 0) {
try {
this.executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS));
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
1 通过 waitForTasksToCompleteOnShutdown 标志来控制是想立刻终止所有任务,还是等待任务执行完成后退出。
2 executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)); 控制等待的时间,防止任务无限期的运行(前面已经强调过了,即使是 shutdownNow 也不能保证线程一定停止运行)。
更多需要我们的思考的优雅停机策略
服务治理框架一般会考虑到优雅停机的问题。通常的做法是事先隔断流量,接着关闭应用。常见的做法是将服务节点从注册中心摘除,订阅者接收通知,移除节点,从而优雅停机;涉及到数据库操作,则可以使用事务的 ACID 特性来保证即使 crash 停机也能保证不出现异常数据,正常下线则更不用说了;又比如消息队列可以依靠 ACK 机制 + 消息持久化,或者是事务消息保障;定时任务较多的服务,处理下线则特别需要注意优雅停机的问题,因为这是一个长时间运行的服务,比其他情况更容易受停机问题的影响,可以使用幂等和标志位的方式来设计定时任务…
事务和 ACK 这类特性的支持,即使是宕机,停电,kill -9 pid 等情况,也可以使服务尽量可靠;而同样需要我们思考的还有 kill -15 pid,正常下线等情况下的停机策略。最后再补充下整理这个问题时,自己对 jvm shutdown hook 的一些理解。
shutdown hook 会保证 JVM 一直运行,直到 hook 终止 (terminated)。这也启示我们,如果接收到 kill -15 pid 命令时,执行阻塞操作,可以做到等待任务执行完成之后再关闭 JVM。同时,也解释了一些应用执行 kill -15 pid 无法退出的问题,没错,中断被阻塞了。
注:若需要平滑停止服务,我们一般可以通过ShutdownHook和Signal来实现。ShutdownHook一般比较难保证关闭任务的执行顺序,这个时候可以考虑使用Signal机制来完全托管我们关闭服务的执行顺序。
参考链接:
https://www.cnkirito.moe/gracefully-shutdown/
网友评论