使用jsvc启动java进程

使用jsvc启动java进程

apache commons daemon,apache的一个子项目。
它的说明文档很简单,它的实际用法也是。

推荐直接去官网查看它的文档。
项目地址为:
https://commons.apache.org/proper/commons-daemon/index.html

一、启动、关闭java进程时遇到的一些需求

在启动或是关闭java进程时,有时会对这些操作和进程运行状态信息提出一些要求。

1. 在查看java进程信息时,希望能隐藏部分classpath。

如果引用的jar文件比较多,那么进程信息中显示的classpath就很长,这个对查看进程信息就很不友好,尤其是在一次要查看多个java进程的情况下。

2. 有时希望能以类似同步阻塞调用的方式来启动和关闭java进程。

只有进程在完全初始化或退出后,执行调用的脚本或命令才返回。

二、相关概念

commons-daemon可以看成是一个独立的jar文件,需要引入到项目的classpath中来配合使用。
jsvc是一个可以在类Unix上运行的可执行文件。

实际使用时,需要联合commons-daemon和jsvc一起使用。
通过它们,可以使在类Unix系统上运行java进程更加容易。

比如下面的命令,使用jsvc来启动一个java进程。

jsvc -user xxx -cwd xxx -procname xxx -pidfile xxx -cp xxx ... JVM_OPTS APP_MAIN_CLASS

可以看出它和普通的java命令类似,但是多了一些前面的参数配置。

三、初识jsvc

大约在2013年或更早的时候,项目使用jsvc来启动java进程。
当初不知道是谁率先引入的jsvc,也不知道他引入jsvc是为了解决什么样的问题,反正这种方式就这么被传承下来了。

但从那时起,使用jsvc的体验就不太好。
因为每次启动时会产生两个进程,每个进程的信息中都包含了大量的classpath信息。
而且没有用到jsvc的其它功能,因此也就没有感觉到使用jsvc带来的优势。

新人可能会对这两个进程产生疑惑,到底哪个是真正的java进程。
而且每次使用ps查看进程时,会发现进程信息占据了大部分的屏幕空间。

比如下面的这个截图。
虽然只查看了某一个具体的网关进程信息,但是大部分屏幕空间都被这些信息占据了,更不用说当要同时查找多个不同功能的进程时所造成的困扰。
没有隐藏classpath
如果是使用jsvc方式启动的java进程,在使用jps -l时,是看不到启动类的fqn或jar的完整路径的。

四、jsvc基本使用

先说明一下,本人使用的类Unix系统都是Linux,比如CentOS、Ubuntu,没有实际使用过Unix系统。

1. 在Linux上安装jsvc命令。

jsvc命令需要通过手动编译源码来构建,官网没有提供现成的下载文件来直接使用。

然而jsvc的构建过程超级简单。

  1. 下载commons-daemon-1.2.2-src.tar.gz,解压后进入src/native/unix。
  2. 运行如下命令(文档页上也有这些步骤):
./configure --with-java=dir         #设置属性
make                                #生成jsvc命令
sudo cp jsvc /usr/bin/              #将jsvc拷贝到某个path目录下

2. 在项目中引入commons-daemon。

  1. 在项目中引入jar。
  2. 启动类实现Daemon接口;或是启动类不实现Daemon接口,但是拥有Daemon接口指定的4个方法。

3. 编写相应的使用脚本

最终使用如下方式调用jsvc命令。
APP_MAIN_CLASS就是实现了Daemon接口的类,或是有Daemon接口的4个方法的类。

启动java进程:
jsvc -cwd xxx -user xxx -procname -xxx -pidfile xxx … -cp JVM_OPTS APP_MAIN_CLASS

关闭java进程:
jsvc -stop -pidfile xxx APP_MAIN_CLASS

五、jsvc进阶

这么多年来,一直没有理解为什么会选择使用jsvc来启动java进程。
目前所在的项目组,也是使用了jsvc,然而也没有用好它,文章中的截图就是来自于这个项目组的一个进程 - 网关。
他们虽然引入了jsvc,但是没有使用到jsvc提供的一些好用的功能,反而造成了一些困扰。

后来自己在寻求简化classpath信息的过程中,偶然使用-debug选项输出了很多调试信息。
根据这些信息猜测,jsvc应该是提供了某些核心功能。
于是去官网看了一下它的文档和api,写一些测试代码试了一下,发现了一些从未有使用过的功能。

1. 隐藏classpath信息。

使用jsvc命令启动java进程后,会产生两个进程,如果java项目本身引入的jar文件比较多,这些进程信息就会比较长。

如果使用nohupjava &这两种方式启动java进程,很多人都会选择在打包阶段将引入jar的classpath信息放到META-INF/MANIFEST.MF文件中,这样就可以隐藏这些classpath。

见过好几个项目使用jsvc(包括现在的),但是它们都没有能解决这个问题。
其实在使用jsvc这种方式中,也可以隐藏掉这些classpath,也是将classpath写到META-INF/MANIFEST.MF文件中。

这是隐藏classpath后的进程信息。
隐藏classpath后的进程信息

2. 只有在java应用初始化完成后,执行调用的脚本或命令才返回(类似于同步阻塞调用方式)。

这种需求也是很常见的,比如脚本在成功启动java进程后,再做一些其它操作,这两个动作是有先后依赖关系的。
之前就尝试在java代码中实现这样的功能,但是没有成功。

如果单纯的使用nohupjava &这两种方式,就不能实现这样的效果。

jsvc可以通过-wait选项来满足这个需求。
如果不使用-wait选项,jsvc命令会立即返回(类似异步调用),也就没法知道java进程是否会初始化成功。
如果使用-wait选项,当start方法耗时超过wait指定的耗时后,jsvc命令才会返回。

3. 只有在java进程退出后,执行的脚本或命令才返回。

jsvc默认就使用了这种方式。
比如要重启进程,得先确认进程被关闭后,再启动进程。

如果使用nohupjava &这两种方式,也是没法直接做到这一点的。
这时大家结束进程的方式有几种:发送SIGTERM(kill -15)信号给进程,或是在代码中预埋触发System.exit(0)的逻辑。
但这些操作都是类似于异步操作,因此只能在脚本中循环检查进程状态,来判断进程是否结束。

4. 捕获自定义信号。

有些人使用捕获信号(比如-15、-12)的方式来做一些特定操作。
jsvc默认屏蔽了-12的信号,给它发送-12的信号是没有任何作用的。
如果给一个普通的java进程发送-12信号,如果它没有捕获这个信号的逻辑,这个进程是会crash的。

jsvc支持捕获-12的信号

5. 进程在crash后会自动重新启动。

比如不小心使用kill -9终止java进程后,守护进程会自动重启java进程。
这个是之前同事告诉我的。

会输出如下信息:
Service killed by signal 9
Waiting 60 s to prevent looping

之前遇到过这种情况:
在内存不足时,java进程会启动失败,但是jsvc的后台进程会不断的去重启java进程进行重试。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章