中小项目高可用,真的需要K8s吗?从单机备份到企业级架构的完整思考

49 阅读5分钟

作为一名个人开发者,我只有一台云服务器,预算有限。当我搜索"高可用"时,满屏都是K8s、Service Mesh、混沌工程……这些方案看起来很棒,但对我这个体量的项目,是不是杀鸡用牛刀?

一、引子:一个困惑引发的思考

先说一下我的项目背景:

  • 在线简历:一个静态展示页面
  • 在线音乐/视频:几百首歌、几十个视频,Nginx直接 serve
  • 后台管理系统:用 Gin 写的管理端
  • 数据存储:mysql + 本地磁盘文件

说白了,就是一个小型个人项目。

那天我在思考一个问题:如果我的服务器宕机了怎么办?如果进程崩溃了怎么办?如果数据库丢了怎么办?

于是我打开了技术社区,搜索"高可用"。

搜索结果让我陷入沉思:K8s、Docker Swarm、Consul、etcd、服务网格、混沌工程……每一个方案都看起来"很专业",但每一个方案的复杂度都让我望而却步。

我只是想解决几个简单问题:

  • 进程崩了能自动重启
  • 服务器重启后服务能自己起来
  • 数据库能自动备份

真的需要把K8s那一整套搬过来吗? 而且我安装过Prometheus + Grafana + Alertmanager‌ 监控方案,根本就跑不起来,服务器配置太低,一运行起来服务器就卡死了,

这篇文章,就是我折腾了一圈后,给出的答案。


二、重新定义:什么是对中小项目真正有用的"高可用"

在开始之前,我们先纠正一个认知误区。

高可用 ≠ 99.999%的可用性。

99.999%意味着一年宕机时间不超过5分钟,那是阿里云、AWS这种体量需要考虑的事情。对于中小项目,99.9%就够了——一年宕机8.5小时,完全可以接受。

那我们真正需要解决的是什么问题?

故障场景发生的可能性影响解决方案投入成本
进程崩溃中(代码bug、OOM)服务不可用,需要手动重启进程守护,自动重启几乎为0
服务器重启低(系统更新、意外重启)服务全挂,需要手动启动开机自启配置几乎为0
数据库损坏低(硬盘故障、误操作)数据丢失,业务受损定期自动备份5分钟配置
流量暴涨极低(个人项目)响应慢,但影响有限单机够用,先不管0
机器永久损坏极低(云服务器有冗余)服务全挂云厂商保证0

看到没?用最简单的手段,就能解决80%以上的故障场景。

这就是中小项目的务实高可用方案。


三、方案一:单机部署的核心配置(我现在用的方案)

我的服务器配置很简单:一台云服务器(2核2G),安装 Nginx、Gin后端、mysql,所有文件存本地磁盘。

架构图长这样:

text

                     ┌─────────────────────────┐
                     │        用户             │
                     └───────────┬─────────────┘
                                 │
                                 ▼
                     ┌─────────────────────────┐
                     │        Nginx            │
                     │  (静态文件 + 反向代理)    │
                     └───────────┬─────────────┘
                                 │
              ┌──────────────────┼──────────────────┐
              │                  │                  │
              ▼                  ▼                  ▼
    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
    │  静态简历    │    │ 音乐/视频   │    │  Gin后端   │
    │  (HTML/CSS) │    │  (本地文件) │    │  (API服务)  │
    └─────────────┘    └─────────────┘    └──────┬──────┘
                                                  │
                                                  ▼
                                        ┌─────────────┐
                                        │     mysql   │
                                        │  (数据存储)  │
                                        └─────────────┘

3.1 用 Systemd 管理服务,实现开机自启和崩溃重启

为什么不直接用 Docker?

因为我这个项目足够简单:一个编译好的 Gin 二进制文件,依赖关系清晰。用 Systemd 直接管理进程,比再套一层 Docker 更轻量、更直接。

步骤1:创建 service 文件

bash

sudo vim /etc/systemd/system/gin.service

写入以下内容:

ini

[Unit]
Description=Gin Data Management Server
After=network.target postgresql.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/gin-server
Restart=always          # 挂了自动重启
RestartSec=5            # 5秒后重试
Environment="DB_HOST=localhost"
Environment="DB_PORT=5432"

[Install]
WantedBy=multi-user.target

步骤2:启用并启动服务

bash

sudo systemctl daemon-reload
sudo systemctl enable gin.service   # 开机自启
sudo systemctl start gin.service

步骤3:验证

bash

# 查看服务状态
sudo systemctl status gin.service

# 查看日志
sudo journalctl -u gin.service -f

现在,我的 Gin 服务具备了:

  • ✅ 服务器重启后自动启动
  • ✅ 进程崩溃后自动重启
  • ✅ 统一的日志管理

3.2 Nginx:系统包安装,自带 Systemd 管理

Nginx 就更简单了,直接用系统包管理安装,它自带了 systemd 服务。

bash

# Ubuntu/Debian
sudo apt install -y nginx

# 查看Nginx的systemd配置,它默认就有 Restart=on-failure
cat /lib/systemd/system/nginx.service | grep Restart
# 输出:Restart=on-failure

sudo systemctl enable nginx   # 开机自启
sudo systemctl start nginx

Nginx 配置也很简单:

nginx

// 只做示例,并不是我的真实配置
server {
    listen 80;
    server_name your-domain.com;
    
    # 简历静态页
    root /var/www/resume;
    
    
    # Gin API 反向代理
    location /api/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

3.3 PostgreSQL:同样用 Systemd 管理

bash

sudo apt install -y mysql-server
sudo systemctl enable mysql-server
sudo systemctl start mysql-server

到此为止,我的三个核心组件都有了进程守护和开机自启。

但是,还有一个最重要的问题没解决:数据备份

3.4 数据库自动备份:最重要的防线

说实话,进程崩了可以重启,服务器挂了可以重买,但数据丢了就真的没了

所以备份比高可用更重要。

步骤1:创建备份脚本

bash

sudo mkdir -p /data/backup/mysql /data/backup/scripts
cd /data/backup/scripts
sudo vim mysql_backup.sh

脚本内容:

bash

#!/bin/bash

# ========== 配置区域 ==========
BACKUP_DIR="/data/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

DB_USER="root"
DB_PASS="你的数据库密码"
DB_NAME="shangcheng"        # 你的数据库名
# =============================

mkdir -p ${BACKUP_DIR}

echo "开始备份数据库: ${DB_NAME} 时间: $(date '+%Y-%m-%d %H:%M:%S')"
mysqldump -u${DB_USER} -p${DB_PASS} ${DB_NAME} | gzip > ${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz

if [ $? -eq 0 ]; then
    echo "✅ 备份成功: ${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz"
    find ${BACKUP_DIR} -name "*.sql.gz" -mtime +${RETENTION_DAYS} -delete
    echo "已清理 ${RETENTION_DAYS} 天前的旧备份"
else
    echo "❌ 备份失败"
    exit 1
fi

步骤2:添加执行权限并测试

bash

sudo chmod +x mysql_backup.sh
sudo ./mysql_backup.sh

# 看到输出:
# 开始备份数据库: shangcheng 时间: 2026-06-08 08:08:31
# ✅ 备份成功: /data/backup/mysql/shangcheng_20260608_080831.sql.gz
# 已清理 30 天前的旧备份

步骤3:配置定时任务

bash

crontab -e

添加一行(每天凌晨2:30执行):

cron

30 2 * * * /data/backup/scripts/mysql_backup.sh >> /var/log/mysql_backup.log 2>&1

步骤4:验证备份文件可用

bash

# 查看备份文件
ls -lh /data/backup/mysql/
# -rw-r--r-- 1 root root 156K 6月 8 08:08 shangcheng_20260608_080831.sql.gz

# 验证内容
gunzip -c /data/backup/mysql/shangcheng_20260608_080831.sql.gz | head -20
# 应该能看到 CREATE TABLE、INSERT INTO 等 SQL 语句

看到备份文件生成的那一刻,我悬着的心终于放下了。


四、方案二:进阶高可用(当你有了2-3台服务器)

如果你的项目长大了,日活上了几千,或者业务不能接受停机,那可以考虑升级方案。

这时候,云服务是你的朋友,不是敌人

4.1 放弃自建 Nginx 主备,直接用云负载均衡器

先说个结论:不要自己搭 Keepalived + Nginx 主备

为什么?因为运维成本高于云服务费用。

看看对比:

维度自建 Nginx+Keepalived云负载均衡器(CLB/SLB)
自身高可用需要自己配置主备、VIP漂移云厂商保证99.95%+
故障切换3-10秒秒级自动
运维成本维护2台Nginx+配置控制台点几下
成本2台服务器+公网IP20-30元/月

云负载均衡器的使用超级简单(以腾讯云CLB为例):

  1. 购买一个负载均衡实例(会自动分配一个VIP)
  2. 配置监听器:HTTP协议,80/443端口
  3. 绑定后端云服务器:把2-3台CVM加进去
  4. 开启健康检查:自动摘除故障节点

就这么简单,不需要碰 Keepalived、VIP、心跳检测。

架构变成了这样:

text

                    ┌─────────────────────────┐
                    │          用户           │
                    └───────────┬─────────────┘
                                │
                                ▼
                    ┌─────────────────────────┐
                    │    云负载均衡器(CLB)     │
                    │    (云厂商托管,高可用)   │
                    └───────────┬─────────────┘
                                │
            ┌───────────────────┼───────────────────┐
            │                   │                   │
            ▼                   ▼                   ▼
    ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
    │    CVM1     │     │    CVM2     │     │    CVM3     │
    │   (应用)    │     │   (应用)    │     │   (应用)    │
    └─────────────┘     └─────────────┘     └─────────────┘
            │                   │                   │
            └───────────────────┼───────────────────┘
                                │
                                ▼
                    ┌─────────────────────────┐
                    │    云Redis + 云RDS      │
                    │    (托管式高可用)        │
                    └─────────────────────────┘

4.2 Session/JWT 状态共享

当你有多个应用实例时,会面临一个问题:用户登录状态怎么共享?

最简单的方案:JWT + Redis存状态

这个方案的好处是:

  • JWT负责身份认证(无状态、跨端友好)
  • Redis负责状态吊销(解决JWT"不能踢人下线"的痛点)

代码示例(Gin中间件):

go

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.JSON(401, gin.H{"error": "未提供token"})
            c.Abort()
            return
        }
        
        // 1. 验证JWT签名
        claims, err := ValidateJWT(token)
        if err != nil {
            c.JSON(401, gin.H{"error": "无效token"})
            c.Abort()
            return
        }
        
        // 2. 检查Redis中是否存在(实现踢人下线)
        exists := redisClient.Exists(c, "user:"+claims.UserID+":token").Val()
        if exists == 0 {
            c.JSON(401, gin.H{"error": "token已失效"})
            c.Abort()
            return
        }
        
        c.Set("userID", claims.UserID)
        c.Next()
    }
}

4.3 数据库高可用:直接上云数据库

自建数据库主从,需要配置复制、写切换脚本、监控……太麻烦了。

直接换成云数据库RDS,自带高可用版:

对比自建主从云RDS高可用版
主备切换手动或写脚本自动,秒级切换
数据备份自己写脚本自动备份,支持按时间点恢复
监控告警自己搭控制台直接看
成本2台服务器约200元/月

200元/月买一个不用操心的数据库,我觉得很值。

4.4 成本核算

升级到进阶方案后的月成本:

组件规格月成本
云负载均衡CLB标准型~30元
云服务器CVM2核4G × 3台~150元/台 ×3 = 450元
云Redis1GB主从版~80元
云RDS1核1G高可用版~200元
合计~760元/月

760元/月,换来99.9%的可用性,企业级的高可用架构。


五、横向对比:各个方案怎么选

我整理了一个完整的对比表格,方便你做选择:

维度单机+Systemd单机+Docker多机+云负载均衡K8s
月成本~150元~150元~600-800元1000+元
运维复杂度中低
学习成本
可用性~99%~99%99.9%99.99%
扩容速度手动换配置手动起容器自动弹性自动弹性
适用场景个人项目、内部系统标准化部署对外服务、日活几千大型微服务

我的建议:

  • 刚起步:选"单机+Systemd",性价比最高
  • 需要标准化:选"单机+Docker",环境一致性更好
  • 需要对外服务:选"多机+云负载均衡"
  • 团队够大、业务够复杂:再考虑K8s

六、JWT vs Session 的深度辨析

这个话题经常被讨论,我也踩过坑,这里说说我的理解。

6.1 常见误区

误区1:"JWT不能踢人下线"

这是指纯无状态的JWT。但如果我们用 JWT + Redis存状态,完全可以实现踢人下线。

误区2:"Session不能跨服务共享"

这完全是错的。Session完全可以存在Redis里实现共享,Spring Session、PHP的session_set_save_handler都是干这个的。

6.2 对比表格

维度Session+Redis纯JWTJWT+Redis(推荐)
踢人下线✅ 删Redis❌ 不行✅ 删Redis
跨服务共享✅ 共用Redis✅ 天生✅ 共用Redis
移动端支持⚠️ 依赖Cookie✅ 友好✅ 友好
服务端存储是(状态存Redis)
性能查一次Redis验签(CPU)验签+查Redis

6.3 结论

对于大多数业务系统,推荐用 JWT + Redis 存状态。

  • JWT负责身份认证(无状态、跨端友好、标准统一)
  • Redis负责状态吊销(踢人下线、权限实时生效)

这样既享受了JWT的便利,又解决了核心痛点。


七、踩坑记录和最佳实践

这些都是我实际踩过的坑,希望你不用再踩一遍。

坑1:以为备份配置好了,但从没验证过

现象:配置了crontab备份,但半年后恢复时发现备份文件是空的。

原因:mysqldump命令执行失败(密码错误、磁盘满了),但没有告警。

解决方案

bash

# 定期验证备份文件
gunzip -c /data/backup/mysql/*.sql.gz | head -20

# 配置告警(可以写个脚本检查备份文件大小)

坑2:Session存本地内存,用户频繁掉线

现象:用户刚登录,刷新一下就要重新登录。

原因:Nginx负载均衡把请求分到了不同的服务器,Session存在服务器A,请求到了服务器B。

解决方案:Session存Redis,或改用JWT+Redis。

坑3:Nginx日志打满磁盘

现象:服务器突然不可用,ssh都连不上。

原因:访问日志和错误日志没有轮转,把磁盘占满了。

解决方案:配置logrotate

bash

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 nginx adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

坑4:服务器重启后服务没起来

现象:重启服务器后,网站打不开。

原因:忘了配置 systemctl enable

解决方案

bash

# 检查是否已启用
systemctl is-enabled gin.service

# 如果显示disabled,启用它
systemctl enable gin.service

八、总结:高可用不是技术炫耀,而是风险控制

写这篇文章,我想传达几个观点:

1. 不要为了"高可用"而高可用

技术是手段,不是目的。用最简单的方案解决最核心的问题,比堆砌技术更有价值。

2. 从小处着手,按需升级

从单机+备份开始,等业务真的需要了,再考虑多机、容器化、K8s。

3. 云服务是中小项目的朋友

20-30元的负载均衡,200元的云数据库高可用版,买的是安心和节省的时间。

4. 最重要的高可用方案是备份+恢复演练

进程崩了可以重启,服务器挂了可以重买,但数据丢了就真的没了。

5. 定期验证你的备份

配置了备份不等于安全,验证了备份才算。


附:完整脚本和配置

1. Systemd service文件

ini

# /etc/systemd/system/gin.service
[Unit]
Description=Gin Data Management Server
After=network.target postgresql.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/gin-server
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

2. MySQL自动备份脚本

bash

#!/bin/bash
# /data/backup/scripts/mysql_backup.sh

BACKUP_DIR="/data/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

DB_USER="root"
DB_PASS="your_password"
DB_NAME="your_database"

mkdir -p ${BACKUP_DIR}
mysqldump -u${DB_USER} -p${DB_PASS} ${DB_NAME} | gzip > ${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz
find ${BACKUP_DIR} -name "*.sql.gz" -mtime +${RETENTION_DAYS} -delete

3. Crontab配置

cron

# 每天凌晨2:30备份数据库
30 2 * * * /data/backup/scripts/mysql_backup.sh >> /var/log/mysql_backup.log 2>&1

4. Nginx配置片段

nginx

server {
    listen 80;
    server_name your-domain.com;
    
    root /var/www/resume;
    
    location /music/ {
        alias /var/www/music/;
    }
    
    location /api/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
    }
}

5. Gin中间件:JWT+Redis验证

go

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        
        claims, err := ValidateJWT(token)
        if err != nil {
            c.JSON(401, gin.H{"error": "无效token"})
            c.Abort()
            return
        }
        
        exists := redisClient.Exists(c, "user:"+claims.UserID).Val()
        if exists == 0 {
            c.JSON(401, gin.H{"error": "token已失效"})
            c.Abort()
            return
        }
        
        c.Set("userID", claims.UserID)
        c.Next()
    }
}

最后

希望这篇文章能帮到和你一样,正在思考"中小项目怎么做高可用"的开发者。

记住:高可用不是目的,服务稳定、数据安全才是。

你的项目是怎么做高可用的?遇到过什么坑?欢迎在评论区交流讨论。


如果这篇文章对你有帮助,欢迎点赞、收藏、转发~