一个故(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掉后自动恢复的能力。