在 Linux 服务器上部署两个 Spring Boot 服务,本质上就是进程管理和端口隔离的问题。
别整那些虚的,直接上干货。根据你对运维复杂度、资源利用率和发布效率的要求,主要有三种主流方案。我会按“从简单粗暴到生产标准”的顺序给你拆解,最后给个选型建议。
方案一:最基础版——直接运行 JAR 包(适合测试/个人项目)
这是最简单的,不需要任何额外工具,但维护起来很痛苦(日志难找、挂了不会自动重启)。
核心逻辑:
- 确保两个服务的
application.yml或application.properties中配置的 server.port 不同(比如一个 8080,一个 8081)。 - 确保数据库连接、Redis 缓存 Key 等没有冲突。
操作步骤:
# 1. 上传两个 jar 包到服务器
scp app-service-a.jar user@server:/opt/apps/
scp app-service-b.jar user@server:/opt/apps/
# 2. 后台启动第一个服务,并指定日志输出
nohup java -jar /opt/apps/app-service-a.jar --server.port=8080 > /var/log/service-a.log 2>&1 &
# 3. 后台启动第二个服务
nohup java -jar /opt/apps/app-service-b.jar --server.port=8081 > /var/log/service-b.log 2>&1 &
缺点:
- 进程挂了你得手动去查。
- 重启麻烦,得先 kill 掉旧进程。
- 资源限制不好控。
方案二:进阶版——使用 Systemd 管理(推荐中小规模生产环境)
这是目前国内大多数中型互联网公司最常用的方式。Linux 自带的 systemd 是标准的进程管理器,自带日志轮转、开机自启、崩溃自动重启等功能。比 nohup 稳定得多。
核心逻辑:
为每个 Spring Boot 服务创建一个独立的 .service 文件。
操作步骤:
-
创建服务配置文件
在/etc/systemd/system/目录下创建两个文件,例如app-a.service和app-b.service。app-a.service内容示例:[Unit] Description=Spring Boot Service A After=syslog.target [Service] User=root ExecStart=/usr/bin/java -jar /opt/apps/app-service-a.jar --server.port=8080 SuccessExitStatus=143 Restart=on-failure # 可选:设置内存上限,防止 OOM 拖垮服务器 MemoryMax=512M [Install] WantedBy=multi-user.targetapp-b.service同理,修改文件名、Description、ExecStart 路径和端口。 -
重载配置并启动
# 加载新的 service 文件 systemctl daemon-reload # 启动服务 systemctl start app-a systemctl start app-b # 设置开机自启 systemctl enable app-a systemctl enable app-b -
日常运维命令
# 查看状态 systemctl status app-a # 查看实时日志(替代 tail -f) journalctl -u app-a -f # 重启 systemctl restart app-a
优点:
- 原生支持,无需安装第三方软件。
- 日志由 systemd 统一管理,可通过
journalctl轻松排查。 - 支持自动重启和资源限制。
方案三:专业版——Docker 容器化部署(强烈推荐,尤其是微服务架构)
如果你已经在使用 Kubernetes (K8s) 或者 Docker Swarm,或者希望环境隔离彻底,Docker 是唯一正解。即使不用 K8s,单机 Docker 也是最佳实践。
核心逻辑:
每个 Spring Boot 应用打包成一个独立的 Docker 镜像,通过 Docker Compose 或独立容器运行。
操作步骤(以 Docker Compose 为例):
-
编写 Dockerfile (假设两个服务结构类似,可以复用)
FROM openjdk:17-jdk-slim WORKDIR /app COPY target/*.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"] -
编写 docker-compose.yml
version: '3.8' services: service-a: build: ./service-a ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod restart: always service-b: build: ./service-b ports: - "8081:8081" environment: - SPRING_PROFILES_ACTIVE=prod restart: always -
启动
docker-compose up -d
优点:
- 环境绝对隔离:Java 版本、依赖库互不影响。
- 迁移方便:换一台服务器,只要装好 Docker,拷贝 yaml 文件即可恢复。
- 资源可控:可以轻松限制 CPU 和内存。
- 易于扩展:未来如果需要横向扩展,只需改副本数。
关键注意事项(避坑指南)
无论选哪种方案,以下三点必须检查,否则必出问题:
-
端口冲突:
确保两个服务的server.port不同。如果其中一个用了默认 8080,另一个必须显式配置其他端口(如 8081, 9090 等)。 -
共享资源竞争:
- 数据库:通常没问题,DB 在网络或独立实例。
- Redis/MQ:确认 Key 前缀是否区分?比如
user:a:info和user:b:info。如果共用同一个 Redis 实例且 Key 不区分,会导致数据串扰。 - 本地文件存储:如果服务读写本地磁盘(如上传图片),确保路径不冲突,或者挂载不同的 Volume/Disk。
-
防火墙/安全组:
在国内云厂商(阿里云、腾讯云、华为云等)上,仅仅在 Linux 内部开放端口是不够的。- 你必须在云控制台的 安全组(Security Group) 中放行对应的端口(如 8080, 8081)。
- 同时检查 Linux 内部的
firewalld或iptables是否拦截。
选型建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 个人博客/小型测试 | 方案一 (nohup) | 快,省事,没人关心稳定性。 |
| 中小型生产项目 | 方案二 (Systemd) | 稳定,轻量,无需引入 Docker 复杂性,运维成本低。 |
| 微服务/大型项目/团队开发 | 方案三 (Docker) | 标准化,可移植性强,便于后续接入 CI/CD 和 K8s。 |
| 高并发/多实例 | 方案三 + Nginx | 单个服务器扛不住时,用 Nginx 做反向XX,负载均衡到多个 Docker 容器或多个服务器。 |
总结:
如果你们团队还没有容器化能力,强烈建议从 Systemd 入手,它比 nohup 靠谱太多,又比 Docker 轻量。如果已经有 DevOps 流程,直接上 Docker Compose 或 K8s,这是趋势。
别纠结,先跑起来,再优化。
云技术博客