#!/bin/bash
sleep 2
nohup java -jar /home/ubuntu/radius/radius-test.jar >> /home/ubuntu/radius/radius.log &
nohup java -jar /home/ubuntu/radius/radius-test-2.jar >> /home/ubuntu/radius/radius-2.log &
我有一个类似于上面的脚本文件,它应该在启动时自动运行。
在我发现的许多方法中,我决定使用 systemd 并成功地获得了所需的行为。
但是,我想知道是否有正确的方法来指定 Type 选项或者是否没有问题。
[Unit]
Description=My Shell Script
[Service]
Type=simple or oneshot
RemainAfterExit=yes
ExecStart=/home/ubuntu/radius/radius-start.sh
[Install]
WantedBy=multi-user.target
---
[Unit]
Description=My Shell Script
[Service]
Type=forking
ExecStart=/home/ubuntu/radius/radius-start.sh
[Install]
WantedBy=multi-user.target
在这两种方法中,服务都会在启动时运行,并且我的 jar 文件也会运行。并且该服务仍然处于活动状态并且 jar 进程也得到维护。然后,当服务终止时,所有jar进程也会终止。
我查了一下,发现fork适合后台方法,子进程运行,然后父进程死掉。 (这部分我也不明白。)它还说必须指定pid文件或者不使用fork方法。
simple或者oneshot据说是需要前台操作的方式来使用。但我的jar进程需要在后台运行。
这两种方法有什么区别?哪个选项是正确的使用方法?
答案1
让我们看看您的单元文件的两个版本,然后我将向您展示我的建议:
simple
或oneshot
服务
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/home/ubuntu/radius/radius-start.sh
Type=oneshot
RemainAfterExit=yes
与 一起使用比更合适Type=simple
。
和simple
你平时跑步一样ExecStart=
。然后,当该过程结束时,所有内容都将被清理,并且服务将“停止”。但是,radius-start.sh
启动一些进程然后立即停止。这意味着 systemd 将查找任何孤立进程并杀死它们作为清理的一部分。
有了oneshot
,那真的意味着跑ExecStart=
然后死。 RemainAfterExit=
可以使用有用的方法帮助您在该单元停止时触发ExecStop=
命令或利用关系来停止其他单元。ConsistsOf=
但它并没有为您提供您生成的子进程的最新状态。
这种技术的一个大问题是,它要么立即终止您的(孤立的)子进程,要么在子进程退出时无法理解该单元已停止/失败。
forking
服务
[Service]
Type=forking
ExecStart=/home/ubuntu/radius/radius-start.sh
这是您的服务的正确版本。forking
将运行ExecStart=
,预计将很快退出。然后,systemd 预计会产生一些子进程,并将跟踪这些子进程的状态。当主进程退出时,systemd 会认为服务已停止。
如果您坚持使用此方法,那么建议进行设置PIDFile=
,以便 systemd 了解两个进程中哪一个是主要进程。
[Service]
...
PIDFile=/run/myservice.pid
...
#!/bin/bash
sleep 2
nohup java -jar /home/ubuntu/radius/radius-test.jar >> /home/ubuntu/radius/radius.log &
echo $! > /run/myservice.pid
nohup java -jar /home/ubuntu/radius/radius-test-2.jar >> /home/ubuntu/radius/radius-2.log &
然而,systemd 的文档明确指出:
请注意,现代项目中应避免使用 PID 文件。尽可能使用 Type=notify 或 Type=simple,这样不需要使用 PID 文件来确定服务的主进程,并避免不必要的分叉。
无需调用bash
此处来运行您的进程,因为您想要执行的所有操作都已由 systemd 本机提供。
逐字建议Type=simple
如果我要逐字实现您的服务,我将使用两项服务来实现它:
# radius-test.service
[Service]
Type=simple
ExecStartPre=/bin/sleep 2
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test.jar
StandardOutput=append:/home/ubuntu/radius/radius.log
# radius-test2.service
[Unit]
After=radius-test.service
[Service]
Type=simple
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test-2.jar
StandardOutput=append:/home/ubuntu/radius/radius-2.log
现在,如果其中一项服务出现故障,我们可以检测到哪一项出现故障并恢复它。如果这些服务需要某种关系,则可以对其进行定义。例如,可以使用 来实现订购After=
。如果您希望其中一个在另一个失败时干净地停止,您可以添加BindsTo=
.
改进解决方案
ExecStartPre=/bin/sleep 2
: 这通常是黑客攻击。延迟会在好日子带来不必要的延迟,或者在坏日子带来不够的延迟。最好理解为什么你需要睡觉。例如,如果您需要在运行服务之前使网络可用,请改用After=network.target
。Type=simple
: 这实际上是默认的。所以这条线可以去掉。- 如果您总是想在启动 radius-test 时运行 radius-test2,则使用关系(或在's部分
Wants=
指定)WantedBy=radius-test
radius-test2
[Install]
- 如果你想在停止
radius-test2
时停止,那么使用关系。radius-test
PartOf=
StandardOutput=append:...
:虽然将内容记录到您自己的文件中没有问题,但您可能会发现使用内置日志很方便。只需删除此行,您就可以通过 获取日志journalctl -u myservice.service
。journalctl
提供许多额外的功能,如日志轮换、大小限制和过滤。我经常做这样的事情:
journalctl -u myservice --since "5 hours ago" --until "4 hours ago"
改进的版本是这样的:
# /etc/systemd/system/radius-test.service
[Unit]
Description=Radius Test 1
After=network.target
Before=radius-test-2
Wants=radius-test-2
[Service]
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test.jar
[Install]
WantedBy=multi-user-target
# /etc/systemd/system/radius-test2.service
[Unit]
Description=Radius Test 2
PartOf=radius-test
[Service]
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test-2.jar
最终的简单解决方案
然而,现在我们已经删除StandardOutput=
并使用了日志,也可以将它们合并为一个单元。这是一个更简单的解决方案,使用ExecStartPost=
:
# /etc/systemd/system/radius-test.service
[Unit]
Description=Radius Test
After=network.target
[Service]
ExecStart=/usr/bin/java -jar /home/ubuntu/radius/radius-test.jar
ExecStartPost=/usr/bin/java -jar /home/ubuntu/radius/radius-test-2.jar
[Install]
WantedBy=multi-user-target