停止用 nohup 启动生产!教你企业级 SpringBoot 部署方案(含无 Root 版)

摘要: 还在用 java -jar 配合 nohup 跑生产?服务器重启服务就挂?日志文件乱成一团?无 Root 权限怎么做进程守护?本文手把手教你使用 Systemd + Nginx 打造稳健的 Java 应用部署架构。

在开发环境,我们习惯了随手敲下一行:
nohup java -jar my-app.jar &

这确实能跑,但在生产环境,这种做法就像是在高压线上走钢丝。
进程管理混乱:想重启?先 pskill,手一抖可能误杀其他服务。
开机无法自启:服务器因故障重启,深夜接到报警电话的痛,你懂的。
权限失控:为了省事直接用 root 跑?黑客都要谢谢你。

今天,我们来聊聊如何用 Linux 原生的 Systemd,优雅、安全地管理 SpringBoot 应用。(文末包含无 Root 权限的内网环境解决方案)


方案一:标准生产环境(拥有 Sudo/Root 权限)

这是最推荐的标准做法,适用于你有权管理服务器配置的场景。

1. 准备工作:创建专用账户

为了安全,绝对不要使用 root 用户运行业务代码。

# 创建一个没有登录权限的用户 app_user
sudo useradd -r -s /bin/false app_user
# 确保该用户对 jar 包有读取权限
sudo chown app_user:app_user /opt/prod/app.jar

2. 编写 Service 单元文件

Systemd 通过 .service 文件管理进程。创建文件 /etc/systemd/system/myapp.service

[Unit]
Description=My SpringBoot Enterprise App
# 在网络服务启动后再启动
After=syslog.target network.target

[Service]
# 【关键】指定运行用户,实现权限隔离
User=app_user
Group=app_user

# 【核心】启动命令 (建议指定 JVM 参数)
ExecStart=/home/zml/.jdks/corretto-1.8.0_412/bin/java -Djava.ext.dirs=/home/zml/.jdks/corretto-1.8.0_412/jre/lib/ext:/home/zml/.jdks/corretto-1.8.0_412/lib/ext  -Xms512m -Xmx512m -Xmn256m -Xloggc:/opt/share/app/demo/logs/sso_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dproject.home=/opt/share/app/sso -jar /opt/share/app/demo/myapp-user.jar  --server.max-http-header-size=524288 --spring.config.location=file:/opt/share/app/demo/config/

# 【关键】Spring Boot 优雅关闭
# 143 代表 SIGTERM 信号,Java 应用正常退出时的状态码
SuccessExitStatus=143

# 崩溃自动重启配置
Restart=always
RestartSec=10

# 日志由 Systemd 接管 (可用 journalctl 查看)
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp

[Install]
WantedBy=multi-user.target

3. 三板斧:激活与管理

# 1. 重载配置
sudo systemctl daemon-reload

# 2. 设置开机自启 (从此告别重启焦虑)
sudo systemctl enable myapp

# 3. 启动服务
sudo systemctl start myapp

方案二:内网受限环境(无 Root 权限)

这是很多银行、国企开发者的痛点:只有普通账号,没有 sudo 权限,Systemd 还能用吗?
答案是:能!Systemd 支持用户模式(User Mode)。

1. 关键前提:开启“驻留模式”

默认情况下,用户退出 SSH,用户下的 Systemd 进程会被杀掉。
你需要找管理员执行唯一的一次操作(或者祈祷管理员已经开启了它):

# 检查方法,如果输出 Linger=no,则需要在服务器上执行一次开启操作。
loginctl show-user $USER --property=Linger
# 开启用户驻留,允许服务在用户注销后继续运行
sudo loginctl enable-linger <你的用户名>

2. 配置用户级服务

配置文件位置变了,放在 ~/.config/systemd/user/ 下。

mkdir -p ~/.config/systemd/user
vim ~/.config/systemd/user/myapp.service

配置差异点:

  • 不需要 User=Group= 字段。
  • [Install] 部分改为 WantedBy=default.target
[Unit]
Description=My User Space App
After=network.target

[Service]
# 注意:使用绝对路径
ExecStart=/home/zml/.jdks/corretto-1.8.0_412/bin/java -Djava.ext.dirs=/home/zml/.jdks/corretto-1.8.0_412/jre/lib/ext:/home/zml/.jdks/corretto-1.8.0_412/lib/ext  -Xms512m -Xmx512m -Xmn256m -Xloggc:/opt/share/app/demo/logs/sso_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dproject.home=/opt/share/app/sso -jar /opt/share/app/demo/myapp-user.jar  --server.max-http-header-size=524288 --spring.config.location=file:/opt/share/app/demo/config/
SuccessExitStatus=143
Restart=always
RestartSec=10
SyslogIdentifier=myapp-user

[Install]
# 【注意】这里不同于系统级配置
WantedBy=default.target

3. 用户级管理命令

所有命令加上 --user 参数即可,完全不需要 sudo:

# 启动
systemctl --user start myapp
# 开机自启 (随服务器启动)
systemctl --user enable myapp
# 查看日志
journalctl --user -u myapp -f

进阶 Tips (让你的部署更专业)

1. JVM 参数别乱写

不要裸奔运行 java -jar。至少要指定堆内存大小,避免从宿主机抢占过多内存导致 OOM 被系统杀掉。

  • -Xms: 初始堆内存
  • -Xmx: 最大堆内存
  • 生产环境建议 -Xms-Xmx 设置为相同值,避免内存抖动。

2. 优雅停机 (Graceful Shutdown)

在 Systemd 配置中我们提到了 SuccessExitStatus=143
Spring Boot 2.3+ 支持优雅停机。在 application.yml 中配置:

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

这能保证服务停止时,不再接收新请求,但会处理完当前正在进行的请求,避免用户操作中断。

3. 关于日志

Systemd 使用 journalctl 管理日志是很好的选择,但如果你的应用日志量巨大,建议结合 Log4j2/LogbackRollingFileAppender 将日志输出到文件,并使用 Filebeat 采集到 ELK 栈中,而不是单纯依赖控制台输出。

为什么这么做?

  1. 自动保活Restart=always 保证服务挂了自动拉起,比写 Shell 监控脚本靠谱一万倍。
  2. 统一日志:使用 journalctl 可以按时间、按服务检索日志,不再对着几 GB 的 nohup.out 发愁。
  3. 优雅停机:配合 SpringBoot 的 server.shutdown=graceful,Systemd 发送停止信号时,应用会处理完当前请求再关闭,保证业务数据完整。

告别黑窗口,拥抱标准化的部署方式,是一个工程师走向专业的必经之路。


进阶配置:Nginx 反向代理

不管是 Root 模式还是普通用户模式(通常只能监听 >1024 端口),你都需要 Nginx 在前面做反向代理。

推荐配置 (nginx.conf):

server {
    listen 80;
    server_name www.your-company.com;

    # 【优化 1】解除上传文件大小限制
    # 默认只有 1M,生产环境建议设置为 100M 或更大
    client_max_body_size 100m; 

    location / {
         # 转发到本地 Systemd 跑的 Java 服务
        proxy_pass http://localhost:8080;
        
        # 【关键】透传真实 IP
        # 如果不加这些,Java 代码里的 request.getRemoteAddr() 拿到的全是 127.0.0.1
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        #长连接与超时设置 (解决 504 错误)
        # 建立连接超时时间 (通常默认 60s 够用)
        proxy_connect_timeout 60s;
        
        # 核心:后端处理等待时间
        # 如果你的 SpringBoot 有导出报表、发送邮件等长耗时任务,
        # 务必将此值调大,否则 Nginx 会掐断连接
        proxy_read_timeout 300s;
        
        # 发送请求超时时间
        proxy_send_timeout 300s;
    }
    # 动静分离:静态资源由 Nginx 直接处理,不经过 Java
    location /static/ {
        alias /opt/prod/static/;
        expires 30d;
    }
}

👇 互动话题
你们公司的生产环境还在用 nohup 吗?遇到过哪些坑?欢迎在评论区留言交流!

Q.E.D.


寻门而入,破门而出