后台开发-Java应用Systemd自动化启动脚本参考示范

103 阅读7分钟

在实际项目中,如果需要你部署系统,可以参考这个文章。

Java应用Systemd自动化启动脚本参考示范

系统要求

本自动化脚本基于 systemd 系统管理工具,需要系统支持 systemctl 命令。

支持systemctl的常见Linux发行版

✅ 主流支持(推荐使用)
  • CentOS 7+ / RHEL 7+ - 默认使用systemd
  • Ubuntu 15.04+ - 默认使用systemd
  • Debian 8+ - 默认使用systemd
  • Fedora 15+ - 默认使用systemd
  • openSUSE 12.2+ - 默认使用systemd
  • Arch Linux - 默认使用systemd
  • SUSE Linux Enterprise 12+ - 默认使用systemd
  • openEuler - 默认使用systemd(华为开源操作系统)
⚠️ 部分支持
  • Ubuntu 14.04 - 可选安装systemd
  • Debian 7 - 可选安装systemd
❌ 不支持
  • CentOS 6 / RHEL 6 - 使用Upstart/init.d
  • Ubuntu 14.04及更早版本 - 使用Upstart
  • Debian 6及更早版本 - 使用SysV init

检查系统是否支持systemctl

# 检查systemctl命令是否存在
which systemctl
​
# 检查systemd版本
systemctl --version
​
# 检查服务状态
systemctl list-units --type=service

如果以上命令正常执行,说明系统支持systemctl,可以使用本脚本。

为什么选择自动化启动脚本

传统部署方式的对比

1. 手动启动 vs 自动化脚本

❌ 手动启动问题:

# 每次都需要手动执行
nohup java -jar app.jar > app.log 2>&1 &
# 进程可能意外退出,需要手动重启
# 系统重启后需要手动启动服务
# 难以统一管理和监控

✅ 自动化脚本优势:

  • 系统启动时自动启动服务
  • 进程异常退出时自动重启
  • 统一的启动、停止、重启管理
  • 完整的日志管理和监控
2. Docker部署 vs 系统服务部署
特性Docker部署系统服务部署(本方案)
资源占用较高(容器开销)较低(直接运行)
启动速度较慢(镜像拉取+启动)较快(直接启动)
内存使用更多(容器+应用)更少(仅应用)
系统集成需要Docker环境原生系统集成
网络配置需要端口映射直接使用系统端口
文件系统需要挂载卷直接访问系统文件
日志管理Docker日志+应用日志统一系统日志
调试便利性需要进入容器直接访问系统环境
部署复杂度高(镜像构建+部署)低(脚本配置)
3. 适用场景分析

✅ 推荐使用系统服务部署的场景:

  • 生产环境长期运行的服务
  • 资源敏感的环境(内存、CPU有限)
  • 需要与系统深度集成的应用
  • 简单的单体Java应用
  • 不需要多环境隔离的场景
  • 团队对Docker不熟悉

✅ 推荐使用Docker部署的场景:

  • 微服务架构
  • 需要环境隔离
  • 多版本应用共存
  • CI/CD流水线
  • 云原生环境
  • 需要快速扩缩容

本方案的核心优势

  1. 简单可靠:无需学习Docker,直接使用系统原生功能
  2. 资源高效:无容器开销,内存和CPU使用更优
  3. 运维友好:使用标准的systemctl命令管理
  4. 故障恢复:自动重启机制保证服务可用性
  5. 日志统一:与系统日志完美集成
  6. 部署快速:配置简单,部署迅速

1. 正确的配置文件模板

1.1 Systemd服务配置文件

创建如下文件,名字叫做your-app.service

[Unit]
Description=your-app-name
# 依赖项,在这些程序之后启动
# After=NetworkManager.service mysqld.service

[Service]
Type=simple
# 配置启动脚本
ExecStart=/bin/bash /path/to/your-app/start.sh
# 配置重启脚本
ExecReload=/bin/bash /path/to/your-app/restart.sh
# 配置停止脚本
ExecStop=/bin/bash /path/to/your-app/stop.sh

# 工作目录
WorkingDirectory=/path/to/your-app
# 用户
User=your-user
# 重启策略
Restart=always
RestartSec=10
# 超时设置
TimeoutStartSec=60
TimeoutStopSec=30

PrivateTmp=true
 
[Install]
WantedBy=multi-user.target

1.2 启动脚本 (start.sh)

#!/bin/bash
# 设置环境变量(如果需要)
source /path/to/your-env-setup.sh
​
# 启动应用(systemd会管理进程)
exec java -jar -Dspring.profiles.active=prod -Dserver.port=8080 /path/to/your-app/your-app.jar 2>&1 | tee /path/to/your-app/app.log

1.3 重启脚本 (restart.sh)

#!/bin/bash
# 停止应用
/usr/bin/pkill -f your-app.jar
# 等待进程完全停止
sleep 5
# 设置环境变量
source /path/to/your-env-setup.sh
# 启动应用
exec java -jar -Dspring.profiles.active=prod -Dserver.port=8080 /path/to/your-app/your-app.jar 2>&1 | tee /path/to/your-app/app.log

1.4 停止脚本 (stop.sh)

#!/bin/bash
/usr/bin/pkill -f your-app.jar

2. 部署命令

# 1. 复制服务配置文件
sudo cp your-app.service /etc/systemd/system/
​
# 2. 重新加载systemd配置
sudo systemctl daemon-reload
​
# 3. 启用服务(开机自启)
sudo systemctl enable your-app
​
# 4. 启动服务
sudo systemctl start your-app
​
# 5. 检查状态
sudo systemctl status your-app

3. 常见错误及避免方法

3.1 ❌ 错误:缺少等待时间

/usr/bin/pkill -f app.jar
# 立即启动,可能进程还没完全停止
exec java -jar app.jar

✅ 正确:

/usr/bin/pkill -f app.jar
sleep 5  # 等待进程完全停止
exec java -jar app.jar

3.2 ❌ 错误:路径问题

# 相对路径可能出错
java -jar app.jar

✅ 正确:

# 使用绝对路径
java -jar /full/path/to/app.jar

4. 日志查看命令

# 查看应用日志文件
tail -f /path/to/your-app/app.log
​
# 查看systemd服务日志
sudo journalctl -u your-app -f
​
# 查看服务状态
sudo systemctl status your-app

5. 关键要点总结

  1. Type=simple:Java可以通过使用simple类型
  2. 使用exec:让Java进程成为systemd直接管理的进程
  3. 使用tee:同时输出到文件和控制台
  4. 添加sleep:重启时等待进程完全停止
  5. 绝对路径:所有路径都使用绝对路径

理解 exec: 理解一个基本概念:在 Linux 中,当你运行一个命令(比如 ls),默认情况下,Shell 会先创建一个自身的副本(子进程) ,然后在这个子进程中去执行 ls。等 ls 结束后,控制权又回到父进程(原来的 Shell)。

exec 跳过了“创建子进程”这一步。它直接告诉操作系统:“停止运行我现在的程序(比如 Bash),把同一个进程的内存、环境等全部清空,然后加载并运行这个新程序(比如 ls)。”, 等这个新程序执行完毕,退出。

6. 某项目完整配置参考

6.1 某项目systemd配置文件 (demo-x.service)

[Unit]
Description=demoX
# 依赖项,在这些程序之后启动
# After=NetworkManager.service mysqld.service[Service]
Type=simple
# 配置启动脚本
ExecStart=/bin/bash /root/services/demo-x/start.sh
# 配置重启脚本
ExecReload=/bin/bash /root/services/demo-x/restart.sh
# 配置停止脚本
ExecStop=/bin/bash /root/services/demo-x/stop.sh
​
# 工作目录
WorkingDirectory=/root/services/demo-x
# 用户
User=root
# 重启策略
Restart=always
RestartSec=10
# 超时设置
TimeoutStartSec=60
TimeoutStopSec=30PrivateTmp=true
 
[Install]
WantedBy=multi-user.target

6.2 实际项目启动脚本 (start.sh)

#!/bin/bash
# 设置JDK环境
source /root/usr/use-jdk-11.sh
# 启动应用(同时输出到文件和控制台,systemd可以同时收集日志)
exec java -jar -Dspring.profiles.active=prod -Dserver.port=8080 /root/services/demo-x/demo-x-server.jar 2>&1 | tee /root/services/demo-x/demo-x.log

6.3 实际项目重启脚本 (restart.sh)

#!/bin/bash
# 停止应用
/usr/bin/pkill -f dataflow-x-server.jar
# 等待进程完全停止
sleep 2
# 设置JDK环境
source /root/usr/use-jdk-11.sh
# 启动应用(同时输出到文件和控制台,systemd可以同时收集日志)
exec java -jar -Dspring.profiles.active=prod -Dserver.port=8080 /root/services/dataflow-x/dataflow-x-server.jar 2>&1 | tee /root/services/dataflow-x/dataflow-x.log

6.4 实际项目停止脚本 (stop.sh)

/usr/bin/pkill -f dataflow-x-server.jar

6.5 实际项目部署命令

# 1. 复制服务配置文件
sudo cp deploy/demo-x.service /etc/systemd/system/
​
# 2. 重新加载systemd配置
sudo systemctl daemon-reload
​
# 3. 启用服务(开机自启)
sudo systemctl enable demo-x
​
# 4. 启动服务
sudo systemctl start demo-x
​
# 5. 检查状态
sudo systemctl status demo-x

6.6 实际项目日志查看

# 查看应用日志文件
tail -f /root/services/demo-x/demo-x.log
​
# 查看systemd服务日志
sudo journalctl -u demo-x -f
​
# 查看最近的错误日志
sudo journalctl -u demo-x --since "1 hour ago" -p err

附录

type的用法

在 systemd 服务中,Type=forking 主要用于以下场景:

主要使用场景

  1. 传统守护进程模式
    当程序启动后会 fork() 一个子进程然后父进程退出
# 示例流程:
# 1. 启动主进程 (PID 100)
# 2. 主进程 fork() 子进程 (PID 101)  
# 3. 主进程退出 (PID 100 结束)
# 4. 子进程继续运行 (PID 101)
  1. Java 应用的特殊情况
    某些 Java 启动脚本会 fork 后台进程:
# 比如使用 nohup 或 & 后台运行
nohup java -jar app.jar &
# 或者脚本中调用 fork()

参考时间:2025年10月10日
适用场景:Java Spring Boot应用