SpringBoot应用开机自启动与进程守护配置

一个故(shi)事(gu)

"小王,生产环境怎么回事?服务全挂了!"

上个月一个周六的凌晨,小王被这通电话惊醒。

机房意外断电,恢复供电后所有服务器重启,但我们的SpringBoot应用没有自动启动,导致业务中断了整整40分钟。

事后排查发现,是之前的运维同事离职时没有留下开机自启动配置文档。更糟的是,应用运行中偶尔会因为OOM崩溃,也没有任何进程守护机制。

经历这次事故后,小王整理了两套SpringBoot应用开机自启动与进程守护方案,在生产环境稳定运行至今。

方案一:systemd配置(推荐)

什么是systemd?

简单说,systemd是现在Linux系统的"大管家",负责管理系统启动和服务进程。

几乎所有主流Linux(CentOS 、Ubuntu 、Debian等)都支持使用systemd。

为什么推荐它?因为:

原生集成 :不需要额外安装软件 功能强大 :支持开机自启、进程监控、日志管理 配置简单:一个服务文件搞定所有设置

实战步骤:从0到1配置

1. 准备SpringBoot应用

假设我们的应用信息:

  • 应用名称:demo-app
  • JAR文件路径:/opt/apps/demo-app.jar
  • 运行用户:appuser(建议使用非root用户)
  • 日志路径:/var/log/demo-app.log
2. 创建启动脚本(关键)

不要直接在systemd中调用java命令!创建一个启动脚本/opt/apps/start.sh

bash 复制代码
#!/bin/bash
# SpringBoot应用启动脚本
APP_NAME="demo-app"
JAR_FILE="/opt/apps/demo-app.jar"
LOG_FILE="/var/log/${APP_NAME}.log"
JVM_OPTS="-Xms512m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
SERVER_PORT=8080

# 检查JAR文件是否存在
if [ ! -f "$JAR_FILE" ]; then
    echo "错误:JAR文件 $JAR_FILE 不存在!"
    exit 1
fi

# 检查端口是否被占用
check_port() {
    netstat -tlnp | grep ":$SERVER_PORT " > /dev/null
    return $?
}

if check_port; then
    echo "错误:端口 $SERVER_PORT 已被占用!"
    netstat -tlnp | grep ":$SERVER_PORT "
    exit 1
fi

# 启动应用
echo "开始启动 $APP_NAME ..."
nohup java $JVM_OPTS -jar $JAR_FILE --server.port=$SERVER_PORT > $LOG_FILE 2>&1 &

# 等待应用启动
echo "等待应用启动,端口:$SERVER_PORT ..."
for i in {1..30}; do
    if check_port; then
        echo "$APP_NAME 启动成功!"
        echo "日志文件:$LOG_FILE"
        exit 0
    fi
    sleep 1
done

# 启动超时处理
echo "错误:$APP_NAME 启动超时!"
echo "查看日志获取详细信息:tail -f $LOG_FILE"
exit 1

给脚本添加执行权限:

bash 复制代码
chmod +x /opt/apps/start.sh
3. 创建systemd服务文件

创建/etc/systemd/system/demo-app.service

ini 复制代码
[Unit]
Description=Demo SpringBoot Application
Documentation=https://example.com/docs
After=network.target mysql.service  # 依赖网络和MySQL服务启动后再启动
Wants=mysql.service  # MySQL启动失败不影响本服务启动

[Service]
User=appuser
Group=appuser
WorkingDirectory=/opt/apps
ExecStart=/opt/apps/start.sh
# 自定义停止命令(优雅关闭)
ExecStop=/bin/bash -c "PID=$(pgrep -f 'demo-app.jar'); if [ -n "$PID" ]; then kill -15 $PID; sleep 10; if ps -p $PID > /dev/null; then kill -9 $PID; fi; fi"
# 启动前备份日志
ExecStartPre=/bin/bash -c "if [ -f /var/log/demo-app.log ]; then mv /var/log/demo-app.log /var/log/demo-app.log.$(date +%%Y%%m%%d%%H%%M%%S); fi"
SuccessExitStatus=143  # 识别SpringBoot优雅关闭状态码
Restart=always  # 总是重启(异常退出时)
RestartSec=5  # 重启间隔5秒
TimeoutStartSec=300  # 启动超时时间(5分钟)
TimeoutStopSec=60  # 停止超时时间
Environment="SPRING_PROFILES_ACTIVE=prod"  # 环境变量
Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk"

[Install]
WantedBy=multi-user.target  # 多用户模式下启动
4. 服务管理命令
bash 复制代码
# 重载systemd配置(修改服务文件后必须执行)
sudo systemctl daemon-reload

# 启动服务
sudo systemctl start demo-app

# 停止服务
sudo systemctl stop demo-app

# 重启服务
sudo systemctl restart demo-app

# 设置开机自启
sudo systemctl enable demo-app

# 取消开机自启
sudo systemctl disable demo-app

# 查看服务状态
sudo systemctl status demo-app

# 查看服务日志
sudo journalctl -u demo-app -f  # -f表示实时跟踪

状态检查示例

bash 复制代码
 demo-app.service - Demo SpringBoot Application
   Loaded: loaded (/etc/systemd/system/demo-app.service; enabled; vendor preset: disabled)
   Active: active (running) since Wed 2023-11-15 10:30:00 CST; 5min ago
     Docs: https://example.com/docs
  Process: 1234 ExecStartPre=/bin/bash -c if [ -f /var/log/demo-app.log ]; then mv /var/log/demo-app.log /var/log/demo-app.log.20231115102959; fi (code=exited, status=0/SUCCESS)
 Main PID: 1245 (start.sh)
    Tasks: 30 (limit: 4915)
   Memory: 256.0M
   CGroup: /system.slice/demo-app.service
           ├─1245 /bin/bash /opt/apps/start.sh
           └─1250 java -Xms512m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar /opt/apps/demo-app.jar --server.port=8080

避坑指南

坑1:权限问题导致启动失败

现象Job for demo-app.service failed because the control process exited with error code.
解决:确保appuser用户有JAR文件和日志目录的权限

bash 复制代码
sudo chown -R appuser:appuser /opt/apps /var/log/demo-app.log
坑2:应用启动慢导致超时

现象Failed to start Demo SpringBoot Application. Timeout was reached.
解决:增加启动超时时间

ini 复制代码
[Service]
TimeoutStartSec=300  # 5分钟
坑3:优雅关闭不生效

现象 :执行stop后应用立即退出,@PreDestroy方法未执行
解决:配置优雅关闭参数

ini 复制代码
[Service]
KillSignal=SIGTERM
TimeoutStopSec=60

同时在SpringBoot配置文件中添加:

yaml 复制代码
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

方案二:Supervisor配置(兼容更多系统)

如果你的服务器是较老的Linux发行版(如CentOS 6),或者需要管理多个进程,Supervisor是个不错的选择。

它是Python编写的进程管理工具,跨平台性好。

安装Supervisor

CentOS安装

bash 复制代码
# 安装EPEL源
sudo yum install -y epel-release
# 安装Supervisor
sudo yum install -y supervisor
# 启动并设置开机自启
sudo systemctl start supervisord
sudo systemctl enable supervisord

Ubuntu安装

sql 复制代码
sudo apt-get update
sudo apt-get install -y supervisor

创建应用配置文件

创建/etc/supervisord.d/demo-app.ini

ini 复制代码
[program:demo-app]
command=/usr/bin/java -Xms512m -Xmx512m -XX:+UseG1GC -jar /opt/apps/demo-app.jar --server.port=8080
directory=/opt/apps
user=appuser
autostart=true  # 随Supervisor启动而启动
autorestart=true  # 进程退出时自动重启
startretries=3  # 启动失败重试次数
startsecs=10  # 启动后稳定运行10秒才算成功
redirect_stderr=true  # 错误日志重定向到标准输出
stdout_logfile=/var/log/demo-app.log
stdout_logfile_maxbytes=10MB  # 日志文件大小限制
stdout_logfile_backups=10  # 日志备份数量
environment=
    SPRING_PROFILES_ACTIVE="prod",
    JAVA_HOME="/usr/lib/jvm/java-11-openjdk"

Supervisor管理命令

bash 复制代码
# 重载配置(修改配置后执行)
sudo supervisorctl update

# 查看所有进程状态
sudo supervisorctl status

# 启动应用
sudo supervisorctl start demo-app

# 停止应用
sudo supervisorctl stop demo-app

# 重启应用
sudo supervisorctl restart demo-app

# 查看应用日志
sudo supervisorctl tail -f demo-app

实战经验: "Supervisor的日志轮转功能非常实用,但要注意单位是MB不是GB!另外,它本身也需要通过systemd来管理开机启动,有点套娃的感觉。"

两种方案对比与选择建议

特性 systemd Supervisor
系统集成 原生集成,无需额外安装 需要单独安装
配置复杂度 中等 简单
进程监控 支持 支持
多应用管理 配置文件分散 集中管理
跨平台性 仅Linux Linux/Unix/Mac

使用建议

生产环境首选systemd :原生、轻量、功能足够 多应用管理选Supervisor :集中配置,适合管理多个微服务 Docker环境不需要:容器本身提供进程管理

高级配置:健康检查与自动恢复

systemd健康检查

创建健康检查脚本/opt/apps/healthcheck.sh

bash 复制代码
#!/bin/bash
PORT=8080
TIMEOUT=5

# 检查健康端点
result=$(curl -s -w "%{http_code}" -o /dev/null --connect-timeout $TIMEOUT http://localhost:$PORT/actuator/health)

if [ "$result" -eq 200 ]; then
    exit 0
else
    echo "健康检查失败,HTTP状态码: $result"
    exit 1
fi

修改服务文件添加健康检查

ini 复制代码
[Service]
HealthCheckSec=30  # 每30秒检查一次
HealthCheckCmd=/opt/apps/healthcheck.sh
HealthCheckFailureAction=restart  # 检查失败则重启

SpringBoot需暴露健康端点

yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true

"健康检查是防止'僵尸进程'的有效手段。我们可能会遇到应用进程存在但无法提供服务的情况,有了健康检查后,systemd会自动重启这类异常应用。"

防止无限重启策略

无论是systemd还是Supervisor,都需要配置重启限制,避免应用陷入重启循环:

systemd配置

ini 复制代码
[Unit]
StartLimitInterval=600  # 10分钟内
StartLimitBurst=5  # 最多重启5次
StartLimitAction=poweroff  # 达到限制后关闭系统(极端情况)

Supervisor配置

ini 复制代码
[program:demo-app]
autorestart=unexpected  # 仅意外退出时重启
exitcodes=0,143  # 正常退出状态码

总结

按照这些步骤配置后,你的SpringBoot应用将具备服务器重启或应用异常down掉后自动恢复的能力。

相关推荐
Nejosi_念旧21 分钟前
解读 Go 中的 constraints包
后端·golang·go
风无雨25 分钟前
GO 启动 简单服务
开发语言·后端·golang
Hellyc25 分钟前
用户查询优惠券之缓存击穿
java·redis·缓存
小明的小名叫小明25 分钟前
Go从入门到精通(19)-协程(goroutine)与通道(channel)
后端·golang
斯普信专业组30 分钟前
Go语言包管理完全指南:从基础到最佳实践
开发语言·后端·golang
今天又在摸鱼1 小时前
Maven
java·maven
老马啸西风1 小时前
maven 发布到中央仓库常用脚本-02
java·maven
代码的余温1 小时前
MyBatis集成Logback日志全攻略
java·tomcat·mybatis·logback
ladymorgana2 小时前
【spring boot】三种日志系统对比:ELK、Loki+Grafana、Docker API
spring boot·elk·grafana
一只叫煤球的猫2 小时前
【🤣离谱整活】我写了一篇程序员掉进 Java 异世界的短篇小说
java·后端·程序员