Skip to content

分布式存储架构

提出问题

当数据量从 TB 级增长到 PB 级,单机硬盘的容量和吞吐都成了瓶颈——你需要一个分布式存储系统来承载。但分布式存储的选型链很宽:块存储(Ceph RBD)、对象存储(MinIO、S3)、文件存储(NFS、CephFS),它们底层的数据分布、容错机制、一致性模型各不相同。面试官问这道题,是想看你对存储系统三大形态的区分、对数据分布算法(比如 CRUSH、一致性哈希)的理解,以及副本 vs 纠删码的工程取舍。生产环境中,选错存储形态或冗余策略,代价可能是硬件成本翻倍或恢复时间超长。

分析问题

块存储 / 对象存储 / 文件存储的区别

三种存储形态面向不同的访问接口和场景:

维度块存储 (Ceph RBD / AWS EBS)文件存储 (NFS / CephFS)对象存储 (MinIO / S3 / Ceph RGW)
接口裸块设备,格式化为 ext4/xfs 后使用POSIX 文件系统(目录树、读写文件)HTTP API (PUT/GET/DELETE),扁平命名空间
典型场景数据库、虚拟机磁盘传统应用共享存储海量非结构化数据、备份、归档
延迟亚毫秒级(本地 NVMe)毫秒级(网络文件系统)数十毫秒级(HTTP + 对象封装)
数据上限受设备数限制受文件系统限制理论上无限(桶 + 对象)
一致性强一致(RBD 一写多读)最终一致或强一致(取决于实现)最终一致(S3 标准)或强一致(S3 条件写)

选型口诀:数据库用块,共享用文件,海量非结构化用对象。

生产踩坑:曾经有个团队把 MySQL 日志(redo log)放到 NFS 上,结果 NFS 在并发写时出现文件元数据不一致,导致 MySQL crash 后恢复失败。数据库的 WAL 日志必须放在块存储上,这是底线。

Ceph 架构与 CRUSH 算法

Ceph 是当下最流行的统一分布式存储系统,通过 RADOS(Reliable Autonomic Distributed Object Store)统一底层。其核心设计哲学是去中心化元数据——数据该写在哪,不依赖中心化路由表,而是由 CRUSH 算法就地计算得出。

           ┌─────────────────────────────────────┐
           │         Application Layer            │
           └──────┬──────────┬──────────┬─────────┘
                  │          │          │
          ┌───────▼──┐ ┌────▼───┐ ┌───▼────────┐
          │  RBD    │ │ CephFS │ │  RGW (S3)  │  ← 三种接入层
          │ (块存储) │ │ (文件) │ │ (对象存储)  │
          └───┬─────┘ └───┬────┘ └───┬─────────┘
              │           │           │
              └───────────▼───────────┘
                  ┌──────────────┐
                  │  librados    │  ← 原生客户端库
                  └──────┬───────┘

          ┌──────────────┴──────────────────────┐
          │           RADOS Cluster              │
          │  ┌─────────┐ ┌─────────┐            │
          │  │ OSD    │ │ OSD    │ ...  OSD_N  │  ← 数据存储
          │  ├─────────┤ ├─────────┤            │
          │  │ MON    │ │ MON    │ ...  MON_3  │  ← 集群监控(Paxos)
          │  ├─────────┤ ├─────────┤            │
          │  │ MGR    │ │ MGR    │              │  ← 管理/监控
          │  └─────────┘ └─────────┘            │
          └──────────────────────────────────────┘

CRUSH(Controlled Replication Under Scalable Hashing) 是一个伪随机哈希算法,输入 (object_id, cluster_map) 直接输出该对象数据应存放到哪些 OSD 上。相比于一致性哈希,CRUSH 的关键创新是引入了 PG(Placement Group)作为中间层

Object → hash(object_id) % PG_count → PG_ID → CRUSH(PG_ID, crushmap) → OSD list

这个 PG 中间层解决了两个问题:

  1. 数据粒度放大:每个 PG 包含成千上万个对象,OSD 增删时只需迁移 PG,而非逐个对象重新哈希
  2. 故障域控制:CRUSH 在 PG 映射时强制不同副本落到不同的故障域(机架/数据中心)
python
# CRUSH 算法核心示意(简化版)
def crush_placement(pg_id, num_replicas, crush_map):
    """
    pg_id: PG 编号
    num_replicas: 副本数(如 3)
    crush_map: 集群拓扑(root → rack → host → OSD)
    返回: [osd_id_1, osd_id_2, osd_id_3]
    """
    hash_val = hash(pg_id)
    selected = []
    for _ in range(num_replicas):
        # 从 root 桶开始,逐层选择(按权重概率)
        osd = select_bucket(hash_val, crush_map, root_bucket)
        # 故障域隔离——确保不选到同一机架
        while osd in selected or same_failure_domain(osd, selected, crush_map):
            hash_val = rehash(hash_val)
            osd = select_bucket(hash_val, crush_map, root_bucket)
        selected.append(osd)
    return selected

CRUSH 的故障域示例:如果集群有 3 个机架,每个机架 4 台 OSD 节点,crushmap 配置为 rack 级故障域,CRUSH 会保证 3 副本各自落在不同机架上。这意味着即使一个机架断电,数据仍然可用。

生产参数:PG 总数一般按 (OSD 数 × 100) / 副本数 估算。一个 100 OSD、3 副本的集群,PG 总量约 3333。PG 太少→单个 PG 过大(数据不均匀),PG 太多→内存占用高(每个 PG 占用约 100KB 元数据)。

副本 vs 纠删码

副本(Replication)和纠删码(Erasure Coding)是两种数据冗余策略,核心权衡点是存储成本 vs 恢复代价

维度三副本(3x Replication)纠删码(EC 4+2)
存储效率33%(3 倍空间)66%(1.5 倍空间)
容错能力任意 2 副本故障任意 2 块故障(n=6, k=4)
恢复代价直接复制完整副本,网络 IO 小需从 k 个分片重建,CPU + 网络 IO 大
写延迟3 副本并行写,延迟低计算编码后写入,额外 CPU 开销
适用场景热数据、高吞吐在线服务冷数据、归档、备份、大文件

EC 原理详解:原始数据分 k 块,编码后生成 n 块(n > k),任意 k 块可恢复原始数据。

原始数据: [D1] [D2] [D3] [D4]   ← k=4 个数据块
         ↓  EC 编码(Reed-Solomon 矩阵乘法)
编码后:   [D1] [D2] [D3] [D4] [P1] [P2]  ← n=6(2 个校验块)

故障 2 块后(假设 D2 和 P1 都丢了):
读取:     [D1] [X]  [D3] [D4] [X]  [P2]
         ↓  RS 解码(解线性方程组)
恢复:     [D1] [D2] [D3] [D4] [P1] [P2]  ← 全部恢复

EC 恢复的真实代价(基于 10TB 硬盘的实测数据):

  • 三副本恢复:从另一个副本全量复制,10TB 数据,万兆网络,约 3 小时完成,CPU 占用 < 10%
  • EC 4+2 恢复:从 4 个存活分片重建,10TB 数据,万兆网络,约 6-8 小时完成,CPU 飙到 80-90%
  • 如果恢复期间另一个硬盘故障,EC 的 MTTR 更长,风险窗口更大

生产建议:热数据用三副本,冷数据用 EC 4+2。Ceph 中可以为不同存储池配置不同策略——这就是分层存储。Ceph 的 EC 池写性能比三副本低 30-50%,不建议 EC 池承载高吞吐写负载。

一致性哈希在数据分布中的应用

一致性哈希(Consistent Hashing)是另一种常见的数据分布方案,被 Cassandra、Amazon Dynamo、MinIO 广泛采用。其核心思想是将哈希空间组织成环,节点和数据都映射到环上,顺时针查找最近的节点:

python
import hashlib

class ConsistentHashRing:
    def __init__(self, nodes=None, replicas=150):
        self.replicas = replicas  # 虚拟节点数
        self.ring = {}
        self.sorted_keys = []
        for node in nodes or []:
            self.add_node(node)

    def _hash(self, key):
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

    def add_node(self, node):
        for i in range(self.replicas):
            vnode_key = f"{node}:{i}"
            h = self._hash(vnode_key)
            self.ring[h] = node
            self.sorted_keys.append(h)
        self.sorted_keys.sort()

    def get_node(self, key):
        if not self.ring:
            return None
        h = self._hash(key)
        for kh in self.sorted_keys:
            if kh >= h:
                return self.ring[kh]
        return self.ring[self.sorted_keys[0]]  # 环回绕

# 使用:增删节点只影响 1/N 的数据迁移

优势:增删节点时仅需迁移部分数据(约 1/N),远小于普通哈希的洗牌式迁移。虚拟节点(replicas) 解决节点异构问题——性能好的节点分配更多虚拟节点,负载更均衡。

生产陷阱

  1. 虚拟节点数过多导致内存暴涨:150 个虚拟节点 × 100 个物理节点 = 15000 个虚拟节点,每个节点占用约 40 字节(hash + node 引用),约 600KB。如果虚拟节点数增加到 1000,内存占用 4MB,影响不大,但环查找效率下降(二分查找 O(log n))。
  2. 节点倾斜:即使使用虚拟节点,如果节点容量差异大(如 2TB 和 8TB 混部),需要按比例分配虚拟节点数,否则小节点被打满。
  3. CRUSH 不存在这个倾斜问题,因为 CRUSH 的权重是连续的,OSD 按 weight 比例参与概率选择。

CRUSH vs 一致性哈希:面试对比

维度CRUSH一致性哈希
元数据依赖无,客户端完全自主计算需要维护节点列表(环映射)
故障域控制原生支持(机架/数据中心)需手动实现(通过虚拟节点标签)
数据迁移量增删 OSD 只影响所属 PG(约 1/N)增删节点影响 1/N 数据
负载均衡按 weight 概率选择,自动均衡靠虚拟节点近似,仍有倾斜风险
主要使用方CephCassandra、DynamoDB、MinIO
实现复杂度较高(crushmap 配置复杂)较低(几百行代码可实现)

面试话术:"CRUSH 和一致性哈希的根本区别在于,CRUSH 通过 PG 中间层和分层选择实现了故障域感知的确定性计算,而一致性哈希靠环结构和虚拟节点解决数据分布问题。CRUSH 适合对故障域有严格要求的块/对象存储系统,一致性哈希更适合轻量级、需要快速上线的分布式缓存或 KV 存储。"

写入流程时序图(以 Ceph RBD 为例)

Client                    MON                   OSD1(primary)       OSD2(副本)        OSD3(副本)
  │                        │                       │                   │                │
  │  1. 认证 + 获取集群信息  │                       │                   │                │
  │───────────────────────►│                       │                   │                │
  │◄───────────────────────│                       │                   │                │
  │                        │                       │                   │                │
  │  2. CRUSH 计算 PG 主 OSD                      │                   │                │
  │     PG_ID = hash(obj) % PG_count              │                   │                │
  │     primary = CRUSH(PG_ID, crushmap)          │                   │                │
  │                        │                       │                   │                │
  │  3. 写请求直接发往主 OSD                       │                   │                │
  │──────────────────────────────────────────────►│                   │                │
  │                        │                       │  4. 并行写副本     │                │
  │                        │                    ──│─────────────────►│                │
  │                        │                    ──│────────────────────────────────────►│
  │                        │                       │  ◄───── ack ────│                │
  │                        │                       │  ◄───── ack ─────────────────────│
  │                        │                       │                   │                │
  │  5. 所有副本确认后回复客户端                    │                   │                │
  │◄──────────────────────────────────────────────│                   │                │
  │                        │                       │                   │                │

关键点:Ceph 的写操作是主副本同步写、其他副本并发写。主 OSD 收到所有副本的 ack 后才返回客户端成功。这个机制保证了强一致性,但写延迟受最慢副本影响。如果某个 OSD 因磁盘故障变慢(比如出现 pending sectors),会拖慢整个集群的写入。生产排查:用 ceph osd perf 查看各 OSD 的延迟,超过 100ms 的 OSD 需要介入。

总结

  • 存储形态选型:块存储→数据库虚拟机,对象存储→海量非结构化数据,文件存储→传统共享
  • 数据分布:Ceph CRUSH 算法去中心化,通过 PG 中间层和故障域隔离实现确定性分布;一致性哈希(Cassandra/MinIO)加虚拟节点平衡负载,但倾斜风险需关注
  • 冗余策略:热数据三副本(恢复快),冷数据 EC 4+2(存储效率翻倍但恢复慢 2-3 倍)
  • 生产避坑:EC 恢复时 CPU 飙高,建议预留 30% 资源并限制恢复速率;一致性哈希虚拟节点数 150-200 即可;CRUSH 调 crushmap 务必先模拟再下发(ceph osd crush rule --export 备份);Ceph 写延迟受最慢 OSD 影响,定期用 ceph osd perf 监控
  • 面试高频题:CRUSH vs 一致性哈希的区别(答出 PG 中间层和故障域隔离算加分项);三副本 vs EC 的恢复时间对比(答出具体数字算加分项);Ceph 写流程的时序(答出主副本同步写算及格)

参考

Ceph 官方文档:CRUSH Map 与 PG 分布 Amazon Dynamo 论文:一致性哈希 + 向量时钟 MinIO Erasure Coding 官方指南 《Designing Data-Intensive Applications》第 6 章:分区与复制 Ceph 社区博客:OSD 性能监控与故障诊断

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。