目前我在systemd中启动java服务的速度很慢,大约需要60秒,直到打开HTTP端口并为其他客户端服务。
另一个客户端服务期望此服务可用(是此服务的客户端),否则在某次重试后它将消失。它也从systemd开始。这也是一项服务。但是使用前者就像数据库一样。
我可以将systemd配置为等到第一个服务使其套接字可用吗? (如果套接字实际上是侦听的话,那么第二个客户端服务应该启动)。
答案 0 :(得分:3)
如果守护进程分叉,systemd会等待守护进程初始化自己。在您的情况下,这几乎是您必须这样做的唯一方式。
提供HTTP服务的守护进程必须在主线程中进行所有初始化,一旦初始化完成并且套接字正在侦听连接,它将fork()
。然后主要过程退出。此时,systemd知道您的进程已成功(退出0)或未成功(退出1)已初始化。
此类服务收到分配的Type=...值,如下所示:
[Service]
Type=forking
...
其他服务必须等待,因此必须要求首先启动。假设您的第一项服务名为A,您将拥有Requires,如下所示:
[Unit]
...
Requires=A
...
当然,总有另一种方式让其他服务知道耐心。这意味着尝试连接到HTTP端口,如果它失败了,稍微睡一会儿(在你的情况下,1或2秒就好了)然后再试一次,直到它工作。
我开发了两种方法,但它们都能很好地工作。
另一种方式,可能是使用restart on failure。因此,如果孩子尝试连接到该HTTP服务并失败,那么它应该失败,对吧? systemd可以反复自动重启您的进程,直到成功为止。这很糟糕,但是如果你无法控制那些守护进程的代码,那么它可能是最简单的方法。
[Service]
...
Restart=on-failure
RestartSec=10
#SuccessExitStatus=3 7 # if success is not always just 0
...
此示例在尝试重新启动之前等待失败10秒后。
你可以试图破解,虽然我从来没有推荐过这样的东西,因为可能会发生某些事情,在服务中发生这种情况......更改文件以便他们有一个睡眠60然后启动主进程。为此,只需编写如下脚本:
#!/bin/sh
sleep 60
$*
然后在.service文件中,调用该脚本,如下所示:
ExecStart=/path/to/script /path/to/service args to service
这将运行脚本而不是直接运行代码。该脚本将首先休眠60秒,然后尝试运行您的服务。因此,如果出于某种原因,这次HTTP服务需要90秒......它仍然会失败。
但是,这可能有用,因为该脚本可以执行各种操作,例如在实际启动服务进程之前使用nc
工具探测端口。你甚至可以编写自己的探测工具。
#!/bin/sh
while true
do
sleep 1
if probe
then
break
fi
done
$*
但是,请注意这样的循环阻塞,直到probe
返回退出代码为止。
答案 1 :(得分:1)
这里有几种选择。
最优雅的解决方案是让systemd为您管理套接字。如果您控制Java服务的源代码,请将其更改为使用System.inheritedChannel()
而不是分配自己的套接字,然后使用如下的systemd单元:
# example.socket
[Socket]
ListenStream=%t/example
[Install]
WantedBy=sockets.target
# example.service
[Service]
ExecStart=/usr/bin/java ...
StandardInput=socket
StandardOutput=socket
StandardError=journal
systemd将立即创建套接字(%t
是运行时目录,因此在系统单元中,套接字将为/run/example
),并在第一次尝试连接时立即启动服务。 (如果您希望无条件地启动服务,也可以使用Install
向其添加WantedBy=multi-user.target
部分。)当您的客户端程序连接到套接字时,它将被内核排队并阻塞直到服务器准备接受套接字上的连接。这样做的另一个好处是,您可以在没有任何停机时间的情况下重新启动服务 - 连接尝试将排队,直到重新启动的服务准备好再次接受连接。
或者,您可以设置服务,以便在准备就绪时向systemd发出信号,然后在客户端之后订购。 (请注意,这需要After=example.service
,而不仅仅是Requires=example.service
!依赖关系和排序是正交的 - 没有After=
,两者都将并行启动。)有两种主要服务类型可能会使这种情况发生变化可能的:
Type=forking
:只要主程序退出,systemd就会认为该服务已准备就绪。由于您不能在Java中fork
,我认为您必须编写一个小的shell脚本,在后台启动服务器,然后等待套接字可用(while ! test -S /run/example; do sleep 1s; done
)。一旦脚本退出,服务就会被认为准备就绪。
Type=notify
:systemd将在服务器被认为准备好之前等待来自服务的消息。理想情况下,消息应该从服务PID本身发送:检查是否可以通过JNI / JNA /任意(具体地,sd_notify
)从libsystemd调用sd_notify(0, "READY=1")
函数。如果这不可能,您可以使用systemd-notify
命令行工具(--ready
选项),但是您需要在服务单元中设置NotifyAccess=all
(默认情况下,只有主进程)可能会发送通知),即便如此,它可能无法正常工作(systemd需要在systemd-notify
退出之前处理该消息,否则它将无法验证消息来自哪个cgroup。