java:systemd-notify 中的看门狗超时不一致

Posted

技术标签:

【中文标题】java:systemd-notify 中的看门狗超时不一致【英文标题】:java: inconsistent watchdog timeout in systemd-notify 【发布时间】:2015-11-27 20:16:19 【问题描述】:

我的 java 应用程序安装在 OpenSUSE 13.2 操作系统上,并且我使用 systemd 进行进程控制。 (系统版本 210)

我想通过 systemd-notify 来利用 systemd 看门狗功能。但是,我注意到由于看门狗的超时时间不一致,应用程序重新启动。

如果 WatchdogSec=120,并且应用程序配置为每 60 秒调用一次 systemd-notify,我观察到平均每 5 到 20 分钟重新启动一次。

这里是该进程的(略微编辑的)systemd 单元文件:

# Cool systemd service
[Unit]
Description=Something Awesome
After=awesomeparent.service
Requires=awesomeparent.service

[Service]
Type=simple
WorkingDirectory=/opt/awesome
Environment="AWESOME_HOME=/opt/awesome" 
User=awesomeuser
Restart=always
WatchdogSec=120
NotifyAccess=all
ExecStart=/home/awesome/jre1.8.0_05/bin/java -jar awesome.jar

[Install]
WantedBy=multi-user.target

这里是调用 systemd-notify 的代码

String pidStr = ManagementFactory.getRuntimeMXBean().getName();
pidStr = pidStr.split("@")[0];

String cmd = "/usr/bin/systemd-notify";

Process process = new ProcessBuilder(cmd, 
                                    "MAINPID=" + pidStr, 
                                    "WATCHDOG=1").redirectErrorStream(true)
                                                 .start();

int exitCode = 0;
if ((exitCode = process.waitFor()) != 0)                 
    String output = IOUtils.toString(process.getInputStream());
    Log.MAIN_LOG.error("Failed to notify systemd: " + 
                              ((output.isEmpty()) ? "" : " " + output) +
                              " Exit code: " + exitCode);


在日志中,我从未看到失败消息(进程总是返回 0 退出代码),并且我 100% 确定任务每分钟每分钟执行一次。我可以看到任务日志在重新启动之前立即执行。

有人知道为什么 systemd-notify 有时不起作用吗?

我正在考虑编写代码以直接调用 sd_pid_notify,但想知道在走这条路线之前是否可以做一个简单的配置。

【问题讨论】:

您是否尝试过使用JNI 调用sd_notify(3)?因此,您可以更准确地检查呼叫状态。我想Java守护程序和systemd之间的调用存在一些问题。此外,我会在ProcessBuilder.start() 之前立即记录一条消息,并在systemd-notify 上使用日志记录外壳包装器,以确保及时执行子进程的调用并且没有任何不可预测的延迟 我对 CentOS7.0 (systemd 208) 也有类似的问题。我有同样的 2 分钟看门狗时间,今天它失败了(似乎是随机的)。就我而言,我每秒直接调用一次sd_notify()。我没有任何迹象表明发送通知的过程完全停止了。 我最终为此使用了 JNA,从那以后它就一直坚如磐石。我将在下面的答案中发布代码。 【参考方案1】:

这是解决问题的 JNA 代码:

import com.sun.jna.Library;
import com.sun.jna.Native;

/**
 * The task issues a notification to the systemd watchdog. The systemd watchdog
 * will restart the service if the notification is not received.
 */

public class WatchdogNotifierTask implements Runnable 

private static final String SYSTEMD_SO = "systemd";
private static final String WATCHDOG_READY = "WATCHDOG=1";

@Override
public void run() 

  try 
    int returnCode = SystemD.INSTANCE.sd_notify(0, WATCHDOG_READY);
    if (returnCode < 0) 
      Log.MAIN_LOG.error(
          "Systemd watchdog returned a negative error code: " + Integer.toString(returnCode));
     else 
      Log.MAIN_LOG.debug("Successfully updated systemd watchdog.");
    
   catch (Exception e) 
    Log.MAIN_LOG.error("calling sd_notify native code failed with exception: ", e);
  
 

/**
 * This is a linux-specific interface to load the systemd shared library and call the sd_notify
 * function. Should we need other systemd functionality, it can be loaded here. It uses JNA for
 * native library calls.
 *
 */
interface SystemD extends Library 
  SystemD INSTANCE = (SystemD) Native.loadLibrary(SYSTEMD_SO, SystemD.class);
  int sd_notify(int unset_environment, String state);



【讨论】:

这个超级有用,我扩展了一下:gist.github.com/juur/048cc3d0554953b717e9c6867970f30e【参考方案2】:

有人知道为什么systemd-notify 有时不起作用吗?

这实际上是几个 systemd 协议中长期存在的问题,而不仅仅是systemd-notify 所说的就绪通知协议。直接发送东西到systemd自己的日志的协议也有这个问题。

两种协议都试图通过读取/proc/<i>client-process-id</i>/* 中的内容来找出有关发送、客户端、进程的信息。不幸的是,systemd-notify 是一个短暂的程序,一旦将消息发送到服务器就会退出。因此读取/proc/<i>client-process-id</i>/* 不会产生服务器需要的有关客户端的信息。特别是,服务器无法确定客户端属于哪个(systemd)控制组,从而无法确定哪个服务单元控制它,从而确定它是否是允许发送就绪通知消息的进程。

正如你所发现的,在你的实际守护进程中调用一个库例程,而不是派生一个短暂的子进程来运行systemd-notify可以避免这个问题,因为当然你的守护进程在发送后不会立即退出通知。但是请注意,如果您在退出守护程序之前立即发出准备就绪通知(具有讽刺意味的是,有些守护程序是为了通知世界它们正在终止),即使在进程中,您也会遇到同样的问题库函数。

顺便说一句,无需将 systemd 库函数作为本机代码调用即可使用此协议。 (并且 not 使用库函数可以获得正确使用该协议的优势,即使 systemd 不在它的服务器端——这是 systemd 库函数的失败。)它不是一个硬协议用 Java 说话,systemd 手册页描述了该协议。您查看一个环境变量,打开一个数据报套接字,使用该变量的值作为要发送到的套接字的名称,发送一条数据报消息,然后关闭该套接字。 Java 能够做到这一点。 ☺

进一步阅读

乔纳森·德·博因·波拉德 (2016)。 "using a synchronous protocol when pulling client credentials"。 Unix 守护进程的就绪协议问题。经常给出的答案。 甲骨文公司 (2005)。 Environment variables。 Java 教程。 sd_notify系统手册。 Freedesktop.org。 UNIX socket implementation for Java?

【讨论】:

您查看一个环境变量,打开一个数据报套接字,使用该变量的值作为要发送到的套接字的名称,发送单个数据报消息,然后关闭该套接字。 Java 可以做到这一点。 听起来像是一个很好的开源库! github.com/faljse/SDNotify,我引用:“Notify 协议使用数据报 unix 套接字,这些套接字无法通过 Java 访问;因此 SDNotify 包含套接字 API 的 JNA 包装器。”因此,Java 无法单独做到这一点。 :'(

以上是关于java:systemd-notify 中的看门狗超时不一致的主要内容,如果未能解决你的问题,请参考以下文章

Python中的Beaglebone Black看门狗

禁用硬件看门狗后使用超级终端的哪种协议来烧写spl

redisson中的看门狗机制总结

ArkUI中的线程和看门狗机制

SDRAM和重定位---看门狗详解

在多任务环境中喂养看门狗的策略