mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2517 字
7 分钟
分布式文件系统
2025-07-14

一个机房断电,里面所有节点同时宕机——如果数据只存在这个机房,业务直接停摆。RAID 能扛住单盘故障,但扛不住整机柜、整个机房的故障。跨节点的数据冗余和调度,是分布式文件系统要解决的核心问题。

Ceph 用 CRUSH 算法计算数据应该放在哪个节点,不需要查中心化元数据表;Gluster 用 DHT 哈希把文件映射到节点,无元数据服务器架构消除了单点。两者思路不同,但目标一致:跨节点组织数据,让存储容量和性能随节点数线性扩展。

一、分布式文件系统概述#

1.1 为什么需要分布式文件系统#

graph TB subgraph 单机存储["单机存储瓶颈"] SINGLE["单台服务器<br/>容量: 100TB<br/>IOPS: 10万<br/>带宽: 10Gbps"] end subgraph 分布式存储["分布式存储"] NODE1["节点 1<br/>100TB / 10万 IOPS"] NODE2["节点 2<br/>100TB / 10万 IOPS"] NODE3["节点 3<br/>100TB / 10万 IOPS"] NODE4["节点 N<br/>..."] TOTAL["总计: N×100TB<br/>N×10万 IOPS<br/>线性扩展"] end SINGLE --> TOTAL style SINGLE fill:#ffcdd2,stroke:#c62828 style TOTAL fill:#c8e6c9,stroke:#2e7d32
需求单机方案分布式方案
容量 > 100TB加磁盘加节点
IOPS > 10万加 SSD加节点
高可用RAID多副本/纠删码
弹性扩展换大机器加节点

1.2 分布式文件系统的核心挑战#

挑战说明解决方案
元数据管理文件→块的映射中心化 vs 去中心化
数据放置数据放在哪个节点一致性哈希 vs CRUSH
一致性多副本间数据一致Quorum + Paxos/Raft
故障恢复节点故障后数据恢复自动重平衡
扩展性加节点后数据均衡动态重分布

二、Ceph 架构#

2.1 Ceph 整体架构#

graph TB subgraph Ceph["Ceph 架构"] CLIENT["客户端"] --> RADOSGW["RADOS Gateway<br/>S3/Swift 接口"] CLIENT --> CEPHFS["CephFS<br/>POSIX 文件接口"] CLIENT --> RBD["RBD<br/>块设备接口"] RADOSGW --> RADOS["RADOS<br/>可靠自动分布式对象存储"] CEPHFS --> RADOS RBD --> RADOS RADOS --> MON["Monitor<br/>集群状态/映射表"] RADOS --> MDS["MDS<br/>元数据服务器<br/>(CephFS 专用)"] RADOS --> OSD["OSD<br/>对象存储守护进程<br/>每盘一个"] end style RADOS fill:#c8e6c9,stroke:#2e7d32 style MON fill:#e3f2fd,stroke:#1565c0 style OSD fill:#fff9c4,stroke:#f9a825

2.2 RADOS 架构#

RADOS(Reliable Autonomic Distributed Object Store)是 Ceph 的核心:

组件说明数量
Monitor (MON)维护集群映射表,提供一致性保证3 或 5 个(奇数)
OSD (Object Storage Daemon)存储数据,处理读写,做副本复制每块磁盘一个
MDS (Metadata Server)管理文件系统元数据(CephFS 专用)1+ 个

2.3 数据映射流程#

graph LR subgraph 数据映射["Ceph 数据映射流程"] OBJ["对象<br/>Object"] --> PG["归置组<br/>PG"] PG --> OSD_SET["OSD 集合<br/>[Primary, Replica1, Replica2]"] end OBJ -->|"hash(object_id) % pg_num"| PG PG -->|"CRUSH(pg_id, cluster_map)"| OSD_SET style OBJ fill:#e3f2fd,stroke:#1565c0 style PG fill:#c8e6c9,stroke:#2e7d32 style OSD_SET fill:#fff9c4,stroke:#f9a825
# Ceph 数据映射流程
def ceph_map_object(object_id, pool_id, cluster_map):
"""将对象映射到 OSD"""
# 1. 计算归置组(PG)
pg_id = hash(object_id) % pg_num
# 2. 使用 CRUSH 算法计算 OSD 集合
osd_set = crush_algorithm(pg_id, cluster_map, pool_rules)
# 3. Primary OSD 处理读写
primary_osd = osd_set[0]
return {
"object_id": object_id,
"pg_id": pg_id,
"osd_set": osd_set,
"primary": primary_osd,
}

三、CRUSH 算法#

3.1 CRUSH 的设计目标#

CRUSH(Controlled Replication Under Scalable Hashing)是 Ceph 的核心数据放置算法:

目标说明为什么
确定性相同输入总是得到相同输出客户端可独立计算数据位置
均衡性数据均匀分布在所有 OSD 上避免热点
故障域感知副本分布在不同故障域容忍故障域故障
最小迁移增减 OSD 时最小化数据迁移减少恢复开销

3.2 CRUSH 算法流程#

# CRUSH 算法简化实现
def crush_choose(pgid, cluster_map, rule, num_replicas):
"""CRUSH 选择 OSD"""
result = []
for replica in range(num_replicas):
# 1. 从故障域的根开始
current = cluster_map.root
# 2. 沿故障域层级向下选择
for failure_domain in rule.failure_domains:
# host → rack → row → room
children = get_children(current, failure_domain)
# 3. 使用 straw 桶算法选择子节点
selected = straw_bucket_select(
children, pgid, replica
)
current = selected
# 4. 检查是否已在结果中(避免同一 OSD)
if current.osd not in result:
result.append(current.osd)
return result
# Straw 桶算法
def straw_bucket_select(items, pgid, replica):
"""Straw 桶选择算法"""
# 为每个 item 计算一个 straw 长度
# 长度与 item 的权重成正比
max_straw = -1
selected = None
for item in items:
# hash(pgid, replica, item_id) → straw 长度
straw = hash(pgid, replica, item.id) & item.weight
if straw > max_straw:
max_straw = straw
selected = item
return selected

3.3 CRUSH vs 一致性哈希#

维度一致性哈希CRUSH
数据放置环形哈希层级化选择
故障域感知
权重支持虚节点原生支持
元数据依赖无(可计算)
迁移量最小较小
适用场景缓存/简单 KV分布式存储

四、Ceph 数据一致性#

4.1 副本写入流程#

sequenceDiagram participant Client participant Primary as Primary OSD participant Replica1 as Replica OSD 1 participant Replica2 as Replica OSD 2 Client->>Primary: 1. 写入请求 Primary->>Replica1: 2. 转发到 Replica 1 Primary->>Replica2: 3. 转发到 Replica 2 Replica1-->>Primary: 4. ACK Replica2-->>Primary: 5. ACK Primary-->>Client: 6. 写入完成(Quorum 达成)

4.2 Quorum 写入#

副本数写入 Quorum读 Quorum说明
221不推荐(无法处理脑裂)
321默认,容忍 1 节点故障
432容忍 1 节点故障
532容忍 2 节点故障

五、Gluster 架构#

5.1 Gluster 整体架构#

graph TB subgraph Gluster["Gluster 架构"] CLIENT_G["FUSE 客户端<br/>挂载卷"] SERVER1["Brick 1<br/>/data/brick1"] SERVER2["Brick 2<br/>/data/brick2"] SERVER3["Brick 3<br/>/data/brick3"] CLIENT_G --> DHT["DHT 哈希<br/>确定数据位置"] DHT --> SERVER1 DHT --> SERVER2 DHT --> SERVER3 end style CLIENT_G fill:#e3f2fd,stroke:#1565c0 style DHT fill:#fff9c4,stroke:#f9a825

5.2 Gluster 卷类型#

卷类型说明空间利用率容错适用场景
Distribute分布式(哈希分片)100%大容量
Replicate镜像(副本)50%高可用
Disperse纠删码(k)/(k+m)大容量+高可用
Distribute-Replicate分布式+镜像50%通用

5.3 Gluster 卷管理实战#

# 创建分布式副本卷(2 副本,跨 3 节点)
gluster volume create gv0 replica 2 \
node1:/data/brick1 node2:/data/brick1 \
node3:/data/brick1 node1:/data/brick2 \
node2:/data/brick2 node3:/data/brick2
# 启动卷
gluster volume start gv0
# 查看卷信息
gluster volume info gv0
# Volume Name: gv0
# Type: Distributed-Replicate
# Number of Bricks: 3 x 2 = 6
# Status: Started
# 在线扩容:添加 Brick
gluster volume add-brick gv0 node4:/data/brick1 node5:/data/brick1
# 触发重平衡
gluster volume rebalance gv0 start

5.4 Ceph vs Gluster 对比#

维度CephGluster
架构中心化元数据(MON)去中心化(DHT)
数据放置CRUSH 算法DHT 哈希
接口块/文件/对象文件/对象
元数据MDS(CephFS)DHT + 扩展属性
一致性强一致最终一致
小文件一般较好
大文件优秀优秀
运维复杂度
社区活跃度

六、数据均衡与恢复#

6.1 Ceph 数据重平衡#

# Ceph 集群状态
ceph status
# cluster:
# id: xxx
# health: HEALTH_OK
# services:
# mon: 3 daemons
# osd: 12 osds: 12 up, 12 in
# data:
# pools: 3 pools, 576 pgs
# objects: 1.2M objects, 450 GB
# usage: 12 TiB used, 108 TiB / 120 TiB avail
# pgs: 576 active+clean
# 添加新 OSD 后的自动重平衡
ceph osd tree
# ID CLASS WEIGHT TYPE NAME
# -1 0.52340 root default
# -3 0.17446 host node1
# 0 hdd 0.08723 osd.0 up 1.00000
# 1 hdd 0.08723 osd.1 up 1.00000
# -5 0.17446 host node2
# 2 hdd 0.08723 osd.2 up 1.00000
# 3 hdd 0.08723 osd.3 up 1.00000
# 重平衡进度
ceph -w
# pgmap v1234: 576 pgs: 576 active+clean; 450 GB data, 12 TiB used

6.2 重平衡策略#

参数默认值说明
osd_max_backfills1同时恢复的 PG 数量
osd_recovery_max_active3每个 OSD 同时活跃的恢复操作
osd_recovery_op_priority3恢复操作优先级(1–63)
osd_recovery_sleep0恢复操作间睡眠时间
Note

重平衡期间,恢复操作会占用大量 I/O 和网络带宽,可能影响前台读写性能。Ceph 通过限制恢复速率来减轻影响,但恢复时间会相应延长。

七、实战:Ceph 部署与操作#

7.1 Ceph 集群部署#

# 使用 cephadm 部署 Ceph
# 1. 安装 cephadm
curl --silent --remote-name \
https://download.ceph.com/rpm-18.2.0/el9/noarch/cephadm
chmod +x cephadm
# 2. 引导集群
./cephadm bootstrap --mon-ip 192.168.1.1
# 3. 添加节点
ceph orch host add node2 192.168.1.2
ceph orch host add node3 192.168.1.3
# 4. 添加 OSD
ceph orch daemon add osd node1:/dev/sdb
ceph orch daemon add osd node2:/dev/sdb
ceph orch daemon add osd node3:/dev/sdb
# 5. 创建池
ceph osd pool create mypool 128 128 replicated
ceph osd pool set mypool size 3
ceph osd pool set mypool min_size 2

7.2 Ceph 性能测试#

# 使用 rados bench 测试性能
# 写入测试
rados bench -p mypool 60 write --no-cleanup
# 读取测试
rados bench -p mypool 60 seq
# 随机读取测试
rados bench -p mypool 60 rand
# 使用 fio 测试 RBD 性能
fio --name=ceph-rbd --ioengine=rbd --pool=mypool \
--rbdname=test-volume --size=10G \
--rw=randwrite --bs=4k --iodepth=32 --numjobs=4

八、CephFS 元数据管理#

8.1 MDS 与元数据池#

CephFS 的元数据由 MDS(Metadata Server)管理,元数据和数据存储在不同的 RADOS 池中:

graph TB subgraph CephFS元数据["CephFS 元数据架构"] CLIENT_F["CephFS 客户端"] --> MDS1["MDS Active<br/>处理元数据请求"] CLIENT_F --> MDS2["MDS Standby<br/>热备"] MDS1 --> META_POOL["元数据池<br/>存储 inode/目录项"] MDS1 --> JOURNAL["MDS Journal<br/>元数据 WAL"] CLIENT_F --> DATA_POOL["数据池<br/>存储文件内容"] end style MDS1 fill:#c8e6c9,stroke:#2e7d32 style META_POOL fill:#fff9c4,stroke:#f9a825 style DATA_POOL fill:#e3f2fd,stroke:#1565c0
组件说明存储位置
MDS Journal元数据变更的 WAL,保证崩溃可恢复元数据池(独立对象)
Capabilities客户端缓存授权,控制缓存一致性MDS 内存 → 客户端内存
元数据池inode、目录项(dentry)、扩展属性RADOS 池(通常用副本池)
数据池文件内容,按对象条带化存储RADOS 池(可用副本池或 EC 池)

元数据池和数据池分离的设计,使得元数据可以放在高性能 SSD 副本池上,而数据放在大容量 HDD EC 池上——不同类型的数据获得最合适的存储介质和冗余策略。

8.2 Capabilities 机制#

Capabilities 是 MDS 颁发给客户端的缓存授权令牌。客户端持有 Cap 时,可以在本地缓存元数据和文件数据,减少与 MDS 的交互。当其他客户端需要访问同一文件时,MDS 会召回(revoke)前一个客户端的 Cap,确保缓存一致性。这种设计在单客户端大量顺序读的场景下效果最好——MDS 只需颁发一次 Cap,后续读取全部命中本地缓存。

九、Ceph 纠删码池#

9.1 EC 池配置#

Ceph 支持纠删码池,用更低的存储开销提供与副本池同等的容错能力:

# 创建纠删码 Profile
ceph osd erasure-code-profile set my-ec-profile \
k=6 m=3 \
crush-failure-domain=host \
plugin=jerasure \
technique=reed_sol_van
# 基于该 Profile 创建池
ceph osd pool create ec-pool 128 128 erasure my-ec-profile
# 查看池信息
ceph osd pool ls detail | grep ec-pool
# pool 4 'ec-pool' erasure profile my-ec-profile size 9 min_size 7
配置k (数据块)m (校验块)容忍故障数空间利用率最小写入 OSD
2+121167%3
4+242267%6
6+363367%9
8+484467%12
12+4124475%16

9.2 EC 池的覆盖写问题#

Warning

纠删码池原生不支持覆盖写(overwrite)。对已有对象的随机修改需要读取所有 k 个数据块、重新计算校验块、再写回——即”读-改-写”(read-modify-write),开销远大于副本池的原地更新。Ceph 从 Luminous 版本开始支持 EC 池的覆盖写(需开启 allow_ec_overwrites),但性能仍不如副本池。

部分写(partial write)的惩罚尤其严重:修改 4KB 数据可能需要读取 6×64KB=384KB 的数据块,计算校验后再写回 9 个 OSD。对于随机写入密集的工作负载,应使用副本池而非 EC 池。EC 池更适合对象存储、冷数据归档等追加写入场景。

十、PG 状态与恢复流程#

10.1 PG 状态机#

PG(Placement Group)是 Ceph 数据管理的基本单位。每个 PG 有自己的状态机:

stateDiagram-v2 [*] --> Creating: PG 创建 Creating --> Peering: OSD 就绪 Peering --> Active: Peering 完成 Peering --> Down: Primary OSD 不可达 Active --> Clean: 所有副本同步 Active --> Recovering: 副本不一致 Recovering --> Active: 恢复完成 Active --> Degraded: 部分 OSD 不可用 Degraded --> Recovering: OSD 重新上线 Active --> Backfilling: 新 OSD 加入 Backfilling --> Active: 回填完成 Down --> Peering: OSD 恢复 Active --> [*]: PG 删除
PG 状态含义客户端影响
CreatingPG 正在创建不可读写
PeeringOSD 间协商 PG 一致性不可读写
ActivePG 可提供服务可读写
Clean所有副本完全同步可读写
Degraded部分副本缺失可读写(降级)
Recovering正在恢复数据可读写(可能变慢)
Backfilling新 OSD 回填数据可读写(可能变慢)
DownPrimary OSD 不可达不可读写

10.2 Peering 流程#

Peering 是 PG 从不一致到一致的核心过程:Primary OSD 收集所有 Acting Set 中 OSD 的 PG 日志,通过比较日志确定权威日志(authority log),计算出缺失的对象列表,然后进入 Active 状态。Peering 期间 PG 不可读写,通常在秒级完成。

十一、Ceph RBD 详解#

11.1 RBD 条带化与分层#

RBD(RADOS Block Device)将块设备镜像条带化为对象存储在 RADOS 池中:

graph LR subgraph RBD["RBD 条带化"] IMG["RBD 镜像<br/>1TB 块设备"] --> S1["Stripe 1<br/>对象 0"] IMG --> S2["Stripe 2<br/>对象 1"] IMG --> S3["Stripe 3<br/>对象 2"] IMG --> SN["..."] end subgraph Layering["RBD 分层克隆"] PARENT["父镜像<br/>(只读)"] --> CHILD1["克隆镜像 1<br/>(CoW)"] PARENT --> CHILD2["克隆镜像 2<br/>(CoW)"] end style IMG fill:#e3f2fd,stroke:#1565c0 style PARENT fill:#c8e6c9,stroke:#2e7d32
# RBD 操作
# 创建镜像
rbd create mypool/myimage --size 1T --image-feature layering,exclusive-lock
# 创建快照
rbd snap create mypool/myimage@snap1
# 保护快照(克隆前必须保护)
rbd snap protect mypool/myimage@snap1
# 基于快照克隆
rbd clone mypool/myimage@snap1 mypool/clone1
# 查看克隆关系
rbd children mypool/myimage@snap1
# 合并克隆(flatten)
rbd flatten mypool/clone1

RBD 的分层克隆采用 CoW(Copy-on-Write)策略:克隆镜像初始不占空间,读取时直接读父镜像,写入时将修改的对象复制到克隆镜像。这种设计使得从同一个基础镜像创建数百个虚拟机只需极少的额外存储。

十二、故障恢复场景#

12.1 OSD 故障恢复流程#

当一个 OSD 故障时,Ceph 的恢复流程如下:

sequenceDiagram participant MON as Monitor participant OSD1 as Primary OSD participant OSD2 as Replica OSD participant OSD3 as 新 OSD(替代故障 OSD) Note over MON: OSD.3 心跳超时 MON->>MON: 标记 OSD.3 down MON->>MON: 更新 OSD Map MON->>OSD1: 推送新 OSD Map MON->>OSD2: 推送新 OSD Map MON->>OSD3: 通知新 OSD 加入 OSD1->>OSD1: PG Peering<br/>计算缺失对象 OSD1->>OSD3: Backfill<br/>传输缺失对象 OSD3-->>OSD1: Backfill 完成 OSD1->>MON: PG 状态 Active+Clean
Note

OSD 故障后的恢复时间取决于数据量和恢复速率配置。默认 osd_max_backfills=1 限制每个 OSD 同时只做 1 个 PG 的回填,这是为了保护前台 I/O 性能。如果需要加速恢复,可以临时调大该值,但会显著影响客户端读写延迟。在Ch9 WAL 与崩溃恢复中讨论了单机崩溃恢复,这里的 PG Peering + Backfill 是分布式层面的恢复机制。

十三、总结#

主题核心要点关键词
Ceph 架构RADOS + MON + OSD + MDS,块/文件/对象三种接口RADOS, CRUSH
CRUSH 算法确定性、均衡、故障域感知的数据放置Straw 桶, 层级选择
数据一致性Quorum 写入保证强一致性2/3 写, 1/3 读
Gluster去中心化架构,DHT 哈希放置DHT, 弹性卷
CephFS 元数据MDS Journal + Capabilities,元数据与数据池分离Cap, Journal
纠删码池低存储开销容错,但不适合随机写入EC, 读-改-写
PG 状态机Peering → Active → Clean,故障触发 Recovering/BackfillingPeering, Backfill
RBD条带化 + 分层克隆 + 快照,CoW 策略Layering, Clone
故障恢复OSD down → PG Peering → Backfill → Active+Clean恢复速率

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

分布式文件系统
https://blog.souloss.com/posts/storage/storage-distributed-filesystem/
作者
Souloss
发布于
2025-07-14
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时