系统设计:分布式文件存储

从零设计一个类似 S3 与 HDFS 的分布式文件存储系统,对比对象存储与分布式文件系统的取舍,详解元数据与数据分离、分块与副本及纠删码、一致性模型、断点续传与多段上传、冷热分层,包含容量估算、架构图、数据模型与面试追问。

系统设计:分布式文件存储

分布式文件存储是大数据与云原生的地基:HDFS 撑起离线计算,S3 撑起图片、视频、备份与数据湖。两者都面对同一个根本矛盾——海量小文件的元数据压力与超大文件的高吞吐需求如何在一套系统里共存。

1. 需求分析

功能性需求

  • 上传:支持单文件上传与多段(Multipart)上传,支持断点续传
  • 下载:支持整文件下载与范围(Range)读取
  • 命名空间:对象存储用扁平 Key,分布式文件系统用目录树
  • 生命周期:到期转冷、到期删除、版本控制与回收站
  • 权限:Bucket 级 ACL、对象级策略、预签名 URL

非功能性需求

  • 持久性:11 个 9(年数据损坏率约十亿分之一)
  • 可用性:99.99%,跨 3 个可用区部署
  • 吞吐:单集群聚合带宽百 GB/s 级
  • 延迟:小文件 P99 低于 100ms,大文件首字节低于 200ms
  • 扩展性:容量从 PB 级平滑扩到 EB 级

关键取舍

对象存储放弃 POSIX 语义(无原地随机写、无原子改名)换来近乎无限的扩展能力;分布式文件系统保留目录树与追加写,但元数据节点是天然瓶颈。面试时先说清楚选型,再展开设计。

2. 容量估算

假设一个中型云盘业务:

  • 日上传文件数:1 亿个,平均大小 2 MB(大量缩略图与小文档)
  • 日新增数据量:1 亿 × 2 MB = 200 TB/天
  • 年新增:200 TB × 365 ≈ 73 PB/年
  • 三副本冗余:73 PB × 3 = 219 PB 物理容量
  • 元数据:每对象约 200 字节(Key、大小、ETag、副本位置),1 亿 × 200B = 20 GB/天,年 7.3 TB

结论:数据容量可以堆磁盘,元数据必须分片。200 亿个对象的元数据若全放单机内存,即使每个只占 500 字节也要 10 TB 内存,单点方案不可行。

3. 整体架构

客户端(SDK / S3 API / HDFS Client)
        │
        ▼
  接入层(网关集群:鉴权、限流、签名校验)
        │
   ┌────┴─────────────────┐
   ▼                      ▼
元数据服务集群          数据节点集群
(Key→对象定位)         (Chunk Server / OSD)
   │                      │
   ▼                      ▼
元数据库(分片+副本)    磁盘池(HDD/SSD 分层)
   │
   └── 生命周期 / 索引 / 版本

写入路径

  1. 客户端向接入层申请写入,网关做鉴权与配额检查
  2. 元数据服务分配对象 ID 与数据分片位置(Placement)
  3. 客户端直接与数据节点建立连接,分块上传
  4. 数据节点完成副本复制或纠删码编码后回执
  5. 元数据服务提交对象元数据(此时对象才对读可见)

读取路径

  1. 客户端携带 Key 查询元数据服务,拿到分片位置列表
  2. 优先选择网络最近的副本,直接读取数据节点
  3. 大文件按 Range 分段并发拉取,客户端拼接

4. 数据模型

对象元数据表

objects (
    bucket_id     BIGINT,
    object_key    VARCHAR(1024),   -- 扁平 Key
    version_id    BIGINT,          -- 版本控制
    size          BIGINT,
    etag          VARCHAR(64),     -- 内容 MD5 或分段摘要
    content_type  VARCHAR(128),
    storage_class TINYINT,         -- 标准/低频/归档
    chunk_refs    JSON,            -- 分片位置与校验和
    created_at    TIMESTAMP,
    PRIMARY KEY (bucket_id, object_key, version_id)
)

分片表

chunks (
    chunk_id     BIGINT PRIMARY KEY,
    size         INT,
    checksum     VARCHAR(64),
    replicas     JSON,             -- [节点A, 节点B, 节点C]
    ec_scheme    VARCHAR(16),      -- 如 6+3,副本方案则为 NULL
    state        TINYINT           -- 正常/迁移中/待修复
)

表设计要点

  • 元数据按 bucket_id 哈希分片,避免单 Key 热点
  • object_key 用前缀索引支持「按目录列举」,但列举是最终一致扫描
  • 版本控制通过 version_id 递增,删除即插入墓碑记录

5. 元数据与数据分离

这是本系统的第一性原理:元数据小、访问频繁、要求强一致;数据大、访问稀疏、可最终一致。两者必须分开存储与扩展。

维度元数据层数据层
单条大小百字节级MB 到 GB 级
访问模式随机小读写顺序大读写
一致性要求强一致可最终一致
扩展方式分片 + 副本加盘 + 加节点
典型介质SSD + 内存HDD / 对象盘

元数据分片的三种做法

  1. 哈希分片:hash(bucket, key) 决定分片,均匀但范围扫描要查所有分片
  2. 目录子树分片:按目录挂载到不同元数据节点(HDFS Federation),支持局部性但易倾斜
  3. 动态分区:按访问热度自动分裂热点分区(类似 HBase Region 分裂)

元数据高可用

  • 采用 Raft/Paxos 复制日志,写多数派才提交
  • 主节点故障时秒级选主,客户端自动重试
  • 定期做元数据快照(Snapshot)与增量日志归档,加速恢复

元数据缓存的通用套路可参考 分布式缓存设计;而副本重建、异步修复这类削峰任务,与 消息队列设计 的思路一致。

6. 分块、副本与纠删码

为什么分块

单个大文件(如 10 GB 视频)必须切成分片(默认 4 MB 到 128 MB),原因有三:便于并行传输、便于按块校验与修复、避免单节点承载整文件。

副本 vs 纠删码

方案冗余度存储开销恢复代价适用场景
三副本3x200%低(直接复制)热数据、小文件
纠删码 6+31.5x50%高(需跨节点重建)冷数据、大文件
纠删码 10+41.4x40%更高归档数据

纠删码原理简述

把数据切成 k 个数据块,通过 Reed-Solomon 编码生成 m 个校验块。任意丢失不超过 m 块都能恢复。代价是读取一个块需同时拉取多个数据块做解码,延迟与 CPU 开销明显上升,因此只对冷数据启用。

修复与再平衡

  • 节点心跳超时判定失效,触发副本重建
  • 后台限速重建,避免修复流量挤占业务带宽
  • 数据倾斜时主动迁移分片,保证磁盘利用率均衡

7. 一致性模型与读写路径

强一致 vs 最终一致

  • 元数据操作(创建、删除、改名)走强一致,保证列表与读结果不矛盾
  • 数据副本复制可异步,读时若发现副本滞后则回源主副本
  • S3 早期是「写后读一致、列表最终一致」,新版已实现强一致,但代价是写路径更长

读写一致性技巧

  • 写对象先写数据、后提交元数据,避免出现「元数据可见但数据未落盘」的悬空对象
  • 读取时校验 ETag 与 checksum,损坏则切换其他副本并触发后台修复
  • 并发写同一 Key 时用版本号做乐观锁,后写覆盖需显式携带前置版本

分段上传的一致性

多段上传中,每个分段独立上传并返回 ETag;只有调用 Complete 时才组装成完整对象。中途失败可查询已上传分段并从断点续传,未完成的分段由生命周期规则定期清理。

8. 大文件上传与冷热分层

多段上传与断点续传

# 伪代码:分片上传 + 断点续传
def upload_large_file(path, key, chunk_size=8 * 1024 * 1024):
    upload_id = s3.create_multipart_upload(key)
    uploaded = set(s3.list_parts(upload_id))       # 查询已完成分片
    for idx, chunk in enumerate(read_chunks(path, chunk_size)):
        if idx in uploaded:                        # 断点续传:跳过
            continue
        etag = s3.upload_part(upload_id, idx, chunk)
        record_part(upload_id, idx, etag)          # 本地记录进度
    s3.complete_multipart_upload(upload_id)        # 全部完成后组装

要点:分片大小要足够大以减少请求数,又要足够小以支持并行与重传;每片独立校验,失败只重传该片。

冷热分层

  • 热层:SSD,承载频繁访问的缩略图、热门视频
  • 温层:HDD,常规数据,访问延迟可接受
  • 冷层:高密度归档盘或磁带,仅支持低频读取
  • 生命周期规则按「最后访问时间 + 对象年龄」自动降层,降层时用纠删码重编码以省空间

成本对比

存储层单 GB 月成本首字节延迟适用数据
标准 SSD高毫秒级热点数据
标准 HDD中十毫秒级常规数据
低频低百毫秒级月访问数次
归档极低秒到分钟级合规留存

9. 面试常见问题

Q: 对象存储为什么不能做原地随机写?
对象存储以「不可变对象」为基本单位,覆盖写会生成新版本,无法像本地文件那样在偏移处修改字节。这样做是为了简化副本一致性与缓存,代价是数据库类场景必须改用块存储。

Q: 元数据服务如何避免成为瓶颈?
水平分片(按 bucket 或 Key 哈希)+ 热点分区动态分裂 + 元数据缓存。HDFS 的 NameNode 单点正是被 S3 的分布式元数据服务超越的关键。

Q: 11 个 9 的持久性怎么算出来?
按磁盘年故障率、纠删码冗余度与修复速度综合估算。核心是让「同时失效的块数」小于校验块数,且修复速度快于下一块故障到来的速度。

Q: 小文件问题怎么解决?
小文件让元数据占比过高。常见手段:合并成大文件再按偏移索引、元数据独立存储与缓存、对小文件直接存元数据不落数据盘。

Q: 副本和纠删码怎么选?
热数据用副本换低延迟,冷数据用纠删码换低成本。混合方案是主流:近期写入用副本,降冷时转纠删码。

Q: 跨区域复制怎么做?
异步复制为主(RTC 有上限),按对象变更日志驱动;跨区带宽昂贵,需按业务重要性分级复制。强一致跨区方案延迟高,一般只用于元数据。

总结

分布式文件存储的答题主线只有三条:元数据与数据分离(决定扩展上限)、冗余策略(副本换延迟、纠删码换成本)、一致性与上传协议(多段上传 + 断点续传 + 版本控制)。先画架构图讲清写入路径与读取路径,再用容量数字证明分片与分层的必要性,最后针对小文件、跨区复制、成本三方面准备兜底方案,基本能覆盖面试官的全部追问。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页