JVM安全退出(如何优雅的关闭java服务)
https://tech.imdada.cn/2017/06/18/jvm-safe-exit/?utm_source=tuicool&utm_medium=referral
背景
用户:货都到了,购物车里怎么还有刚买的东西,what?
产品:有用户反映,提单完成了,怎么没清购物车,研发赶紧看看是不是有bug啊?
研发:恩,我看看,!@#¥%……&*()一顿狂查,搜嘎,当时在上线,重启应用,异步任务丢了……
产品:能不能行,上线你就丢任务,丢不丢人啊!
研发:…………
上线!重启!你还在为丢失任务而烦恼么?看这里看这里,从此不再丢任务,JVM可以安全退出的
在交易流程中,为了提升服务的性能,我们做了一些异步化的优化,比如更新用户最近使用的收货地址、提单完成后通过MQ去发送各种通知类消息、清理用户的购物车等等这些操作,异步化加快了应用的响应速度同时也带来一个隐患,如何保障异步操作的执行?这个场景主要发生在应用重启时,对于通过线程或线程池进行的异步化,JVM重启时,后台执行的异步操作可能尚未完成。这时,需要通过JVM安全关闭来保证异步操作进行完成后,JVM再执行关闭。
更广泛的说,在Linux上很多应用通常会通过kill -9 pid的方式强制将进程杀掉,这种方式简单高效,因此很多应用的停止脚本经常会选择使用kill -9 pid的方式。强制进程退出,会带来一些副作用,对应用程序而言其效果等同于突然掉电,可能会导致如下一些问题:
- 缓存中的数据尚未持久化到磁盘中,导致数据丢失;
- 正在进行文件的write操作,没有更新完成,突然退出,导致文件损坏;
- 线程池的任务队列中尚有接收到的任务还没来得及处理,导致任务丢失;
- 数据库操作已经完成,例如账户余额更新,准备返回应答消息给客户端时,消息尚在通信线程的发送队列中排队等待发送,进程强制退出导致应答消息没有返回给客户端,客户端发起超时重试,会带来重复更新问题;
- 其它问题等…
这些问题都有可能对我们的业务产生影响,造成不必要的损失,为了避免这些问题,我们需要在JVM关闭时做些扫尾的工作,为此JVM提供了关闭钩子(shutdown hooks)来做这些事情。本文探讨了利用关闭钩子的相关内容。
JVM 关闭
首先,我们了解下哪些情况会导致JVM关闭,如下图
对于强制关闭的几种情况,系统关机,操作系统会通知JVM进程关闭并等待,一旦等待超时,系统会强制中止JVM进程;kill -9、Runtime.halt()、断电、系统crash这些种方式会直接无商量中止JVM进程,JVM完全没有执行扫尾工作的机会。因此对用应用程序而言,我们强烈不建议使用kill -9 这种暴力方式退出。
而对于正常关闭、异常关闭的几种情况,JVM关闭前,都会调用已注册的shutdown hooks,基于这种机制,我们可以将扫尾的工作放在shutdown hooks中,进而使我们的应用程序安全的退出。基于平台通用性的考虑,我们更推荐应用程序使用System.exit(0)这种方式退出JVM。
JVM 与 shutdown hooks 交互流程如下图所示,可以对照源码进一步的学习shutdown hooks工作原理。
Jvm安全退出
对于tomcat类Web应用,我们可以直接通过Runtime.addShutdownHook(Thread hook)注册自定义钩子,在钩子中实现资源的清理;而对于worker类应用,我们可以采用如下的方式安全的退出应用。
基于信号的进程通知机制
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。通俗来讲,信号就是进程间的一种异步通信机制。信号具有平台相关性,Linux平台支持的一些终止进程信号如下所示:
信号名称 | 用途 |
---|---|
SIGKILL | 终止进程,强制杀死进程 |
SIGTERM | 终止进程,软件终止信号 |
SIGTSTP | 停止进程,终端来的停止信号 |
SIGPROF | 终止进程,统计分布图用计时器到时 |
SIGUSR1 | 终止进程,用户定义信号1 |
SIGUSR2 | 终止进程,用户定义信号2 |
SIGINT | 终止进程,中断进程 |
SIGQUIT | 建立CORE文件终止进程,并且生成core文件 |
Windows平台存在一些差异,它的一些信号举例如下所示:
信号名称 | 用途 |
---|---|
SIGINT | Ctrl+C中断 |
SIGTERM | kill发出的软件终止 |
SIGBREAK | Ctrl+Break中断 |
信号选择:为了不干扰正常信号的运作,又能模拟Java异步通知,在Linux上我们需要先选定一种特殊的信号。通过查看信号列表上的描述,发现 SIGUSR1 和 SIGUSR2 是允许用户自定义的信号,我们可以选择SIGUSR2,在Windows上我们可以选择SIGINT。
通过这种信号机制,对应用程序JVM发送特定信号,JVM可以感知并处理该信号,进而可以接受程序退出指令。
安全退出实现
首先看下通用的JVM安全退出的流程图:
第一步,应用进程启动的时候,初始化Signal实例,它的代码示例如下:
1 |
Signal sig = new Signal(getOSSignalType()); |
其中Signal构造函数的参数为String字符串,也就上文介绍的信号量名称。
第二步,根据操作系统的名称来获取对应的信号名称,代码如下:
1 |
private String getOSSignalType() |
判断是否是windows操作系统,如果是则选择SIGINT,接收Ctrl+C中断的指令;否则选择USR2信号,接收SIGUSR2(等价于kill -12 pid)指令。
第三步,将实例化之后的SignalHandler注册到JVM的Signal,一旦JVM进程接收到kill -12 或者 Ctrl+C则回调handle接口,代码示例如下:
1 |
Signal.handle(sig, shutdownHandler); |
其中shutdownHandler实现了SignalHandler接口的handle(Signal sgin)方法,代码示例如下:
1 |
public class ShutdownHandler implements SignalHandler { |
第四步,在接收到信号回调的handle接口中,初始化JVM的ShutdownHook线程,并将其注册到Runtime中,示例代码如下:
1 |
private void registerShutdownHook() |
第五步,接收到进程退出信号后,在回调的handle接口中执行虚拟机的退出操作,示例代码如下:
1 |
Runtime.getRuntime().exit(0); |
JVM退出时,底层会自动检测用户是否注册了ShutdownHook任务,如果有,则会自动执行注册钩子的Run方法,应用只需要在ShutdownHook中执行扫尾工作即可,示例代码如下:
1 |
class ShutdownHook implements Runnable |
通过以上的几个步骤,我们可以轻松实现JVM的安全退出,另外,通常安全退出需要有超时控制机制,例如30S,如果到达超时时间仍然没有完成退出,则由停机脚本直接调用kill -9强制退出。
使用关闭钩子的注意事项
关闭钩子本质上是一个线程(也称为Hook线程),对于一个JVM中注册的多个关闭钩子它们将会并发执行,所以JVM并不保证它们的执行顺序;由于是并发执行的,那么很可能因为代码不当导致出现竞态条件或死锁等问题,为了避免该问题,强烈建议在一个钩子中执行一系列操作。
Hook线程会延迟JVM的关闭时间,这就要求在编写钩子过程中必须要尽可能的减少Hook线程的执行时间,避免hook线程中出现耗时的计算、等待用户I/O等等操作。
- 关闭钩子执行过程中可能被强制打断,比如在操作系统关机时,操作系统会等待进程停止,等待超时,进程仍未停止,操作系统会强制的杀死该进程,在这类情况下,关闭钩子在执行过程中被强制中止。
- 在关闭钩子中,不能执行注册、移除钩子的操作,JVM将关闭钩子序列初始化完毕后,不允许再次添加或者移除已经存在的钩子,否则JVM抛出 IllegalStateException。
- 不能在钩子调用System.exit(),否则卡住JVM的关闭过程,但是可以调用Runtime.halt()。
- Hook线程中同样会抛出异常,对于未捕捉的异常,线程的默认异常处理器处理该异常,不会影响其他hook线程以及JVM正常退出。
总结
为了保障应用重启过程中异步操作的执行,避免强制退出JVM可能产生的各种问题,我们可以采用关闭钩子、自定义信号的方式,主动的通知JVM退出,并在JVM关闭前,执行应用程序的一些扫尾工作,进一步保证应用程序可以安全的退出。
JVM安全退出(如何优雅的关闭java服务)的更多相关文章
- Linux系统下如何优雅地关闭Java进程?
资料出处: http://www.sohu.com/a/329564560_700886 https://www.cnblogs.com/nuccch/p/10903162.html 前言 Linux ...
- 如何优雅的关闭Java线程池
面试中经常会问到,创建一个线程池需要哪些参数啊,线程池的工作原理啊,却很少会问到线程池如何安全关闭的. 也正是因为大家不是很关注这块,即便是工作三四年的人,也会有因为线程池关闭不合理,导致应用无法正常 ...
- 如何优雅地停止Java进程
目录 理解停止Java进程的本质 应该如何正确地停止Java进程 如何注册关闭钩子 使用关闭钩子的注意事项 信号量机制 总结 理解停止Java进程的本质 我们知道,Java程序的运行需要一个运行时环境 ...
- Effective java 系列之更优雅的关闭资源-try-with-resources
背景: 在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,如果我们不在 ...
- Java进阶知识点:更优雅地关闭资源 - try-with-resource
一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制, ...
- Java进阶知识点3:更优雅地关闭资源 - try-with-resource及其异常抑制
一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制, ...
- 更优雅地关闭资源 - try-with-resource及其异常抑制
原文:https://www.cnblogs.com/itZhy/p/7636615.html 一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在 ...
- 更优雅地关闭资源 - try-with-resource及其异常抑制--转载
原文地址:https://www.cnblogs.com/itZhy/p/7636615.html 一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必 ...
- 解决了一个java服务线程退出的问题
问题背景 早上才上班,测试就提了一个问题:"昨天所有批量任务都没有跑".我看了一下任务监控页面,任务是有生成的,但却一直在等待调度状态.初步怀疑是我们的调度服务问题,于是上去查 ...
随机推荐
- 品友推广的投放原理 RTB:Real Time Bidding(实时竞价) DSP:Demand-Side Platform(需求方平台) 广告交易平台:AD Exchange
总结: 1.实时竞价 0.1秒出价各个广告主出价,投放价高者: RTB(Real Time Bidding)实时竞价,是一种利用第三方技术在数以百万计的网站或移动端针对每一个用户展示行为进行评估以及出 ...
- python将图片转base64,前端显示
https://blog.csdn.net/u013055678/article/details/71406746 <img src='xxxxxxx'> 跟这样的效果是一样的吧? ...
- JsonResponse对象浅析
JsonResponse JsonResponse 对象: class JsonResponse(data, encoder=DjangoJSONEncoder, safe=True, json_ ...
- Jquery 实现跨域处理
JS部分代码: $.ajax({ url:url, dataType:'jsonp', data:{title:title}, jsonp:'callback', success:function(l ...
- 知乎Live总结-重复nature文章笔记Single-cell
来自知乎Live-孟浩巍 1.文章重要技术及图讲解 首先在转录组RNA-seq中,有基因表达差异.基因融合.可变剪切.RNA单点突变. 在基因组中,单点变异.结构变异,CNV变异(拷贝数变异) 三类基 ...
- Dijkstra 算法初探
一.Dijkstra 算法的介绍 Dijkstra 算法,又叫迪科斯彻算法(Dijkstra),算法解决的是有向图中单个源点到其他顶点的最短路径问题.举例来说,如果图中的顶点表示城市,而边上的 ...
- Runtime.getRuntime().exec()需要注意的地方
文章出处http://www.cnblogs.com/fclbky/p/6112180.html 有时候我们可能需要调用系统外部的某个程序,此时就可以用Runtime.getRuntime().exe ...
- python16_day06【类、RE模块、subprocess模块、xml模块、shelve模块】
一.shelve模块 import shelve # 基于pickle模块, d = shelve.open('shelve_test') class Test(object): def __init ...
- beego——模型定义
复杂的模型定义不是必须的,此功能用作数据库数据转换和自动建表 默认的表名规则,使用驼峰转蛇形: AuthUser -> auth_user Auth_User -> auth__user ...
- 在w3cschool学完html,css,javascript,jquery以后,还是不会做前端怎么办?
w3cschool是一个非盈利性的在线技术学习网站,提供按W3C标准编写的基础教程.完整的看完w3cschool上面的手册,可以基本掌握编程语法.基础性的东西通常都会比较零散,因此,在学习一段时间后, ...