SStorpath
原理预计 40 分钟

CRUSH 与 PG:数据到底落在哪块盘上

没有中心元数据服务,客户端却能算出数据在哪 —— CRUSH 是 Ceph 最漂亮的设计。

学完这节你能做到

  • 手工推演 object → PG → OSD 的映射过程
  • 为集群估算合理的 PG 数量
  • 读懂 CRUSH map 与 rule,按机架划分故障域

没有元数据表,怎么知道数据在哪

传统分布式存储用一张元数据表记录"哪个数据在哪台机器上"。这张表要么放在中心节点(成为瓶颈和单点),要么分布式维护(复杂且要一致性协议)。

Ceph 的答案是:不记录,直接算。

对象名 ──哈希──> PG ──CRUSH 算法──> 一组 OSD

客户端拿到 crushmap 之后,本地就能算出任何对象该去哪。没有查询,没有中心节点,客户端数量可以无限扩展。

第一步:对象 → PG

pg_id = hash(对象名) % pg_num

就是简单的哈希取模。那为什么要有 PG(Placement Group)这一层,不直接把对象映射到 OSD?

因为需要一个中间粒度。 假设集群有 1200 万个对象和 36 个 OSD:

  • 直接映射对象到 OSD:要跟踪 1200 万个对象的分布状态,元数据爆炸
  • 引入 PG:把对象分成 673 个组,只跟踪 673 个 PG 的状态

PG 是 Ceph 里所有状态管理的单位:恢复、回填、scrub、peering 都是以 PG 为单位进行的。ceph -s 里那行 673 active+clean 就是在报告这 673 个组的状态。

第二步:PG → OSD,CRUSH 登场

CRUSH(Controlled Replication Under Scalable Hashing)输入四样东西:

  1. PG 编号
  2. crushmap:集群的物理拓扑树(root → rack → host → osd)
  3. CRUSH rule:选取规则,包括故障域层级和副本数
  4. osdmap:哪些 OSD 当前是 up / in

输出是一个有序的 OSD 列表,例如 [7, 23, 41]。第一个是 primary,负责协调这个 PG 的所有读写。

# 亲手推演一次:这个对象在哪
ceph osd map rbd myobject
# osdmap e1234 pool 'rbd' (3) object 'myobject' -> pg 3.a7f2c1b8 (3.b8) -> up ([7,23,41], p7) acting ([7,23,41], p7)

这条命令会成为你排障时的常客:给定一个对象,立刻知道它落在哪几块盘上

亲手推一遍

下面是一套 3 机架 × 2 主机 × 4 OSD 的演示集群。建议按顺序做这四组实验,比读十遍文字管用:

  1. 改对象名的最后一个字符 —— PG 完全变到另一个位置,说明哈希没有局部性
  2. 把故障域从 host 改成 rack —— 观察三个副本被强制分散到三个机架
  3. 点掉 acting 里的某个 OSD —— up 与 acting 分叉,PG 进入 remapped
  4. 把故障域设成 rack,然后点掉整个 rack 的 8 个 OSD —— 副本数凑不齐,进入 undersized+degraded
推演对象 → PG → OSD 映射
1. hash(对象名) = 1774540074
2. pg_seq = hash % pg_num = 1774540074 % 128 = 42
3. PG = 3.2a
4. CRUSH(PG, crushmap, rule) → up = [17, 15, 20]
5. 剔除 down 后 acting = [17, 15, 20],primary = osd.17
active+clean
点击任意 OSD 可把它标记为 down,观察 acting 怎么变 primary 副本 down
rack1
ceph-node1
ceph-node2
rack2
ceph-node3
ceph-node4
rack3
ceph-node5
ceph-node6
把 pg_num 从 128 调到 256,抽样 200 个对象里约 50% 会落到不同的 PG 上 —— 这些数据都要搬家。 这就是「调整 pg_num 会触发大规模迁移」的由来。
up set 与 acting set 的区别
  • up set:按当前 crushmap 计算出的"应该"负责这个 PG 的 OSD
  • acting set:当前"实际"在负责的 OSD

正常情况下两者相同。当某个 OSD 挂了、正在回填、或者被临时接管时,它们会不一致——up [7,23,41] acting [7,23,55] 表示 41 出了问题,55 临时顶上。看到两者不一致,就说明集群处于非稳态。

PG 数量怎么定

PG 太少:每个 PG 承载的数据太多,分布不均、恢复粒度粗。 PG 太多:每个 OSD 要维护的 PG 状态过多,内存和 CPU 开销上升。

官方经验公式:

pg_num = (OSD 数 × 100) ÷ 副本数     然后向上取到 2 的幂

例:36 个 OSD,3 副本
    36 × 100 ÷ 3 = 1200 → 取 2048?

但这是所有池加起来的总量,要按各池的数据量占比分配。实际操作中现在基本不用手算了:

# 开启自动调整(默认已开)
ceph osd pool set rbd pg_autoscale_mode on

# 看 autoscaler 的建议
ceph osd pool autoscale-status
POOL                SIZE  TARGET SIZE  RATE  RAW CAPACITY  RATIO  PG_NUM  NEW PG_NUM  AUTOSCALE
rbd                28.0T               3.0        262.0T  0.3206     256              on
cephfs.prod.meta  186.0G               3.0        262.0T  0.0021      32              on
cephfs.prod.data   16.0T               3.0        262.0T  0.1832     256              on
!调整 pg_num 会触发大规模数据迁移

pg_num 变化意味着对象到 PG 的映射变了,几乎所有数据都要重新分布。在有数据的集群上调整 PG,会产生长时间的高负载。

因此:建池时就按预期规模把 PG 设好,或者依赖 autoscaler 在数据量还小的时候完成调整。如果必须在生产集群上调,务必限速并选在业务低峰。

PG 状态机:读懂那些奇怪的组合

ceph -s 里 PG 状态是若干标志的组合,常见的有:

状态含义该做什么
active+clean一切正常
peering副本之间正在协商数据版本等,通常几秒
degraded副本数不足等恢复,或查为什么 OSD 没起来
undersized实际副本数少于 size同上
backfilling正在往新 OSD 大批量搬数据等,可限速
recovering正在补少量落后的对象
remappedacting set 与 up set 不一致通常伴随其它状态
inconsistentscrub 发现副本内容不一致需要人工介入
incomplete缺少足够的历史信息,无法确定正确数据严重,可能丢数据
stale长时间没收到该 PG 的状态上报检查 primary OSD 是否失联
# 找出所有非 active+clean 的 PG
ceph pg dump_stuck

# 看某个 PG 的详细情况:谁是 primary,卡在哪
ceph pg 3.1f query

# scrub 发现不一致时的修复
ceph pg repair 3.1f
×incomplete 和 inconsistent 完全不是一回事
  • inconsistent:副本内容对不上,但 Ceph 知道谁是对的,ceph pg repair 通常能修
  • incomplete:Ceph 不知道正确数据是什么,往往是因为持有最新数据的 OSD 彻底没了

看到 incomplete 不要自己乱试命令,先停下来评估:涉及哪些池、哪些数据、有没有备份。这是少数需要向上升级、必要时联系专业支持的场景。

CRUSH rule 实操:按机架分布

# 查看现有规则
ceph osd crush rule ls
ceph osd crush rule dump replicated_rule

# 在拓扑里加入机架层级
ceph osd crush add-bucket rack1 rack
ceph osd crush move rack1 root=default
ceph osd crush move ceph-node1 rack=rack1

# 创建一个按 rack 分布的规则
ceph osd crush rule create-replicated by-rack default rack

# 让某个池使用它(会触发数据迁移)
ceph osd pool set rbd crush_rule by-rack

改完一定要复核实际分布,不能只看命令返回成功:

ceph osd tree                      # 拓扑对不对
ceph pg ls-by-pool rbd | head      # 副本是不是真的跨机架了
检查点单选

为什么 Ceph 要在对象和 OSD 之间加一层 PG,而不是直接把对象映射到 OSD?

检查点单选

ceph pg dump 显示某 PG 为 up [7,23,41] acting [7,23,55]。这说明什么?

检查点单选

在一个已经存了 40TB 数据的池上把 pg_num 从 128 改到 512,会发生什么?

这节课的落点

  • 对象 → PG 是哈希取模,PG → OSD 是 CRUSH 计算,全程无需查表
  • PG 是所有状态管理的单位:恢复、回填、scrub、peering
  • ceph osd map <pool> <object> 能直接算出对象落在哪几块盘
  • up set 是应然,acting set 是实然,不一致就说明非稳态
  • pg_num 变更会引发全量迁移,靠 autoscaler 或前期规划避免
  • inconsistent 可以 repair,incomplete 要立刻升级处理

延伸资料

  • ·k8s-in-actionstorage/cephadm/2-ceph-rados.md