努力
加油

在单台云服务器上运行多个JavaWeb服务有哪些注意事项?

在单台云服务器上部署多个 JavaWeb 服务,听起来简单,但一旦流量上来或者业务复杂度增加,坑非常多。很多新手觉得“跑通 Hello World”就是成功,其实真正的挑战在于资源隔离、端口冲突、日志混乱和故障排查。

作为过来人,我总结了几条核心注意事项,分为“硬伤规避”和“运维优化”两个层面来讲。

一、 硬伤规避:别让自己半夜被叫醒

1. 端口冲突是第一大敌

JavaWeb 应用(尤其是 Spring Boot)默认使用 8080 或 8081。如果你直接启动两个 jar 包,第二个肯定起不来。

  • 做法:不要依赖默认的端口。通过 JVM 参数 -Dserver.port=xxx 或者配置文件 application.yml/properties 明确指定每个服务的端口。
  • 进阶:如果服务很多,建议引入 Nginx 做反向XX,前端统一走 80/443 端口,后端通过域名或路径区分服务,这样你只需要关注内网端口不冲突即可。

2. 内存溢出(OOM)与 GC 风暴

这是最隐蔽也最致命的。单台服务器的总内存是有限的(比如 4G 或 8G)。如果你开了 5 个 Java 服务,每个都设置堆内存为 2G,服务器瞬间就会 Swap 交换甚至宕机。

  • 原则:堆内存总和 < 物理内存的 70%-80%。剩下的内存留给操作系统、非堆内存(Metaspace)、Direct Memory 以及数据库缓存等。
  • 配置技巧:
    • 使用 -Xms 和 -Xmx 设置相同值,避免频繁扩容带来的性能抖动。
    • 对于微服务架构,尽量控制单个服务的初始堆大小。例如,4G 内存的机器,跑 4 个中等规模的服务,每个服务的 -Xmx 建议设在 512M-1G 之间。
    • 开启 G1 垃圾回收器(JDK 9+ 推荐),对多核和多线程场景更友好:-XX:+UseG1GC。

3. 文件描述符限制(Too many open files)

Java Web 服务涉及大量网络连接和文件操作。Linux 系统默认的单进程文件描述符限制通常是 1024。当并发连接数上来时,很容易报错 java.net.SocketException: Too many open files。

  • 检查命令:ulimit -n
  • 解决:修改 /etc/security/limits.conf,将 nofile 调大,比如调到 65535。重启后生效。

4. 日志地狱

多个服务同时运行,如果所有日志都输出到控制台或默认的文件,你会面临两个问题:

  • 磁盘 IO 瓶颈:多个进程疯狂写同一个磁盘区域,影响整体性能。
  • 排查困难:出了问题,你怎么知道哪行日志属于哪个服务?
  • 最佳实践:
    • 必须分离日志目录:每个服务独立输出到自己的 log 文件夹(如 /var/log/service-a/, /var/log/service-b/)。
    • 使用日志框架轮转机制:Logback 或 Log4j2 配置 RollingFileAppender,按天或按大小切割日志,并保留一定天数,防止磁盘被打满。
    • 注意权限:确保运行 Java 进程的用户有写入对应日志目录的权限。

二、 运维优化:让系统更稳定、易管理

1. 进程守护:别让进程挂了没人知道

用 nohup java -jar ... & 这种野蛮方式启动,一旦 SSH 断开或者服务器重启,服务就没了。

  • 推荐方案 A(轻量级):使用 systemd。编写 .service 文件,配置 Restart=always,开机自启。这是 Linux 原生且最稳定的方式。
  • 推荐方案 B(容器化):虽然问题是“单台服务器”,但强烈建议使用 Docker。Docker 天然解决了环境隔离、端口映射、日志收集和自动重启的问题。即使不用 K8s,单机 Docker Compose 也是极佳的选择。

2. 防火墙与安全组

不要只依赖云厂商的安全组!安全组是网络层面的第一道防线,但服务器内部也要有意识。

  • 策略:只开放必要的端口(如 80, 443, 22)。内部服务间通信如果使用内网 IP,尽量不暴露网络端口。
  • 注意:Spring Boot Actuator 端点(如 /actuator/env)默认可能暴露敏感信息,生产环境务必关闭或加认证。

3. 监控与告警:你需要一双眼睛

单台服务器上跑多个服务,你不可能时刻盯着 CPU 和内存。

  • 基础监控:安装 htop、netstat 随时查看。
  • 专业监控:推荐部署 Prometheus + Grafana。
    • 在每个 Java 应用中集成 Micrometer,暴露 metrics。
    • 重点监控:JVM Heap Usage、Thread Count、GC Time、HTTP Request Rate 和 Error Rate。
    • 配置阈值告警:比如 CPU 持续 10 分钟超过 80%,或者某个服务响应时间超过 2 秒,通过钉钉/企业微信机器人通知你。

4. 优雅停机(Graceful Shutdown)

当你需要更新服务或重启机器时,直接 kill -9 会导致正在处理的请求失败,用户体验极差。

  • Spring Boot 支持:从 2.3 版本开始,Spring Boot 原生支持优雅停机。只需在配置文件中添加:
    server:
      shutdown: graceful
    spring:
      lifecycle:
        timeout-per-shutdown-phase: 30s

    配合 systemd 的 TimeoutStopSec 设置,可以实现等待当前请求处理完毕后再关闭 JVM。

5. 时区与编码统一

  • 时区:确保服务器时区、JVM 时区、数据库时区一致。建议在启动脚本中强制指定 -Duser.timezone=Asia/Shanghai,避免因夏令时或跨时区导致的时间戳混乱。
  • 编码:统一 UTF-8。在 JVM 启动参数中加入 -Dfile.encoding=UTF-8,防止日志乱码或数据库交互出现中文问号。

三、 一个实用的启动脚本示例(Systemd 风格思路)

假设你要运行 app-service.jar,以下是推荐的启动逻辑片段:

#!/bin/bash
APP_NAME="my-web-app"
APP_JAR="/opt/apps/${APP_NAME}.jar"
LOG_DIR="/var/log/${APP_NAME}"
PID_FILE="/var/run/${APP_NAME}.pid"

# 创建日志目录
mkdir -p $LOG_DIR

# 启动命令
java 
  -Xms512m -Xmx512m 
  -XX:+UseG1GC 
  -Dspring.profiles.active=prod 
  -Dserver.port=8081 
  -Dlogging.file.path=${LOG_DIR} 
  -jar ${APP_JAR} > ${LOG_DIR}/stdout.log 2>&1 &

echo $! > $PID_FILE

总结

在单台云服务器上跑多个 JavaWeb 服务,核心思想是 “隔离” 和 “可控”。

  1. 资源隔离:通过 JVM 参数限制内存,防止一个服务拖垮整个机器。
  2. 环境隔离:独立的端口、独立的日志目录、独立的进程管理(Systemd/Docker)。
  3. 状态可控:完善的监控告警,让你能在用户投诉前发现问题。

最后提醒一点:单节点是高风险架构。无论你的优化做得多好,它都是单点故障。如果业务允许,尽早考虑双机热备、负载均衡或迁移到云原生平台(K8s/ECS 集群),这才是长久之计。