存储方案选型
磁盘满了那天
上线第三周的周一早上,我收到一条告警:
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 缓存中的热图仍然可以访问,只有新上传和冷门图片受影响。
