等待systemd直到服务套接字变为可用,然后启动一个依赖的服务

时间:2016-08-11 10:06:23

标签: java sockets systemd

目前我在systemd中启动java服务的速度很慢,大约需要60秒,直到打开HTTP端口并为其他客户端服务。

另一个客户端服务期望此服务可用(是此服务的客户端),否则在某次重试后它将消失。它也从systemd开始。这也是一项服务。但是使用前者就像数据库一样。

我可以将systemd配置为等到第一个服务使其套接字可用吗? (如果套接字实际上是侦听的话,那么第二个客户端服务应该启动)。

2 个答案:

答案 0 :(得分:3)

初始化过程需要分叉

如果守护进程分叉,systemd会等待守护进程初始化自己。在您的情况下,这几乎是您必须这样做的唯一方式。

提供HTTP服务的守护进程必须在主线程中进行所有初始化,一旦初始化完成并且套接字正在侦听连接,它将fork()。然后主要过程退出。此时,systemd知道您的进程已成功(退出0)或未成功(退出1)已初始化。

此类服务收到分配的Type=...值,如下所示:

[Service]
Type=forking
...

"所需"将确保进程等待

其他服务必须等待,因此必须要求首先启动。假设您的第一项服务名为A,您将拥有Requires,如下所示:

[Unit]
...
Requires=A
...

以耐心记住的节目

当然,总有另一种方式让其他服务知道耐心。这意味着尝试连接到HTTP端口,如果它失败了,稍微睡一会儿(在你的情况下,1或2秒就好了)然后再试一次,直到它工作。

我开发了两种方法,但它们都能很好地工作。

使用systemd自动重启功能?

另一种方式,可能是使用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

或者,您可以设置服务,以便在准备就绪时向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。

相关问题