土法炼钢 · 系统与基础设施

【Ceph RADOS】CRUSH 加深:rule/bucket/weight 机制与失败域语义

文章导航

分类入口
storagedistributed
标签入口
#ceph#crush#bucket#osdmap#placement#failure-domain#squid#v19.2.5

目录

站内 distributed/38 已经讲清 CRUSH 的算法直觉——给定对象 hash 和副本数,确定性地计算出一组 OSD,不需要查中心目录。本篇不重复算法直觉,而是深入 distributed/38 之下的机制细节:bucket 类型的选择如何影响重平衡量、rule 的 step 语法如何精确控制失败域、weight 体系的三层含义,以及 CRUSH 与一致性哈希的根本性差异。

本文是「Ceph RADOS 存储内核」系列第 4 篇(共 18 篇)。→ 系列目录

篇目 核心内容
第 1 篇 · RADOS 全景 缺口、五轴坐标系、18 篇路线
第 2 篇 · MON/MGR 与 Map quorum、OSDMap/PGMap、epoch 传播
第 3 篇 · OSD 与 Messenger daemon、msgr2、Op 进入 OSD
第 4 篇 · CRUSH 加深 rule/bucket/weight 机制钉
第 5 篇 · PG 与 Peering PrimaryLogPG、PG log、状态机
第 6 篇 · 客户端写路径 librados → 副本应答、stale read 边界

版本锚定:Ceph Squid v19.2.5。CRUSH 算法核心实现在 src/crush/mapper.c @ v19.2.5;map 管理在 src/crush/CrushWrapper.h


一、CRUSH map 的完整结构

CRUSH map 内嵌在 OSDMap(OSDMap::crushsrc/osd/OSDMap.h @ v19.2.5),含三块:device 列表(含 hdd/ssd/nvme class)、bucket 树、rule 列表。客户端与每个 OSD 必须对同一 epoch 的树与 rule 得出同一 acting——这是「本地计算」的前提,也是 map 传播税的来源。

导出与反编译(需真实集群或离线 map 文件):ceph osd getcrushmapcrushtool -d。本篇不粘贴伪造 dump;只钉字段语义。相对 distributed/38,下文默认读者已理解「hash → PG → CRUSH 出 OSD 集合」的故事线,直接进入 rule/失败域/权重层/迁移下界

本篇相对 38 的加深点有三条:(1)bucket alg 对迁移量的可检验影响;(2)firstnindep 对副本池/EC 的语义分叉;(3)device weight / reweight / primary_affinity 三层不可互换。不重写 straw2 抽签伪代码。若只想复习直觉,读 38 即可;若要做扩缩容评审、失败域验收与迁移量估算,应从本篇的字段、rule 语义与代价模型出发,而不是再背一遍抽签故事本身而已。


二、bucket 类型:相对 38 只钉迁移不变量

distributed/38 已给 straw2 抽签直觉与伪代码。本篇不重讲算法故事,只钉 Squid 默认与排障含义src/crush/crush.hmapper.c @ v19.2.5):

类型 何时还会遇到 变更权重时的迁移 排障信号
straw2 新建 bucket 默认 接近最优:\(M\approx M_{\min}\)(见第六节) 正常
straw(straw1) 老集群未转换 可能超量迁移 扩容迁移量 ≫ \(\Delta W/W\)
list / uniform / tree 遗留 差或全量 仅考古

ceph osd crush dumpalg 字段;迁移量异常时先查是否仍为 straw1,再查 upmap。straw2 修正动机见 mapper.c 注释与 Weil SC 2006 §3.2 的加权选择框架——无独立 peer-reviewed straw2 论文,以源码+官方 CRUSH 文档为 A 级。

为什么迁移量值得单独钉死:扩容窗口的风险往往不是「算不算得出位置」,而是「会搬多少、搬多久、会触发多少 peering」。若实际迁移字节远高于 \(M_{\min}\),却去调 osd_recovery_sleep,等于在错误的轴上用力。把 bucket alg 当成第一检查项,是本篇相对 38 的工程增量。

老集群升级时,转换 straw1→straw2 本身也可能触发一轮重平衡;变更窗口要单独评估,不能假设「只是改个名字」。转换前用 crushtool 对抽样 PG 对比变更前后映射,比直接在生产树上一键替换更可控。


三、rule step:失败域与 firstn / indep

crush_do_rule()mapper.c)维护当前工作集;相对 38 的「chooseleaf 选不同 rack」直觉,这里钉 step 语义与两种选择模式

step take <bucket> [class <hdd|ssd|nvme>]
step chooseleaf firstn|indep <n> type <failure-domain>
step emit
构件 精确含义
take + class 设备类过滤在树侧完成;混闪盘池靠 class 而不是靠手工拆树
chooseleaf … type T 每个结果叶子来自不同的 type-\(T\) 祖先 → 失败域 = type,不是 bucket 名字
firstn 副本池常用:有序选取,尽量填满 \(n\) 个;失败时重试
indep EC 常用:位置稳定,某 OSD 缺失时其他位置编号不变(避免整条带重映射)
emit 输出当前集;可多段 emit(复杂 rule / 部分 EC 布局)

失败模式type host 但拓扑把多个 OSD 挂在同一 host bucket 下 → 规则「看起来」满足 host 域,实际共命运。改名无效,改 type 或改树才有效。运维上「机柜掉电丢三副本」往往不是 CRUSH 算错,而是树没有按真实供电/网络域建模。

n=0 表示使用 rule 的 rule_rep(副本数 / 条带宽度由 pool 绑定)。EC profile 与 replicated rule 的 type erasure|replicated 必须与 pool 一致,否则创建期就会失败。

相对 38 的另一处加深:choose(停在中间 bucket)与 chooseleaf(落到 OSD)不要混用口吻。排障时若看到 acting 里出现「同 rack 两个 OSD」而 rule 写的是 type rack,先怀疑树,而不是怀疑 straw2 随机性——CRUSH 是确定性函数,同一 map 下结果可复现。

设备 class 过滤解决的是「混闪盘池不要靠手工拆两棵树」的问题,但 class 不能代替失败域:同 class 的盘仍可能共机柜。rule 里 take … classchooseleaf type 是正交的两刀,缺一不可。写 EC rule 时优先确认是否误用 firstn:位置编号漂移会把整条带修复成本放大,这是 EC 路径(第 11 篇)的前置坑。


四、weight 体系:三层含义与排障

CRUSH 的 weight 在三层分别存在,混淆是常见配置事故:

Device weight(CRUSH 内)

存在 CRUSH device 项,单位常按 TiB 量级理解(如 1.000 ≈ 1 TiB 级权重)。影响 straw2 选择概率与长期容量均衡。ceph osd crush reweight 改的是这一层;上线时 OSD 常按磁盘容量自动填。

OSD reweight(OSDMap,0.0–1.0)

存在 OSDMap,不进 CRUSH 树内部。ceph osd reweight 在 CRUSH 结果之后做缩放;0.0 近似踢出分布。适合维护窗口临时引流,不是改失败域的办法。

primary_affinity

存在 osd_primary_affinity[]。CRUSH 先给出 acting 集合,Primary 选取再叠亲和性。跨站点时把远端 affinity 调低,可让读/写序列化点更靠近客户端——但会让「谁当 Primary」与「副本是否均匀」脱钩,排障时要同时看 CRUSH 与 affinity。

误操作对照:想「少写某盘」却只改 affinity → 它仍可能大量做副本;想「永远不当 Primary」却只改 device weight → Primary 仍可能落在它上面。

还有一种常见混淆:把 OSD outreweight 0 当成完全同义。运维语义接近,但标志位、自动 in/out 策略与监控展示可能不同;排障时以 OSDMap 字段为准,不要靠口诀。权重体系的目标是让「容量、临时引流、谁当序列化点」三件事情可以分开调,而不是用一个旋钮解决所有问题。


五、CRUSH vs 一致性哈希:假设差在哪

维度 一致性哈希(ring) CRUSH
结构 虚拟节点环,需维护/分发 层级 bucket 树,嵌在 OSDMap
失败域 多靠虚拟节点/preference list 隐式满足 chooseleaf type T 显式硬约束
重平衡 加节点理论只迁邻居 straw2 接近 \(M_{\min}\),但依赖 map 传播
「去中心化」含义 环状态分布式 放置计算本地;map 仍中心广播

Weil SC 2006 的「decentralized placement」指的是数据路径不查中心目录,不是「没有权威 map」。与 Dynamo preference list 相比,CRUSH 用 rule 语言把机架/机房约束写成可机器执行的步骤;代价是第 2 篇的 epoch 传播税。

工程上还有一层容易被忽略的耦合:每次 CRUSH 树或 rule 变更都会推高 OSDMap epoch,从而触发大面积 PG 的 interval 变化与 peering。因此「优化放置」和「稳定 epoch」经常打架——Balancer 小步 upmap 有时比一次性大改 crush 树更可控,不是因为数学更优,而是因为对 past intervals 的冲击更小。

把 CRUSH 当成「算完就完」的静态函数,会漏掉它与 MON、peering、读租约共享的时间轴:map 越新,放置越「正确」,但集群瞬时越忙。选型与变更窗口要把这条轴算进去。


六、迁移量下界、upmap 与可观测

扩缩容时,理论最小迁移份额:

\[M_{\min} = \frac{\Delta W}{W_{\text{total}}} \times P_{\text{total}}\]

其中 \(\Delta W\) 为权重变化,\(P_{\text{total}}\) 为 PG×副本(或 EC 分片)规模。straw2 接近该下界;straw1/list/uniform 可能显著超过。实际还要叠加:

排障顺序建议:先比 \(M\)\(M_{\min}\) → 查 bucket alg → 再查 upmap → 最后才怀疑网络/恢复限速。

可检验问题:给定一次 crush reweight,用 crushtool / 导出 map 在变更前后对同一批 PG 跑 crush_do_rule,迁移集合是否与监控中的 misplaced 对象同阶?若差一个数量级,优先查 alg 与 upmap,而不是先调 recovery sleep。

另一可检验问题:关闭 balancer 前后,同一扩容操作的 epoch 增量与 peering PG 峰值如何变化?若 upmap 模式下 misplaced 更少但 epoch 更碎,需要在「迁移字节」与「peering 风暴」之间显式取舍,而不是默认「均衡一定更好」。


七、小结

  1. CRUSH map 内嵌于 OSDMap:变更抬升 epoch;所有参与者必须共图才能局部计算一致。
  2. straw2 是默认且几乎唯一应保留的 alg:相对 straw1 的超量迁移;细节算法见 distributed/38,本篇只把它当排障开关。
  3. 失败域 = chooseleaf 的 typefirstn(副本)与 indep(EC 位置稳定)语义不同;改名不改域。
  4. weight 三层不可混:device weight、OSD reweight、primary_affinity 存储位置与作用点不同。
  5. 「去中心化」= 放置计算去中心化:map 仍由 MON 广播;本地计算的前提是 epoch 够新。
  6. upmap 是二次修正:排障与变更窗口必须同时看 CRUSH 与 upmap,并评估对 epoch/peering 的副作用。纯 CRUSH 可解释;纯 upmap 追逐利用率——生产里两者常并存,深度来自说清各自不变量与失败模式,而不是做二选一口号式的表态。

八、谱系、争论与开放问题

谱系

争论:纯 CRUSH vs upmap 二次修正

A 侧(纯函数 CRUSH):Weil SC 2006 的可解释性——任意节点同一 map → 同一结果;运维可手算/crushtool 复现。
B 侧(Balancer upmap / pg_upmap_items:在 CRUSH 输出上打补丁以压平利用率;迁移更少、更贴容量,但「PG 为何在此 OSD」变成两层故事,排障与 ceph osd pg-upmap 状态耦合。

另一条对照轴是 Dynamo 式 preference list(SOSP 2007):环上隐式满足多样性,难以表达「必须跨 rack」的硬约束;CRUSH 用 rule 显式编码失败域,代价是 map 中心化广播(第 2 篇)。

开放问题

  1. CRUSH map 增量OSDMap::Incremental 减全量传输,但 CRUSH 树变更仍常整图替换;数万 OSD 时序列化/传播成本仍开放(tracker large-cluster 讨论)。
  2. 失败域 type 与真实拓扑漂移:自动化扩容若挂错父 bucket,rule 仍「合法」却共命运——需要拓扑校验,而非更多 straw 变体。可读入口:官方 crush-map-edits 与生产变更评审清单(把「树是否像真实故障域」写成检查项)。可检验提法:对每个 chooseleaf type T,抽样验证 acting 集合在真实供电/网络域上的最小割是否等于 T;若否,先改树再谈 weight。

九、参考资料

规范 / 官方文档(A)

源码(A)

论文(A)

站内对照

实验台账


上一篇:OSD 与 Messenger · 下一篇:PG 与 Peering

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .