在Linux上运行Java应用程序作为服务
1. 简介
从系统的角度来看,任何 Java 应用程序都只是 Java 虚拟机的一个实例。在这个简短的教程中,我们将了解如何使我们的应用程序作为系统服务运行。
我们将使用systemd 软件包的功能。系统是大多数现代 Linux 发行版中的初始化和服务管理系统。
在整个教程中,我们将考虑两种实现:一种用于简单案例,另一种用于更高级的案例。
2. 简单的服务
在systemd世界中,要创建系统服务,我们需要准备一个单元文件并以正确的方式注册它。我们将很快讨论文件位置,但首先,让我们从内容开始:
[Unit]
Description=My Java driven simple service
After=syslog.target network.target
[Service]
SuccessExitStatus=143
User=appuser
Group=appgroup
Type=simple
Environment="JAVA_HOME=/path/to/jvmdir"
WorkingDirectory=/path/to/app/workdir
ExecStart=${JAVA_HOME}/bin/java -jar javaapp.jar
ExecStop=/bin/kill -15 $MAINPID
[Install]
WantedBy=multi-user.target
我们将服务Type设置为simple,因为系统直接启动 JVM 进程,没有产生子进程。
ExecStop指定服务终止命令,systemd足够聪明,可以计算出启动进程的PID 。它会自动创建一个 MAINPID 环境变量。
然后,我们指示systemd发送一个 15 (SIGTERM) 系统信号以优雅地终止进程。
JVM 设计者让 Java 返回一个非零的退出代码,以防它被系统信号终止。作为一个非零基数,它们取了 128,结果退出代码是 128 和信号数值之和。
通过将SuccessExitStatus设置为 143 ,我们告诉systemd*将该值 (128+15) 作为正常的 exit 处理*。
3. 分叉服务
上面的简单服务单元文件对于琐碎的应用程序可能已经足够了。但是,更实际的情况可能会包括其他设置。
这些可以是 JVM 参数以及任何其他特定于应用程序的参数,例如配置或数据文件位置。这可能会导致编写一个包装器 shell 脚本,我们可以在其中设置所有必需的参数,然后再启动 JVM。
假设我们已经有一个包装脚本,现在只想把它变成一个系统服务:
#!/bin/bash
JAVA_HOME=/path/to/jvmdir
WORKDIR=/path/to/app/workdir
JAVA_OPTIONS=" -Xms256m -Xmx512m -server "
APP_OPTIONS=" -c /path/to/app.config -d /path/to/datadir "
cd $WORKDIR
"${JAVA_HOME}/bin/java" $JAVA_OPTIONS -jar javaapp.jar $APP_OPTIONS
**由于我们使用 shell 脚本启动服务,JVM 将由 shell(bash)进程启动。**此操作称为fork ,这就是我们将 service Type设置为forking的原因。
将变量定义移动到脚本的主体也使单元文件更加简洁:
[Unit]
Description=My Java forking service
After=syslog.target network.target
[Service]
SuccessExitStatus=143
User=appuser
Group=appgroup
Type=forking
ExecStart=/path/to/wrapper
ExecStop=/bin/kill -15 $MAINPID
[Install]
WantedBy=multi-user.target
4.注册并运行服务
无论我们选择何种服务类型,要完成任务,我们都必须知道如何设置和运行系统服务本身。
首先,我们需要以我们想要的服务名称命名单元文件。在我们的示例中,可能是javasimple.service或javaforking.service。
然后,我们将单元文件放在systemd可以找到它的位置之一。对于任意服务,/etc/systemd/system 是一个不错的选择。
在这种情况下,我们系统单元的完整路径将是:
- /etc/systemd/system/javasimple.service
- /etc/systemd/system/javaforking.service
放置系统单元的另一个可能路径是*/usr/lib/systemd/system*。这通常是系统安装包使用的位置。
但是,当我们开发自己的包含系统服务的*.rpm或.deb*安装包时,我们应该考虑它更合适。
无论哪种情况,我们都将使用systemctl *实用*程序控制服务并传递start、stop或status命令。
然而,在此之前,我们应该通知systemd它必须重建其内部服务数据库。这样做会让它知道我们引入的新系统单元。我们可以通过将daemon-reload命令传递给systemctl来做到这一点。
现在,我们准备好运行我们提到的所有命令:
sudo systemctl daemon-reload
sudo systemctl start javasimple.service
sudo systemctl status javasimple.service
● javasimple.service - My Java driven simple service
Loaded: loaded (/etc/systemd/system/javasimple.service; <strong>disabled</strong>; vendor preset: disabled)
Active: active (running) since Sun 2021-01-17 20:10:19 CET; 8s ago
Main PID: 8124 (java)
CGroup: /system.slice/javasimple.service
└─8124 /path/to/jvmdir/bin/java -jar javaapp.jar
每次修改单元文件时,我们都需要运行daemon-reload命令。
接下来,我们注意到系统报告我们的服务正在运行但已disabled。系统启动时,禁用的服务不会自动启动。
当然,我们可以将其配置为随系统自动启动。这是我们使用另一个systemctl命令的地方 - enable:
sudo systemctl enable javasimple.service
Created symlink from /etc/systemd/system/multi-user.target.wants/javasimple.service to /etc/systemd/system/javasimple.service
现在,我们可以看到它已启用:
sudo systemctl status javasimple.service
● javasimple.service - My Java driven simple service
Loaded: loaded (/etc/systemd/system/javasimple.service; enabled; vendor preset: disabled)
Active: active (running) since Sun 2021-01-17 20:10:19 CET; 14min ago
Main PID: 8124 (java)
....