You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

CentOS7下Java应用作为systemd服务启动失败问题排查

解决CentOS7下systemd运行Java应用循环启停的问题

从你给出的日志、脚本和systemd配置来看,核心问题出在systemd服务类型设置PID文件权限以及脚本与systemd进程管理的冲突上,咱们一步步拆解和修复:

问题根源分析

1. Type=simple与脚本后台启动不匹配

你在systemd服务里设了Type=simple,这个类型要求ExecStart启动的进程必须前台运行——systemd会把这个进程当作服务的主进程,一旦它退出,就会认为服务终止,触发你配置的Restart=always重启服务。

但你的启动脚本里用了&把Java进程后台运行,这就导致:

  • 脚本执行完后台启动命令后,自身进程直接退出
  • systemd看到主进程(脚本)退出,立刻触发重启
  • 重启时脚本的is_running检测到后台还在跑的Java进程,就输出"Already started"
  • 紧接着systemd的重启逻辑或者脚本的停止逻辑被触发,又执行停止操作,形成恶性循环

2. PID文件的权限问题

你的PID文件放在/var/run/myservice.pid,这个目录默认是root权限。当systemd以appuser身份运行脚本时,appuser没有权限直接写入/var/run,会导致PID文件创建失败,进而让is_running函数的判断出错——比如脚本以为服务没启动,但实际Java进程已经在后台,下次启动就会误报"Already started"。

3. 自定义进程检测与systemd逻辑冲突

systemd本身有完善的进程管理能力,你自己写的get_pidsis_running这些逻辑反而会和systemd的状态判断打架,两边对"服务是否运行"的认知不一致,进一步加剧混乱。


修复方案

方案一:调整脚本与systemd配置兼容

1. 修改systemd服务文件

Type=simple改成Type=forking(这个类型专门适配启动后后台运行的进程),同时调整PID文件路径到appuser有权限写入的位置:

  • 要么创建appuser专属的PID目录:mkdir -p /home/appuser/run && chown appuser:appgrp /home/appuser/run,然后PID文件设为/home/appuser/run/myservice.pid
  • 要么给/var/run下创建专属目录:mkdir -p /var/run/myservice && chown appuser:appgrp /var/run/myservice,PID文件设为/var/run/myservice/myservice.pid

修改后的服务文件关键片段:

[Service]
Type=forking
WorkingDirectory=/app/myworkingdir/run
PIDFile=/home/appuser/run/myservice.pid  # 换成你选的有权限的路径
ExecStart=/app/myworkingdir/run/myscript.sh start
ExecStop=/app/myworkingdir/run/myscript.sh stop
User=appuser
Group=appgrp
Restart=always
RestartSec=30
StartLimitInterval=60
StartLimitBurst=5
TimeoutStartSec=30
LimitNOFILE=65536

2. 简化脚本的进程检测逻辑

is_running改成优先依赖PID文件的准确判断,避免用ps模糊匹配:

is_running() {
    if [ -f "$pid_file" ]; then
        local pid=$(cat "$pid_file")
        ps -p "$pid" > /dev/null 2>&1
        return $?
    else
        return 1
    fi
}

同时修改start部分,直接把后台进程的PID写入文件(不要用ps去查):

start)
    if is_running; then
        echo "Already started"
        exit 0
    else
        # 用nohup后台运行,直接把进程PID写入文件
        su -s /bin/sh $user -c "cd /app/myworkingdir ; nohup java -jar myjar.jar >> /var/log/systemout.log 2>> /var/log/systemerr.log & echo \$! > $pid_file"
        # 等待2秒确保进程启动完成
        sleep 2
        if ! is_running; then
            echo "Failed to start service"
            exit 1
        fi
    fi
    ;;

3. 重新加载配置并测试

systemctl daemon-reload
# 先停掉残留的服务和进程
systemctl stop myservice.service
pkill -u appuser java
# 启动服务并查看状态
systemctl start myservice.service
systemctl status myservice.service

方案二:直接用systemd管理Java进程(更推荐)

其实完全可以去掉中间的shell脚本,让systemd直接管理Java进程,这样更简洁也更符合systemd的设计:

[Unit]
Description=myservice
After=syslog.target network.target

[Service]
Type=simple
WorkingDirectory=/app/myworkingdir
User=appuser
Group=appgrp
ExecStart=/usr/bin/java -jar myjar.jar
Restart=always
RestartSec=30
StartLimitInterval=60
StartLimitBurst=5
TimeoutStartSec=30
LimitNOFILE=65536
# 日志交给systemd管理,不用自己维护日志文件
StandardOutput=journal+console
StandardError=journal+console

[Install]
WantedBy=multi-user.target

这样systemd会前台运行Java进程,日志自动进入journalctl,进程管理更可靠,也不会出现脚本和systemd冲突的问题。

内容的提问来源于stack exchange,提问作者Sven

火山引擎 最新活动