在实际项目中,如果需要你部署系统,可以参考这个文章。
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流水线
- 云原生环境
- 需要快速扩缩容
本方案的核心优势
- 简单可靠:无需学习Docker,直接使用系统原生功能
- 资源高效:无容器开销,内存和CPU使用更优
- 运维友好:使用标准的systemctl命令管理
- 故障恢复:自动重启机制保证服务可用性
- 日志统一:与系统日志完美集成
- 部署快速:配置简单,部署迅速
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. 关键要点总结
- Type=simple:Java可以通过使用simple类型
- 使用exec:让Java进程成为systemd直接管理的进程
- 使用tee:同时输出到文件和控制台
- 添加sleep:重启时等待进程完全停止
- 绝对路径:所有路径都使用绝对路径
理解
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=30
PrivateTmp=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 主要用于以下场景:
主要使用场景
- 传统守护进程模式
当程序启动后会 fork() 一个子进程然后父进程退出:
# 示例流程:
# 1. 启动主进程 (PID 100)
# 2. 主进程 fork() 子进程 (PID 101)
# 3. 主进程退出 (PID 100 结束)
# 4. 子进程继续运行 (PID 101)
- Java 应用的特殊情况
某些 Java 启动脚本会 fork 后台进程:
# 比如使用 nohup 或 & 后台运行
nohup java -jar app.jar &
# 或者脚本中调用 fork()
参考时间:2025年10月10日
适用场景:Java Spring Boot应用