GracefulShutdown(优雅关机/优雅退出)。Tiao是一个专门为女性办理离婚的网站……好家伙,一站式解决女性离婚,这也太专业了吧。看来不仅节目要优雅的收场,就连离婚也要优雅的啊!在计算机中,优雅关机实际上是指程序的关机方案。既然有优雅关机,就一定有不优雅关机。Windows的正常关闭需要打开和关闭Windows计算机。长按电源键强制关机,或直接关闭电源关机。这是一次硬关机。如果操作系统没有收到任何信号,它就会消失,多么不雅!这时候在系统里,或者某些软件在关闭前还没有处理好,比如你加班写了4个小时的PPT还没来得及保存。。。不过一般来说,除了死机,很少有人强制关机,大部分人的操作还是通过电源选项->关机操作,让操作系统自己处理关机。比如Windows在关机前会主动关闭所有应用程序,但很多应用程序会捕获进程的关闭事件,导致无法正常关闭自身,导致系统无法正常关机。比如办公套件,关闭前不保存,会弹出一个框让你保存,这种机制会干扰操作系统的正常关闭。或者你用的是win10,动不动就自己更新系统的那种。如果更新系统时断电强行关机,再开机可能会有惊喜……更新文件写了一半,你猜怎么着?怎么了?网络中的优雅关机是靠不住的!TCP的八脚随笔相信大家都背过,挥四次手才能断开连接,但是挥四次手也是建立在正常关机的前提下的。如果强行拔网线或强行断电,对端无法及时检测到你的断线。如果此时对端继续发包,就会收到错误。可以看到除了优雅的四次挥手,还有心跳的TCPKeepAlive。仅此还不够。应用层要再做一层心跳,同时要正确优雅地处理连接断开和ConnectionReset错误。因此,如果我们在编写网络程序时,必须提供一个关闭机制,在关闭事件中正常关闭socket/server,从而减少更多因关闭而导致的异常问题。如何监听关闭事件?各种语言都会提供这个关闭事件的监听机制,只是用法不一样。在这个关机监视器的帮助下,很容易实现优雅关机。JAVA监控关机JAVA提供了一个简单的关机事件监听机制,可以接收正常关机信号的事件,比如命令行程序下Ctrl+C的退出信号。Runtime.getRuntime().addShutdownHook(newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("关机前...");}}));此配置完成后,在正常关机前,会启动ShutdownHook线程并执行,输出为Beforeshutdown。当然,如果直接强行关闭,比如windows下结束进程,linux下kill-9……大神也监控不了。在C++中,C++中也有类似的实现。只要在atexit函数中注册函数,程序就正常了。注册的fnExit函数可以在关闭前执行。voidfnExit1(void){puts("Exitfunction1.");}voidfnExit2(void){puts("Exitfunction2.");}intmain(){atexit(fnExit1);atexit(fnExit2);(“主功能。”);return0;}shutdown过程中可能遇到的问题想象这样一个场景,一个消息消费逻辑,事务提交成功后向周边系统推送。早就收到了关闭信号,但是由于大量消息的堆积,部分已经堆积在内存队列中,但是并没有执行并行消费处理的逻辑。此时,某些消费者线程提交事务,收到ForceKill信号后,再推送给周围系统,就会出现数据不一致的问题。这个服务的数据已经存储了,但是还没有推送给第三方。。。拿另一个数据库来说,存储引擎有聚簇索引和非聚簇索引的概念。如果Insert语句执行完后,聚簇索引刚刚写入,非聚簇索引还没有写入,进程就被kill掉了。那么两个索引数据就直接不一致了。向上!但是,作为一个存储引擎,它肯定会处理这种不一致的情况。但如果能正常关闭,存储引擎能安全执行,这种不一致的风险就会大大降低。进程停止JAVA进程停止的机制是,直到所有非守护线程都停止,进程才会退出。然后直接给JAVA进程发送一个shutdown信号,进程就可以关闭了吗?当然不是!JAVA中的线程默认是非阻塞线程。只要非守护线程不停止,JVM进程就不会停止。所以收到关闭信号后,你要自己关闭所有的线程,比如线程池……线程中断线程怎么主动关闭呢?不好意思,这个确实不能关闭(stop方法从JAVA1.1开始就被废弃了),只能通过等待线程自己执行,或者加入软状态和中断来实现:privatevolatilebooleanstopped=false;@Overridepublicvoidrun(){while(!stopped&&Thread.interrupted()){//做某事...}}publicvoidstop(){stopped=true;interrupt();}当线程处于WAITTING状态时,interrupt方法会打断WAITTING状态,强行返回并抛出InterruptedException。比如当我们的线程卡在SocketRead操作,或者Object.wait/JUC下的某个锁等待状态时,调用interrupt方法会打断等待状态,直接抛出异常。但是如果线程没有卡在WAITING状态,并且还在线程池中创建,没有软状态,那么上面的关闭策略就不适用了。线程池ThreadPoolExecutor的关闭策略提供了两种关闭方式:shutdown——中断空闲的Worker线程,等待所有任务(线程)完成。因为空闲的Worker线程会处于WAITING状态,中断方式会直接打断WAITING状态,停止这些空闲线程。shutdownNow-中断所有工作线程,无论是否空闲。对于空闲线程,直接和shutdown方法一样停止。对于一个正在工作的Worker线程来说,不一定是WAITING状态,所以interrupt不能保证一定会关闭。注意:大部分的线程池,或者调用线程池的框架,它们默认的关闭策略是调用shutdown而不是shutdownNow,所以执行线程不一定会被打断但是作为业务线程,必须处理**中断异常**。否则万一shutdownAll,或者手动创建的线程中断,业务线程没有及时响应,可能会导致线程完全无法关闭。三方框架的关闭策略除了JDK线程池,一些三方框架/库也会提供一些优雅关闭的方法。Netty中的EventLoopGroup.shutdownGracefully/shutdown——关闭线程池等资源Reddsion中的Redisson.shutdown——关闭连接池中的连接并销毁各种资源ApacheHTTPClient中的CloseableHttpClient.close——关闭连接池中的连接,关闭Evictorthread等。主流成熟的框架都会为你提供优雅的shutdown方法,保证你调用shutdown后,可以销毁资源,关闭它创建的线程/池。尤其是这种涉及到创建线程的三方框架,必须提供正常的关闭方法,否则线程可能无法关闭,导致最终的JVM进程无法正常退出。Tomcat中的GracefulshutdownTomcat的关闭脚本(sh版)设计的非常好,直接告诉你如何关闭:命令:stopStopCatalina,waitingupto5secondsfortheprocesstoendstopnStopCatalina,waitinguptonsecondsfortheprocesstoendstop-forceStopCatalina,waitupto5secondsandthenusekill-KILLifstilln-forceStopCatalina,waituptonsecondsandthenusekill-KILLifstillrunning设计非常灵活,直接提供了4种关闭方式供你选择。在强制模式下,一个SIGTERM信号(kill-15)将被发送到进程。这个信号可以被JVM捕获,注册的ShutdownHook线程就会被执行。等待5秒后,如果进程还在,则强制kill掉。流程如下图所示:然后会执行Tomcat中注册的ShutdownHook线程,手动关闭各种资源,比如Tomcat自身的连接,线程池等。当然,最重要的一步是关闭所有APP://org.apache.catalina.core.StandardContext#stopInternal//关闭所有应用下的所有Filter-filter.destroy();filterStop();//CloseAll所有应用程序下的监听器-listener.contextDestroyed(event);听众停止();借助这两个关闭前的Hook,应用程序可以自己处理关闭,比如XML时代使用的ServletContextListener:
