2G 内存云服务器部署 Spring Boot + MySQL 实战:从踩坑到上线

2G 内存云服务器部署 Spring Boot + MySQL 实战:从踩坑到上线

前言

最近把自己的全栈博客项目部署到了腾讯云的入门级服务器(2核2G),过程中踩了不少坑。本文记录完整的部署过程和问题排查思路,希望对同样在小规格服务器上部署 Java 项目的同学有所帮助。

项目技术栈:

  • 后端:Java 17 + Spring Boot 3.2.3 + Spring Security + JPA
  • 数据库:MySQL 8.0
  • 前端:Flutter Web
  • 反向代理:Nginx 1.26
  • 容器:Docker 28.4

服务器配置:

  • 腾讯云轻量应用服务器
  • 2 核 CPU / 2GB 内存 / 50GB 磁盘
  • TencentOS Server 4 (x86_64)

一、方案一:Docker Compose 全容器化(失败)

1.1 docker-compose.yml

最初的方案是标准的全容器化部署:

yaml 复制代码
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: vonblog
      MYSQL_USER: vonblog
      MYSQL_PASSWORD: ${DB_PASSWORD}
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  backend:
    build: ./backend
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/vonblog?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      SPRING_DATASOURCE_USERNAME: vonblog
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}

  nginx:
    image: nginx:alpine
    depends_on:
      - backend
    ports:
      - "80:80"
      - "443:443"

1.2 问题现象

后端容器反复报错:

复制代码
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
Caused by: java.net.ConnectException: Connection refused

1.3 排查过程

逐项排查:

bash 复制代码
# 1. MySQL 容器状态 --- 正常
docker compose ps
# db: Up 18 minutes (healthy)

# 2. DNS 解析 --- 正常
docker exec vonblog-backend-1 nslookup db
# Address: 172.19.0.2

# 3. MySQL 用户权限 --- 已授权
docker exec vonblog-db-1 mysql -uroot -p -e "GRANT ALL ON vonblog.* TO 'vonblog'@'%';"

# 4. 端口监听 --- MySQL 确实在监听
docker exec vonblog-db-1 ss -tlnp | grep 3306

一切看起来都正常,但后端就是连不上。

1.4 根因分析

2GB 内存是瓶颈。

free -h 一看:

复制代码
              total        used        free      shared  buff/cache   available
Mem:           1.9Gi       588Mi       643Mi       8.0Mi       916Mi       1.3Gi

MySQL 8.0 默认配置下内存占用约 400-500MB,Spring Boot JVM 默认堆大小也是几百 MB。两个容器同时启动,加上 Docker 本身的开销,内存直接见底。

在内存不足的情况下,容器间的网络通信会出现各种诡异问题:TCP 连接建立失败、超时、被内核 OOM killer 干掉后重启等。Connection refused 只是表象,本质是资源不足。

二、方案二:混合部署(成功)

2.1 架构调整

复制代码
┌─────────────────────────────────────┐
│          腾讯云 2C2G 服务器           │
│                                     │
│  ┌───────────┐  ┌────────────────┐  │
│  │  Docker    │  │   宿主机        │  │
│  │  MySQL 8.0 │  │  Java 17 (jar) │  │
│  │  (容器)    │  │  Nginx (systemd)│  │
│  └───────────┘  └────────────────┘  │
│       ↑ 3306        ↑ 8080    ↑ 80  │
│       └──── 127.0.0.1 ────────┘     │
└─────────────────────────────────────┘

核心思路:MySQL 留在 Docker(数据隔离方便),Spring Boot 和 Nginx 直接跑在宿主机上(省内存)。

2.2 步骤一:启动 MySQL 容器

bash 复制代码
docker run -d \
  --name vonblog-mysql \
  --restart always \
  -e MYSQL_ROOT_PASSWORD=YourRootPassword \
  -e MYSQL_DATABASE=vonblog \
  -e MYSQL_USER=vonblog \
  -e MYSQL_PASSWORD=YourDbPassword \
  -p 3306:3306 \
  -v mysql_data:/var/lib/mysql \
  mysql:8.0 \
  --character-set-server=utf8mb4 \
  --collation-server=utf8mb4_unicode_ci

2.3 步骤二:安装 Java 17

bash 复制代码
# TencentOS / CentOS / RHEL 系
yum install -y java-17-openjdk-headless

# 验证
java -version
# openjdk version "17.0.18"

2.4 步骤三:运行 Spring Boot jar

关键:限制 JVM 堆内存,2G 服务器不能让 JVM 随意扩张。

bash 复制代码
mkdir -p /opt/vonblog/uploads

nohup java -Xms256m -Xmx512m \
  -jar /opt/vonblog/app.jar \
  --spring.profiles.active=prod \
  --spring.datasource.url="jdbc:mysql://127.0.0.1:3306/vonblog?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false" \
  --spring.datasource.username=vonblog \
  --spring.datasource.password=YourDbPassword \
  > /opt/vonblog/app.log 2>&1 &

echo "PID: $!"

注意几个要点:

  • -Xms256m -Xmx512m:堆内存限制在 256-512MB,给系统和 MySQL 留空间
  • 127.0.0.1:3306:通过宿主机 loopback 连接 Docker 映射出来的端口
  • allowPublicKeyRetrieval=true:MySQL 8.0 默认使用 caching_sha2_password,需要这个参数

2.5 步骤四:配置 Nginx

bash 复制代码
yum install -y nginx

cat > /etc/nginx/conf.d/vonblog.conf << 'EOF'
server {
    listen 80;
    server_name _;
    client_max_body_size 10M;

    # 前端静态文件
    root /opt/vonblog/web;
    index index.html;

    # API 反代
    location /api/ {
        proxy_pass http://127.0.0.1:8080/api/;
        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;
    }

    # 上传文件
    location /uploads/ {
        alias /opt/vonblog/uploads/;
        expires 30d;
    }

    # Flutter Web SPA 路由
    location / {
        try_files $uri $uri/ /index.html;
    }
}
EOF

systemctl enable nginx
systemctl start nginx

try_files $uri $uri/ /index.html 这行很重要------Flutter Web 是 SPA 单页应用,所有路由都要回落到 index.html,否则刷新页面会 404。

2.6 效果

bash 复制代码
# 启动耗时
Started VonBlogApplication in 10.393 seconds

# 内存占用
free -h
# Mem: 1.9Gi total, ~400Mi free, ~1.1Gi used (含 buff/cache)
# MySQL 容器约 300MB + JVM 约 400MB + Nginx 约 20MB + 系统约 300MB

刚好塞下,不富裕但够用。

三、前端文件传输的曲折

3.1 SCP 被断连

本地 flutter build web --release 构建完后用 SCP 上传,结果:

复制代码
Connection closed by 118.x.x.x port 22

反复尝试都被断。原因是之前多次 SSH 密码交互触发了服务器的入侵防护策略(fail2ban 或类似机制)。

3.2 解决方案:临时 HTTP 上传服务

在服务器上用 Python 起一个临时的文件接收服务:

python 复制代码
# 服务端(服务器上执行)
python3 -c "
import http.server, os
class H(http.server.BaseHTTPRequestHandler):
    def do_PUT(self):
        length = int(self.headers['Content-Length'])
        with open('/opt/vonblog/web.tar.gz', 'wb') as f:
            f.write(self.rfile.read(length))
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'OK')
        os._exit(0)  # 接收完自动退出
http.server.HTTPServer(('0.0.0.0', 9999), H).serve_forever()
" &
bash 复制代码
# 客户端(本地执行)
tar -czf web.tar.gz web/
curl -X PUT --data-binary @web.tar.gz http://服务器IP:9999/upload
bash 复制代码
# 服务端解压
cd /opt/vonblog && tar -xzf web.tar.gz

7.8MB 的压缩包,3 秒传完。简单粗暴但有效。

⚠️ 注意:这个方案没有任何认证,仅适合临时使用,用完立即关闭端口或进程。

四、内存优化建议

如果你也在 2G 服务器上跑 Java 项目,这些参数值得关注:

4.1 JVM 参数

bash 复制代码
# 严格限制堆内存
-Xms256m -Xmx512m

# 如果更紧张,可以进一步压缩
-Xms128m -Xmx256m

# 使用 G1GC(Java 17 默认),低延迟友好
-XX:+UseG1GC

4.2 MySQL 内存调优

如果 MySQL 也要省内存,可以在启动时追加参数:

bash 复制代码
docker run ... mysql:8.0 \
  --innodb-buffer-pool-size=128M \
  --max-connections=50 \
  --table-open-cache=200

4.3 Swap 兜底

2G 内存的服务器强烈建议开 Swap:

bash 复制代码
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

有 Swap 兜底,偶发的内存峰值不会直接触发 OOM killer。

五、待优化

  1. systemd 服务化 :目前后端用 nohup 启动,服务器重启后不会自动恢复。应该写一个 systemd service 文件。
  2. HTTPS:用 Let's Encrypt + certbot 配置免费 SSL 证书。
  3. 日志轮转app.log 会无限增长,需要配置 logrotate。
  4. 监控:没有任何监控,进程挂了无感知。至少应该加个健康检查脚本 + crontab。

总结

方案 优点 缺点 适用场景
Docker Compose 全容器 环境隔离、一键部署 内存开销大 4G+ 内存服务器
混合部署 省内存、连接稳定 管理稍复杂 2G 内存服务器
全宿主机 最省资源 环境污染 1G 极端场景

核心结论:小规格服务器不要迷信全容器化,够用就行。 Docker Compose 在 4G 以上内存的机器上体验很好,但在 2G 机器上,混合方案是更务实的选择。


本文基于实际项目部署经验撰写,项目技术栈:Spring Boot 3.2.3 + Flutter Web + MySQL 8.0 + Nginx + Docker

如有疑问欢迎评论交流。

相关推荐
liulilittle1 小时前
Ubuntu 系统 libc6-dev 依赖冲突解决
linux·运维·服务器·ubuntu·shell
飞Link1 小时前
洞察数据的“分寸感”:深度解析对比学习(Contrastive Learning)
开发语言·python·学习·数据挖掘
飞函安全1 小时前
Vite 8.0:Rust.bundle,性能提升10-30倍
开发语言·人工智能·rust
liulilittle1 小时前
Debian/Ubuntu 18.04 上安装 GLIBC 2.28 (2026)
linux·运维·服务器·开发语言·c++·ubuntu·debian
Volunteer Technology1 小时前
核心框架源码常见问题(下)
java·开发语言·spring
MMendex1 小时前
华为云项目实战day1
linux·服务器·nginx·华为云·ssh·负载均衡
不知名。。。。。。。。1 小时前
仿muduo库实现高并发服务器----通信链接管理Connection
运维·服务器·网络
狐571 小时前
2026-03-15-因服务器网络访问问题从 GitHub 平滑迁移至 Gitee 等国内平台的方法
服务器·gitee·github
荆楚闲人1 小时前
ubuntu启动时无登录框无法进入系统的急救方法
linux·服务器·ubuntu