1. 项目概述与背景
最近在腾讯云2核2G的轻量应用服务器上部署了一个全栈博客项目,技术栈包含Spring Boot 3.2.3后端、MySQL 8.0数据库和Flutter Web前端。原本计划采用Docker Compose全容器化部署,但在实际执行过程中遇到了内存不足导致的各种问题。经过多次尝试和调整,最终采用混合部署方案成功上线。
这个项目特别适合预算有限但又需要部署Java Web应用的个人开发者或小团队参考。2G内存的云服务器价格亲民,但如何在有限资源下稳定运行Spring Boot+MySQL组合确实需要一些技巧。下面我会详细分享从失败到成功的完整过程,包括问题排查思路、解决方案选择依据和具体配置细节。
2. 初始方案:Docker Compose全容器化部署
2.1 架构设计思路
最初选择全容器化部署主要基于以下考虑:
- 环境隔离:每个服务运行在独立容器中,避免依赖冲突
- 一键部署:通过docker-compose.yml定义所有服务,简化部署流程
- 版本控制:可以精确控制每个服务的版本
- 数据持久化:使用Docker卷管理MySQL数据
docker-compose.yml配置了三个核心服务:
- MySQL 8.0容器:作为数据库服务
- Spring Boot应用容器:运行Java后端
- Nginx容器:处理前端静态文件和反向代理
2.2 具体问题现象
部署后,Spring Boot容器反复出现连接MySQL失败的错误:
code复制com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
Caused by: java.net.ConnectException: Connection refused
表面看是网络连接问题,但实际排查发现:
- MySQL容器状态正常(显示为healthy)
- 容器间DNS解析正常(能正确解析db主机名)
- MySQL用户权限配置正确
- 3306端口确实在监听状态
2.3 根本原因分析
通过free -h查看内存使用情况:
code复制 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自身有约100-200MB开销
- 系统基础服务需要300MB左右
这些加起来已经超过2G物理内存,导致:
- 容器间网络通信不稳定
- 内核OOM Killer可能随机终止进程
- TCP连接建立失败或超时
3. 成功方案:混合部署架构
3.1 架构调整思路
新架构的核心原则是在保证关键功能的前提下最大限度节省内存:
code复制┌─────────────────────────────────────┐
│ 腾讯云 2C2G 服务器 │
│ │
│ ┌───────────┐ ┌────────────────┐ │
│ │ Docker │ │ 宿主机 │ │
│ │ MySQL 8.0 │ │ Java 17 (jar) │ │
│ │ (容器) │ │ Nginx (systemd)│ │
│ └───────────┘ └────────────────┘ │
│ ↑ 3306 ↑ 8080 ↑ 80 │
│ └──── 127.0.0.1 ────────┘ │
└─────────────────────────────────────┘
选择将MySQL保留在容器中主要考虑:
- 数据目录隔离更安全
- 版本管理和升级更方便
- 备份恢复更简单
而Spring Boot和Nginx直接运行在宿主机:
- 省去容器运行时开销
- 减少一层网络转发
- 直接利用系统资源
3.2 详细实施步骤
3.2.1 MySQL容器部署
优化后的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 \
--innodb-buffer-pool-size=128M \
--max-connections=50
关键优化参数:
--innodb-buffer-pool-size=128M:将缓冲池从默认的128MB降低--max-connections=50:限制最大连接数(默认151)
3.2.2 Java环境准备
在TencentOS上安装OpenJDK 17:
bash复制yum install -y java-17-openjdk-headless
验证安装:
bash复制java -version
# openjdk version "17.0.18"
3.2.3 Spring Boot应用部署
优化后的启动脚本:
bash复制nohup java -Xms256m -Xmx512m \
-XX:+UseG1GC \
-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 &
关键JVM参数:
-Xms256m -Xmx512m:堆内存限制在256-512MB-XX:+UseG1GC:使用G1垃圾收集器(Java 17默认)
3.2.4 Nginx配置
基础配置要点:
nginx复制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;
}
# Flutter Web路由处理
location / {
try_files $uri $uri/ /index.html;
}
}
特别注意事项:
try_files $uri $uri/ /index.html:必须配置以支持Flutter Web的SPA路由client_max_body_size:根据实际需要调整文件上传大小限制
3.3 内存占用对比
部署后的内存使用情况:
code复制Mem: 1.9Gi total, ~400Mi free, ~1.1Gi used (含buff/cache)
- MySQL容器:约300MB
- JVM:约400MB
- Nginx:约20MB
- 系统:约300MB
4. 前端部署的特别技巧
4.1 文件传输问题
直接使用SCP上传前端构建产物时,由于多次认证失败触发服务器的fail2ban防护,导致连接被拒绝。临时解决方案是使用Python搭建HTTP文件接收服务:
服务端(在服务器上执行):
bash复制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/
安全提示:此方法没有认证机制,仅限临时使用,完成后应立即关闭端口
4.2 前端路由配置
Flutter Web作为单页应用(SPA),需要特殊的路由处理。Nginx配置中必须包含:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
否则页面刷新会导致404错误。
5. 内存优化进阶技巧
5.1 JVM内存调优
对于2G服务器,推荐配置:
bash复制-Xms256m -Xmx512m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:ParallelGCThreads=2 \
-XX:ConcGCThreads=1
极端情况下可进一步压缩:
bash复制-Xms128m -Xmx256m \
-XX:+UseSerialGC
5.2 MySQL内存优化
在my.cnf或启动参数中添加:
code复制innodb_buffer_pool_size=128M
key_buffer_size=16M
tmp_table_size=32M
max_heap_table_size=32M
table_open_cache=200
thread_cache_size=10
5.3 系统级优化
- 启用Swap:
bash复制fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
- 调整Swappiness:
bash复制echo 'vm.swappiness=10' >> /etc/sysctl.conf
sysctl -p
- 清理不必要的服务:
bash复制systemctl stop postfix
systemctl disable postfix
6. 生产环境完善建议
6.1 Systemd服务化
创建/etc/systemd/system/vonblog.service:
ini复制[Unit]
Description=Von Blog Backend
After=network.target
[Service]
User=root
WorkingDirectory=/opt/vonblog
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/vonblog/app.jar --spring.profiles.active=prod
SuccessExitStatus=143
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
启用服务:
bash复制systemctl daemon-reload
systemctl enable vonblog
systemctl start vonblog
6.2 日志管理
配置logrotate/etc/logrotate.d/vonblog:
code复制/opt/vonblog/app.log {
daily
rotate 7
missingok
notifempty
compress
delaycompress
copytruncate
}
6.3 监控与健康检查
简易监控脚本/usr/local/bin/check_vonblog.sh:
bash复制#!/bin/bash
if ! curl -sf http://localhost:8080/actuator/health; then
systemctl restart vonblog
echo "$(date) - Restarted vonblog" >> /var/log/vonblog_monitor.log
fi
添加到crontab:
bash复制*/5 * * * * /usr/local/bin/check_vonblog.sh
7. 方案对比与选择建议
| 方案类型 | 内存占用 | 管理复杂度 | 适用场景 | 推荐指数 |
|---|---|---|---|---|
| 全容器化 | 高(1.5G+) | 低 | 4G+内存服务器 | ★★☆☆☆ |
| 混合部署 | 中(1.2G左右) | 中 | 2G内存服务器 | ★★★★☆ |
| 全宿主机 | 低(1G以下) | 高 | 1G内存服务器 | ★★★☆☆ |
关键选择原则:
- 4G以上内存:推荐全容器化,享受容器化的所有优势
- 2G内存:混合部署是最佳平衡点
- 1G内存:考虑全宿主机部署,或升级服务器
8. 经验总结与避坑指南
- 内存是第一瓶颈:在2G服务器上,任何默认配置都可能超标
- 监控Swap使用:
free -h要看swap分区的使用情况 - 连接数限制:MySQL和Spring Boot都要限制最大连接数
- 渐进式优化:先确保能跑起来,再逐步调优
- 备选方案:当内存不足时,考虑以下方案:
- 使用MariaDB替代MySQL(内存占用更小)
- 换用轻量级JVM如GraalVM Native Image
- 静态网站托管到对象存储(如COS)
在资源受限的环境部署Java应用确实充满挑战,但通过合理的架构设计和参数调优,2G内存的服务器完全可以稳定运行Spring Boot+MySQL的中小型应用。关键是要理解每个组件的资源需求,并做好平衡取舍。