首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

ShutdownHook-java中优雅地停止服务

1.什么是ShutdownHook

在Java程序中可以通过添加关闭钩子,实现在程序退出时关闭资源、平滑退出的功能。使用Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在以下几种场景被调用:

程序正常退出

使用System.exit()

终端使用Ctrl+C触发的中断

系统关闭

使用Kill pid命令干掉进程

2.java进程平滑退出的意义

很多时候,我们会有这样的一些场景,比如说nginx反向代理若干个负载均衡的web容器,又或者微服务架构中存在的若干个服务节点,需要进行无间断的升级发布。在重启服务的时候,除非我们去变更nginx的配置,否则重启很可能会导致正在执行的线程突然中断,本来应该要完成的事情只完成了一半,并且调用方出现错误警告。如果能有一种简单的方式,能够让进程在退出时能执行完当前正在执行的任务,并且让服务的调用方将新的请求定向到其他负载节点,这将会很有意义。自己注册ShutdownHook可以帮助我们实现java进程的平滑退出。

3.java进程平滑退出的思路

(1) 在服务启动时注册自己的ShutdownHook(2) ShutdownHook在被运行时,首先不接收新的请求,或者告诉调用方重定向到其他节点(3) 等待当前的执行线程运行完毕,如果五秒后仍在运行,则强制退出

4.如何屏敝第三方组件的ShutdownHook

4.1问题提出

我们会发现,有一些第三方组件在代码中注册了关闭自身资源的ShutdownHook,这些ShutdownHook对于我们的平滑退出有时候起了反作用。比如dubbo,在static方法块里面注册了自己的关闭钩子,完全不可控。在进程退出时直接就把长连接给断开了,导致当前的执行线程无法正常完成,源码如下:AbstractConfig.java

4.2分析源码

这里先给出两个基本的类的源码:Runtime.java中相关方法源码

ApplicationShutdownHooks.java

从Runtime.java和ApplicationShutdownHooks.java的源码中,我们看到并没有一个可以遍历操作shutdownHook的方法。Runtime.java仅有的一个removeShutdownHook的方法,对于未写线程名的匿名类来说,无法获取对象的引用,也无法分辨出彼此。ApplicationShutdownHooks.java不是public的,类中的hooks也是private的,只有通过反射的方式才能获取并控制它们。

4.3解决方式

通过反射的方式注入自己的ShutdownHook并清除其他Thread

其中ExcludeIdentityHashMap的相关实现如下:定义ExcludeIdentityHashMap类来帮助我们阻止非自己的ShutdownHook注入,只针对服务我们命令规则的XXXHook进行控制ExcludeIdentityHashMap.java

5.实现服务的平滑退出

对于生产业务的服务来说,目前只有这几种任务的入口:Http请求、dubbo请求、RabbitMQ消费、Quartz任务。

5.1 Http请求

测试发现Jetty容器在stop的时候不能实现平滑退出,springboot默认使用的tomcat容器可以,以下是部分代码示例:TomcatShutdownHook.java

Connector首先parse(),所有新的请求都会收到connection reset的SocketException,nginx和ribbon默认情况下都会重试下一个服务;threadPoolExecutor等待线程池中的线程5秒,结束后退出。源码地址:https://github.com/shunyang/springcloud-starter/tree/master/tomcat-shutdown-spring-boot-starter

5.2 dubbo请求

尝试了许多次,看了相关的源码,dubbo不支持平滑退出;解决方法只有一个,那就是修改dubbo的源码,以下两个地址有详细介绍:http://frankfan915.iteye.com/blog/2254097https://my.oschina.net/u/1398931/blog/790709

5.3 RabbitMQ消费

以下是SpringBoot的示例,不使用Spring原理也是一样的RabbitShutdownHook.java

5.4 Quartz任务

QuartzShutdownHook.java

6.为何重启时有时会有ClassNotFoundException

springboot通过java -jar example.jar的方式启动项目,在使用脚本restart的时候,首先覆盖旧的jar包,然后stop旧线程,启动新线程,这样就可能会出现此问题。因为在stop的时候,ShutdownHook线程被唤醒,在其执行过程中,某些类(尤其是匿名类)还未加载,这时候就会通知ClassLoader去加载;ClassLoader持有的是旧jar包的文件句柄,虽然新旧jar包的名字路径完全一样,但是ClassLoader仍然是使用open着的旧jar包文件,文件已经找不到了,所以类加载不了就ClassNotFound了。如何解决呢?也许有更优雅的方式,但是我没有找到;但是我们可以简单地把顺序调整一下,先stop、再copy覆盖、最后start,这样就OK了。

欢迎关注我的公众号,获取更多文章,并与我交流沟通。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180425G1723E00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券