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