作者:鲍凤其
在即将发布的 dble 2.19.09.0 版本中,我们将升级 dble 中 JSW 的版本,将 JSW 版本从 3.2.3 版本升级到 3.5.41 版本。升级的原因在于我们在使用过程中发现了几个比较严重的 bug,这几个 bug 会导致 dble 的守护进程异常退出和 hang 死。hang 死的案例可参考 issue: https://github.com/actiontech... 。看到这里的同学,有些同学可能对 JSW 可能还不太了解,我这里先简单介绍一下。
JSW 是 Java Service Wrapper 的缩写,也就是 dble 中通常所说的 wrapper。JSW 可用于将 Java 程序包装成一个后台服务运行。除此以外,JSW 还可以在你的 Java 程序宕掉以后,自动把服务拉起。相当于提供了一个守护进程的功能。它的一个主要目标就是,单点服务做到尽可能高可靠,宕掉后第一时间帮你把它拉起来!这样,能够最大化降低运维成本。
JSW 除了 Window 和 Linux 还支持其他平台,社区中经常听到有同学问 dble 有 Windows 版本此类的问题。在这里我想说可以有,但是 dble 官方只提供了 Linux 版,Windows 版本就需要各位同学自己动手编译打包了。Java Service Wrapper 分为社区版和企业版,企业版的功能更加强大,但是要收费。目前一般使用的都是社区版,免费并且开源。
要了解上面的 bug,我们需要先了解一下 JSW 的整体流程。下面我们先从整体来看一下 dble 和 JSW 守护进程的关系,如下图:
dble 在启动后,如果通过系统命令查看后台进程,会发现其实后台运行了两个进程。
守护进程的启动大致我大致分为两个阶段:1. 初始化阶段,2. 状态和事件处理阶段。
在此阶段,守护进程不停的轮询监听端口是否有事件达到,根据 Java 程序的状态执行不同的操作,以此反复。下面详细描述下过程,可对照下面的图来看。
在这里我们假设 JVM 由于某种原因 hang 了一段时间。我们来看下守护进程是如何处理的。一旦 JVM hang 住了,则守护进程的 ping 命令不能及时返回,此时守护进程将状态置为 killing 并准备杀死 Java 程序。守护进程调用系统调用 kill 向子进程发送 SIGKILL 信号,发送信号之后,守护进程会等待 0.5s 以确保这期间子进程被回收和状态的正确性。
到这里我们已经介绍完 JWS 的启动流程和当 Java 程序异常时的处理。现在我们来看下之前我们遇到的问题:
在最后的异常处理那节,从 killing 到 down 状态的转换过程中,主线程内会打印 log 来提示用户。打印日志前需要获取日志锁,但是此时主线程接收到 SIGCHLD 信号并回调了该信号的处理函数,在处理函数中也需要打印日志,此时也去获取日志锁,但是此锁是不可重入锁,因此发生死锁,导致守护进程主线程 hang 死。在 JWS 新的版本中,打印日志的操作会放在单独的线程中处理来解决这个问题。
这个问题是一系列问题的总成,但是根本原因是相同的,还是在于 SIGCHLD 信号。在最后一个异常处理那个图中 SIGCHLD 信号的返回我画了两个。正常情况下是在守护进程等待 Java 子进程被回收的过程中收到该信号。但是如果 Java 子进程资源回收超过 0.5s 时,守护进程已经往下执行准备重启 Java 程序了,此时再回调到 SIGCHLD 信号处理函数,会导致状态异常而退出。新版本在这方面做出了一些改善。
上面我对 dble 中使用的 JWS 从启动流程和错误处理两个方面做了简单的介绍,在最后对平常工作中遇到的 bug 进行了探究,希望对你们有帮助。