跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

2G 内存云服务器部署 Spring Boot + MySQL 实践

在 2G 内存云服务器上部署 Spring Boot 和 MySQL 时,全容器化方案因内存不足导致连接失败。通过混合部署架构(MySQL 容器 + 宿主机运行 Java 和 Nginx),限制 JVM 堆内存并优化数据库配置,成功解决资源瓶颈。配合 Swap 分区兜底,实现了稳定运行。建议小规格服务器避免全容器化,采用混合方案更务实。

鲜活发布于 2026/3/16更新于 2026/6/2631 浏览
2G 内存云服务器部署 Spring Boot + MySQL 实践

2G 内存云服务器部署 Spring Boot + MySQL 实践

前言

在低配服务器上部署全栈项目过程中遇到不少问题。本文记录完整的部署过程和问题排查思路,希望对同样在小规格服务器上部署 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 磁盘
  • Linux Server

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

1.1 docker-compose.yml

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

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 排查过程

逐项排查:

# 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 容器

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

# 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 随意扩张。

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

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 效果

# 启动耗时
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 起一个临时的文件接收服务:

# 服务端(服务器上执行)
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()
# 客户端(本地执行)
tar -czf web.tar.gz web/
curl -X PUT --data-binary @web.tar.gz http://服务器 IP:9999/upload
# 服务端解压
cd /opt/vonblog && tar -xzf web.tar.gz

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

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

四、内存优化建议

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

4.1 JVM 参数

# 严格限制堆内存
-Xms256m -Xmx512m
# 如果更紧张,可以进一步压缩
-Xms128m -Xmx256m
# 使用 G1GC(Java 17 默认),低延迟友好
-XX:+UseG1GC

4.2 MySQL 内存调优

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

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

4.3 Swap 兜底

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

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 机器上,混合方案是更务实的选择。

目录

  1. 2G 内存云服务器部署 Spring Boot + MySQL 实践
  2. 前言
  3. 一、方案一:Docker Compose 全容器化(失败)
  4. 1.1 docker-compose.yml
  5. 1.2 问题现象
  6. 1.3 排查过程
  7. 1. MySQL 容器状态 — 正常
  8. db: Up 18 minutes (healthy)
  9. 2. DNS 解析 — 正常
  10. Address: 172.19.0.2
  11. 3. MySQL 用户权限 — 已授权
  12. 4. 端口监听 — MySQL 确实在监听
  13. 1.4 根因分析
  14. 二、方案二:混合部署(成功)
  15. 2.1 架构调整
  16. 2.2 步骤一:启动 MySQL 容器
  17. 2.3 步骤二:安装 Java 17
  18. TencentOS / CentOS / RHEL 系
  19. 验证
  20. openjdk version "17.0.18"
  21. 2.4 步骤三:运行 Spring Boot jar
  22. 2.5 步骤四:配置 Nginx
  23. 2.6 效果
  24. 启动耗时
  25. 内存占用
  26. Mem: 1.9Gi total, ~400Mi free, ~1.1Gi used (含 buff/cache)
  27. MySQL 容器约 300MB + JVM 约 400MB + Nginx 约 20MB + 系统约 300MB
  28. 三、前端文件传输的曲折
  29. 3.1 SCP 被断连
  30. 3.2 解决方案:临时 HTTP 上传服务
  31. 服务端(服务器上执行)
  32. 接收完自动退出
  33. 客户端(本地执行)
  34. 服务端解压
  35. 四、内存优化建议
  36. 4.1 JVM 参数
  37. 严格限制堆内存
  38. 如果更紧张,可以进一步压缩
  39. 使用 G1GC(Java 17 默认),低延迟友好
  40. 4.2 MySQL 内存调优
  41. 4.3 Swap 兜底
  42. 五、待优化
  43. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 前后端分离架构 vs 传统架构:核心差异与选型指南
  • C++ 类与对象进阶特性与编译器优化实战
  • 大语言模型(LLM)原理与应用实战
  • SKResNet 架构详解:融合选择性卷积与残差结构
  • MIT 电机模式控制:参数、场景与调试指南
  • Python 中的“==”与 is:本质区别与实战优化
  • 基于 AD7606 的 8 通道高速同步采集系统设计与 Verilog 实现
  • 前端文件上传进阶:分片、断点续传与体验优化
  • IntelliJ IDEA 切换 Git 用户配置详解
  • 复制带随机指针的链表:三步法原地深拷贝详解
  • Spring Boot 中 MultipartFile 转 File 对象的四种实现方案
  • PHP Token 主流实现方案深度解析
  • 前端函数防抖详解:原理、手写实现与 Lodash 应用
  • 教育权益验证自动化流程说明
  • CapCutAPI:开源剪映编程控制与视频编辑方案
  • Flutter 底部导航与顶部选项卡实战:状态保持与鸿蒙适配
  • SDXL Prompt Styler 提示词风格化工具使用指南
  • 使用谷歌云端硬盘部署 Stable Diffusion 个人 AI 绘画环境
  • 利用 Frontend-Design Skill 增强大模型前端审美与代码质量
  • FPGA 实现 CAN 总线原理与 Verilog 代码详解

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online