跳到主要内容2G 内存云服务器部署 Spring Boot + MySQL 实战 | 极客日志Java大前端java
2G 内存云服务器部署 Spring Boot + MySQL 实战
在 2G 内存的云服务器上部署 Spring Boot 应用面临资源瓶颈。尝试全容器化方案因内存不足导致连接失败,最终采用混合部署架构,将数据库保留在 Docker 而应用运行于宿主机以节省资源。通过限制 JVM 堆内存、优化 MySQL 配置及开启 Swap 分区,成功稳定运行。此外还分享了小带宽下的文件传输技巧及后续系统服务化与监控的优化方向。
颠三倒四1 浏览 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 全容器化(失败)
最初的方案是标准的全容器化部署,试图将所有服务都跑在 Docker 里。
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"
问题现象
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
Caused by: java.net.ConnectException: Connection refused
排查过程
docker compose ps
docker exec vonblog-backend-1 nslookup db
docker exec vonblog-db-1 mysql -uroot -p -e "GRANT ALL ON vonblog.* TO 'vonblog'@'%';"
docker exec vonblog-db-1 ss -tlnp | grep 3306
根因分析
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 只是表象,本质是资源不足。
方案二:混合部署(成功)
架构调整
核心思路:MySQL 留在 Docker(数据隔离方便),Spring Boot 和 Nginx 直接跑在宿主机上(省内存)。
┌─────────────────────────────────────┐
│ 腾讯云 2C2G 服务器 │
│ │
│ ┌───────────┐ ┌────────────────┐ │
│ │ Docker │ │ 宿主机 │ │
│ │ MySQL 8.0 │ │ Java 17 (jar) │ │
│ │ (容器) │ │ Nginx (systemd)│ │
│ └───────────┘ └────────────────┘ │
│ ↑ 3306 ↑ 8080 ↑ 80 │
│ └──── 127.0.0.1 ────────────┘ │
└─────────────────────────────────────┘
步骤一:启动 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
步骤二:安装 Java 17
yum install -y java-17-openjdk-headless
java -version
步骤三:运行 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,需要这个参数
步骤四:配置 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;
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;
}
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。
效果
# 启动耗时
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
前端文件传输的曲折
SCP 被断连
本地 flutter build web --release 构建完后用 SCP 上传,结果:
Connection closed by 118.x.x.x port 22
反复尝试都被断。原因是之前多次 SSH 密码交互触发了服务器的入侵防护策略(fail2ban 或类似机制)。
解决方案:临时 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 项目,这些参数值得关注:
JVM 参数
-Xms256m -Xmx512m
-Xms128m -Xmx256m
-XX:+UseG1GC
MySQL 内存调优
如果 MySQL 也要省内存,可以在启动时追加参数:
docker run ... mysql:8.0 \
--innodb-buffer-pool-size=128M \
--max-connections=50 \
--table-open-cache=200
Swap 兜底
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
有 Swap 兜底,偶发的内存峰值不会直接触发 OOM killer。
待优化
- systemd 服务化:目前后端用
nohup 启动,服务器重启后不会自动恢复。应该写一个 systemd service 文件。
- HTTPS:用 Let's Encrypt + certbot 配置免费 SSL 证书。
- 日志轮转:
app.log 会无限增长,需要配置 logrotate。
- 监控:没有任何监控,进程挂了无感知。至少应该加个健康检查脚本 + crontab。
总结
| 方案 | 优点 | 缺点 | 适用场景 |
|---|
| Docker Compose 全容器 | 环境隔离、一键部署 | 内存开销大 | 4G+ 内存服务器 |
| 混合部署 | 省内存、连接稳定 | 管理稍复杂 | 2G 内存服务器 |
| 全宿主机 | 最省资源 | 环境污染 | 1G 极端场景 |
核心结论:小规格服务器不要迷信全容器化,够用就行。 Docker Compose 在 4G 以上内存的机器上体验很好,但在 2G 机器上,混合方案是更务实的选择。
相关免费在线工具
- 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