3. Ceph 数据存储原理:CRUSH 算法、PG 与 PGP、数据分布与故障域

Ceph 之所以能够实现无中心化的高可靠、高可扩展存储,其核心在于 CRUSH 算法(Controlled Replication Under Scalable Hashing)与 PG(Placement Group)的巧妙设计。本章将深入剖析数据从客户端写入到最终落盘的完整路径,并解释 PG 与 PGP 在数据分布中的关键作用,以及如何通过故障域配置保障数据安全。

3.1 数据写入路径与 CRUSH 算法

当客户端向 Ceph 集群写入一个对象时,数据并不直接发送到 OSD,而是经过以下计算路径:

  1. 对象名 → PG:客户端使用对象名、存储池 ID 和哈希函数计算出该对象所属的 PG ID。
  2. PG → OSD 列表:CRUSH 算法根据集群拓扑(Cluster Map)和存储池的副本策略,计算出该 PG 应该分布在哪一组 OSD 上(例如 3 副本即返回 3 个 OSD)。
  3. 数据写入:客户端直接与主 OSD 通信,由主 OSD 负责将数据同步到从 OSD。
关键认知:CRUSH 算法是确定性的伪随机算法。只要集群拓扑和存储池策略不变,同一个对象永远会被映射到相同的 PG 和 OSD 集合上,无需元数据服务器参与寻址。
CRUSH 数据写入路径 CRUSH 数据写入路径 客户端 对象名 + Pool ID 哈希 → PG ID CRUSH 算法 OSD 列表 说明:CRUSH 算法直接根据集群拓扑(Cluster Map)计算出 PG 对应的 OSD 集合, 无需查询中心化元数据表,这是 Ceph 高可扩展性的基石。

3.2 PG 与 PGP 的概念与作用

PG(Placement Group) 是 Ceph 中数据管理的基本单位,它是一个逻辑容器,用于将大量对象聚合为更小的管理单元。每个 PG 包含一组对象,并负责将这些对象的副本分布到指定的 OSD 上。

PGP(Placement Group for Placement) 是用于控制 PG 在 OSD 上分布粒度的参数。PGP 的值决定了 CRUSH 算法在计算 PG 分布时使用的 PG 数量。

参数 全称 作用 典型值
PG Placement Group 对象的逻辑分组,决定数据副本的分布范围 每个 OSD 100~200 个 PG
PGP Placement Group for Placement 控制 PG 在 OSD 上的分布粒度,PGP ≤ PG 通常与 PG 相等
重要原则:PGP 的值不能大于 PG 的值。当增加 PG 数量时,必须同时调整 PGP 才能使新的分布生效。如果只增加 PG 而不增加 PGP,数据不会重新分布。

3.3 数据分布与故障域

Ceph 通过 故障域(Failure Domain) 来保证数据的高可用性。故障域定义了数据副本在物理或逻辑上的隔离层级,常见的故障域包括:

  • OSD 级别:副本分布在不同的 OSD 上,防止单个磁盘故障。
  • 主机级别:副本分布在不同的主机上,防止单台服务器故障。
  • 机架级别:副本分布在不同的机架上,防止机架级故障。
  • 数据中心级别:副本分布在不同的数据中心,防止区域性灾难。

CRUSH 算法在计算 PG 的 OSD 集合时,会严格遵循故障域约束。例如,当故障域设置为 host 时,CRUSH 会确保同一个 PG 的多个副本不会落在同一台主机上。

故障域与数据分布 故障域与数据分布(3 副本示例) 机架 1 主机 1-1 OSD.1 OSD.2 主机 1-2 OSD.3 OSD.4 机架 2 主机 2-1 OSD.5 OSD.6 主机 2-2 OSD.7 OSD.8 机架 3 主机 3-1 OSD.9 OSD.10 主机 3-2 OSD.11 OSD.12 故障域 = 机架:PG 的 3 个副本分别分布在 3 个不同机架上,任意一个机架故障不影响数据可用性

3.4 PG 与 PGP 的调整实践

在 Rook Ceph 环境中,可以通过修改存储池的 pg_num 和 pgp_num 参数来调整 PG 数量。以下是一个典型的调整流程:

# 查看当前存储池的 PG 和 PGP 数量
ceph osd pool get <pool-name> pg_num
ceph osd pool get <pool-name> pgp_num

# 增加 PG 数量(例如从 32 增加到 64)
ceph osd pool set <pool-name> pg_num 64

# 增加 PGP 数量,触发数据重新分布
ceph osd pool set <pool-name> pgp_num 64

# 监控数据迁移状态
ceph -w
最佳实践:
  • PG 总数建议按公式计算:PG 总数 = (OSD 总数 × 100) / 副本数,并向上取整到 2 的幂次。
  • 每次调整 PG 数量时,建议以 2 的幂次递增(如 32 → 64 → 128),避免数据分布不均。
  • 调整 PGP 会触发数据迁移,建议在业务低峰期操作。

3.5 故障域配置示例

在 Rook Ceph 中,故障域通过 CRUSH map 的 crush-root 和 crush-failure-domain 参数控制。以下是一个将故障域设置为 host 的存储池配置示例:

# 创建存储池时指定故障域
ceph osd pool create mypool 128 128 replicated
ceph osd pool set mypool crush_rule replicated_rule

# 查看当前 CRUSH 规则
ceph osd crush rule dump replicated_rule

# 输出示例(关键字段):
# {
#     "rule_id": 0,
#     "rule_name": "replicated_rule",
#     "ruleset": 0,
#     "type": 1,
#     "min_size": 1,
#     "max_size": 10,
#     "steps": [
#         {"op": "take", "item": "default", "item_name": "default"},
#         {"op": "chooseleaf_firstn", "num": 0, "type": "host"},
#         {"op": "emit"}
#     ]
# }

在 chooseleaf_firstn 步骤中,type 字段指定了故障域层级。常见的故障域层级包括:

故障域层级 type 值 说明
OSD osd 副本分布在不同的 OSD 上
主机 host 副本分布在不同的主机上
机架 rack 副本分布在不同的机架上
数据中心 datacenter 副本分布在不同的数据中心
Rook 中的故障域配置:在 Rook Ceph 的 CephCluster CRD 中,可以通过 spec.storage.nodes 下的 topologyKey 字段定义故障域层级。例如,设置 topologyKey: "topology.kubernetes.io/zone" 可以实现跨可用区的数据分布。

3.6 数据分布均衡性

CRUSH 算法虽然能保证确定性的数据分布,但在实际运行中,由于 OSD 的容量差异、PG 的创建和删除、OSD 的增减等因素,可能会导致数据分布不均。Ceph 提供了 reweight 和 rebalance 机制来调整数据分布:

  • reweight:手动调整 OSD 的权重,影响 CRUSH 算法选择该 OSD 的概率。
  • rebalance:自动触发 PG 在 OSD 之间的迁移,使数据分布趋于均衡。
# 查看 OSD 的使用率
ceph osd df

# 手动调整 OSD 权重(例如将 osd.5 的权重从 1.0 调整为 0.8)
ceph osd crush reweight osd.5 0.8

# 触发自动 rebalance(通常由集群自动执行)
ceph balancer mode upmap
ceph balancer on
注意:频繁调整 OSD 权重或触发 rebalance 会导致大量数据迁移,增加集群负载。建议在业务低峰期进行,并监控集群的健康状态。