导航菜单

存储方案选型

磁盘满了那天

上线第三周的周一早上,我收到一条告警:

Disk usage alert: /var/www/photos is 94% full
  Total: 100 GB
  Used:  94 GB
  Available: 6 GB

两周,94 GB。我查了一下增长速度:

# 磁盘增长分析
daily_uploads = 50            # 每天 50 张
avg_original_size_mb = 5.2    # 平均原图大小
thumbnail_multiplier = 1.3    # 缩略图额外占 30%

daily_growth_gb = (daily_uploads * avg_original_size_mb * (1 + thumbnail_multiplier)) / 1024
# = (50 × 5.2 × 1.3) / 1024 ≈ 0.33 GB/天

monthly_growth_gb = daily_growth_gb * 30
# ≈ 9.9 GB/月

# 问题:按这个速度,100GB 磁盘只能撑 10 个月
# 如果用户增长到 500 人/天,只能撑 1 个月

这还不是最糟的。真正的风险是:本地磁盘没有冗余。一旦磁盘坏了,所有图片全丢。

我调研了三种存储方案

方案一:NAS / 自建存储集群

# 自建 MinIO 集群(S3 兼容的对象存储)
# 最低配置:4 节点 × 2TB = 8TB 可用(纠删码模式)

HARDWARE_COST = {
    'servers': 4,
    'per_server': 5000,     # 每台 5000 元(含 2×2TB 硬盘)
    'total': 20000,
    'bandwidth': 2000,       # 2000 元/月(100Mbps 专线)
    'electricity': 500,      # 电费 500 元/月
    'maintenance': '你自己', # 运维人力
}

# 第一年成本:20,000 + 2,000×12 + 500×12 = 50,000 元

优点:完全自主可控。缺点:初始投入大,需要运维。

方案二:云服务商对象存储

# 阿里云 OSS
OSS_COST = {
    'storage_per_gb_month': 0.12,    # 标准存储
    'cdn_traffic_per_gb': 0.24,      # CDN 回源流量
    'request_per_10k': 0.01,         # API 请求
    'no_upfront': True,              # 无初始投入
    'pay_as_you_go': True,           # 按量付费
}

# 第一年成本估算(逐步增长到 500 张/天)
monthly_storage_gb = 50 * 5.2 * 30 / 1024 * 1.3 * 6  # 半年平均 ≈ 60 GB
annual_storage_cost = 60 * 0.12 * 12  # ≈ 86 元

# OSS 第一年的存储成本不到 100 元

优点:零初始投入,无限扩展,高可用(99.995%)。缺点:数据在别人的机房。

方案三:混合方案(热数据 OSS + 冷数据自建)

把最近 30 天的热数据放 OSS,30 天以上的冷数据定期归档到自建 NAS。

复杂度高,现阶段不需要。

我的决定:阿里云 OSS

算完账就很清楚了:

对比项自建 MinIO阿里云 OSS
初始投入20,000 元0 元
第一年成本~50,000 元~500 元
可用性取决于你的运维能力99.995%(11 个 9 数据可靠性)
可扩展性受硬件限制无限
运维负担重(硬件、网络、备份)
数据安全需要自己做备份和冗余自动多副本 + 纠删码

对于一个刚起步的社区,OSS 是唯一合理的选择

迁移代码很简单:

import oss2

# 初始化 OSS 客户端
auth = oss2.Auth('your_key', 'your_secret')
bucket = oss2.Bucket(auth, 'oss-cn-beijing.aliyuncs.com', 'guangying-images')

def save_to_oss(local_path, object_key):
    """上传文件到 OSS"""
    result = bucket.put_object_from_file(object_key, local_path)
    if result.status == 200:
        return f'https://guangying-images.oss-cn-beijing.aliyuncs.com/{object_key}'
    else:
        raise Exception(f'上传失败: {result.status}')

def download_from_oss(object_key, local_path):
    """从 OSS 下载文件"""
    bucket.get_object_to_file(object_key, local_path)

def delete_from_oss(object_key):
    """从 OSS 删除文件"""
    bucket.delete_object(object_key)

# 批量迁移已有图片到 OSS
def migrate_local_to_oss():
    """把本地磁盘的图片迁移到 OSS"""
    for root, dirs, files in os.walk(UPLOAD_FOLDER):
        for filename in files:
            local_path = os.path.join(root, filename)
            object_key = f'migrated/{filename}'
            
            # 上传到 OSS
            url = save_to_oss(local_path, object_key)
            
            # 更新数据库中的 URL
            db.execute(
                "UPDATE photos SET url = %s WHERE filename = %s",
                (url, filename)
            )
            
            # 验证上传成功后删除本地文件
            if verify_oss_object(object_key):
                os.remove(local_path)
                print(f"迁移完成: {filename}")

迁移完成后,我的服务器磁盘使用率从 94% 降到了 12%。

本节小结

我学到了什么

  • 本地磁盘存储不可靠、不可扩展、不可冗余
  • 对象存储(OSS/S3)是图片存储的业界标准
  • 按量付费模式对小团队非常友好,起步成本几乎为零

🎯 下一步:文件存在 OSS 上了,用什么目录结构和命名规则来组织?

我的思考

思考 1

如果有一天阿里云 OSS 挂了(虽然概率极低),你的图片服务会怎样?如何降低这种风险?

参考答案

OSS 的 SLA 是 99.995%,意味着一年大约有 26 分钟的不可用时间。但万一整 Region 故障(如 2015 年 AWS S3 us-east-1 宕机 4 小时),影响很大。

多活策略

方案 A:多 Region 复制(跨区域复制)
  主 Region:oss-cn-beijing
  备 Region:oss-cn-shanghai
  异步复制延迟:秒级
  成本:双倍存储 + 跨区域流量费

方案 B:CDN 兜底
  OSS 挂了 → CDN 缓存中还有最近访问的图片
  缓存命中率 95% 时,只有 5% 的请求受影响
  但新上传的图片无法访问

方案 C:DNS 切换
  监控检测到 OSS 故障 → 自动将图片域名 CNAME 到备用存储
  需要提前做好数据同步

对于”光影”的规模,CDN 兜底是最实用的方案。OSS 故障时,CDN 缓存中的热图仍然可以访问,只有新上传和冷门图片受影响。

搜索