Ceph数据分布算法实操:PG规划、权重调整与扩容排障指南 1. 一篇偏理论的系列文第三篇凭什么讲实操如果你在搜索引擎里打开这个标题大概率会看到一堆CRUSH算法的公式推导、straw2桶类型的权值计算、PG与OSD的映射关系图解。前两篇如果按这个节奏走第三篇很容易变成“更深的数学课”或者“更细的源码解读”。但说实话对于一个正在维护Ceph集群、已经被扩缩容和慢请求折磨过的工程师来说真正缺的不是公式而是“这些公式在集群里到底怎么影响我的PG分布”。所以我打算把《Ceph数据分布算法三》这篇写成一次“数据分布算法的沙盘推演”。我不打算再铺垫一遍bucket、rule、hash seed这些基础概念——那是第一篇和第二篇的活儿。这篇直接进入你可能正在面对的几件事PG数量到底怎么定、OSD权重调整后分布为什么还是歪的、扩容一个机柜为什么数据迁移量吓死人、以及CRUSH规则里那些故障域参数写错之后会出现什么诡异现象。为什么值得单独拿一篇讲这些因为我在生产环境里见过太多“PG分布不均”和“扩容后性能反而下降”的案例根因都指向同一个地方不是Ceph算法本身有问题而是使用的人没有充分理解这个算法背后那几个关键参数的真实语义导致配置和使用方式偏离了算法设计的假设。这篇就是把这些“语义”和“假设”摊开来讲清楚。如果你是刚接触Ceph、还没亲手建过集群这篇确实会稍微烧脑一点。但如果你已经有一两个月的运维经验正在为PG数量、OSD权重、数据平衡周期这些问题头疼那这篇几乎就是照着你的工单写的。我们直接从“PG数怎么就算对了”开始。2. PG数量的刀尖上行走从数学公式到风险代价2.1 那个被反复引用的公式和它的隐含前提每次讨论PG数量几乎所有人都会背出那个建议公式总PG数 ≈ (OSD总数 × 100) / 副本数比如你有30个OSD、副本数为3那就是 (30 × 100) / 3 1000 个PG。这个数字确实安全无论你怎么调整OSD权重每块盘上承载的PG数量都在一个可控范围内。但很少有人讲清楚这个公式背后其实有一个非常重要的前提每个OSD的容量和性能应当大致相近。如果你的集群里SSD和HDD混布或者同一种盘但容量差了一倍上面这个公式算出来的PG总数虽然没错但分布效果远没有你想象的那么均匀。为什么因为PG映射到OSD时CRUSH会优先考虑权重通常对应容量同时希望PG数量分布均衡。可如果OSD容量差异过大小容量盘会被分配相对少的PG大容量盘相对多——听上去很合理但实际运行中你会看到大容量盘的总IOPS被摊薄小容量盘却可能因为热点数据被塞满。所以我把大家“够了就行”的公式细化成下面这套判断逻辑如果你的集群OSD容量一致、没有特殊性能分层按上面公式取整性能和安全基本平衡。如果存在容量差异建议把公式改成按“权重比例”分配计算每个OSD权重占集群总权重的比例再把这个比例乘以总PG数。容量越大的盘PG数越多这点和CRUSH的加权随机选择是天然吻合的。无论怎么算单个OSD承载的PG上限别超过500。一旦超过恢复风暴时单盘承担的复制压力会明显拉高延迟低于100又可能导致分布粒度不够恢复时的并行度不足。这里再补充一个容易忽略的点公式里的“OSD总数”应该取的是参与数据存储的OSD总数而不是集群里“在线的OSD总数”。如果你有部分OSD处于out状态或者被暂时踢出集群它们不承担PG却仍会被计入总数导致算出来的PG总量偏大。2.2 一个PG数调整的真实改动流程我们团队在生产环境里就做过一次PG数量调整。老集群初始建了512个PG后来因为扩容OSD从20个增加到35个调整后的建议值开始指向1024。如果你直接修改pg_num和pg_pg_numCeph会触发一次“PG分裂”也就是把现有PG一分为二。那次调整的完整命令行和关键步骤是这样的# 确认当前状态记录旧PG数和各池现状 ceph osd pool ls detail ceph pg dump | grep -E ^pg_stats | head -n 1 # 先调整pgp_num让PG映射逐步收敛 ceph osd pool set pool_name pgp_num 1024 # 确认调整后映射稳定后再修改pg_num ceph osd pool set pool_name pg_num 1024这里有个极关键的细节先调pgp_num和先调pg_num的顺序影响完全不同。实际正确的顺序是先调pgp_num再调pg_num。原因在于pgp_num代表“参与数据映射的PG数量”调整它会让Ceph基于新的权重重新计算已有PG的分布pg_num则是“池里实际存在的PG数量”改pg_num会增加新PG但不会立刻改变旧PG的位置。这两个参数协同变化时才能配合CRUSH做平滑迁移。但更重要的一点是调pg_num引发了PG分裂而分裂出的新PG在初始时刻属于同一个OSD集合。此时如果你观察数据分布会发现新PG没有立刻迁移而是等pgp_num生效后才逐步扩散。这个过程中Ceph会启动一轮PG backfill把新PG中的数据复制到新的目标OSD上。如果新PG恰好落在低性能的OSD上或者集群里同时在跑大批读取任务你就会看到部分OSD的负载明显飙升。所以操作前务必做三件事第一把集群的recovery和backfill速率临时降下来再执行修改第二逐池操作不要一个命令改所有池第三改完后用ceph pg stat持续观察activeclean的比例直到回到正常水位再去动下一个池。2.3 我踩过的一个PG数量坑数据倾斜的隐藏开关有一次我们刚完成扩容为了省事把新加的一组OSD直接加入现有桶然后只调整了它们自己的权重没有调整PG数量。三个月后某台容量最大的OSD使用率已经到了87%而同机柜的另一块盘只有53%。排查过程并没有直接指向PG数量而是从OSD的PG分布入手我按PG数量排序发现那块87%的盘上PG数量并不异常但它承载的PG中好几个都来自同一个数据热点池。CRUSH只保证PG均匀不保证数据大小均匀。如果某个池本身数据量很大而它的PG总数相对池的容量配比来说太少就会出现“PG数量均衡、但数据量失衡”的现象。解决方式分两步给大池单独增加PG数量而不是所有池用同一个pg_num。很多新版本Ceph支持每个池独立设置这一点常被忽略。如果业务允许把热数据池拆成多池或开启池级别的autoscale模式由集群根据实际数据量自动调整PG数量。所以那一轮扩展后我把监控里的PG数量指标分成两类看一类是“PG数量分布”评估CRUSH的均衡性另一类是“每个PG的实际数据量”评估池级别的偏斜。两个指标配合才能把数据分布算法这块吃透。3. 权重、故障域和容器设计CRUSH规则才是分布的真正导演3.1 权重不只是容量straw2是怎么理解“加权随机”的如果你已经打开过ceph osd tree应该见过类似这样的输出ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF -1 0.19598 root default -3 0.09799 host node1 1 hdd 0.04999 osd.1 up 1.00000 1.00000这个WEIGHT值默认单位是TB通常直接由ceph-volume按磁盘容量初始化。但你完全可以手动调整它比如把一个慢盘从2.0改成0.5它就少承担80%的PG分布权重。这里有一个新手常犯的错误以为权重调小后数据就会立刻从这块盘上迁走。真实情况是权重的变化只会影响后续CRUSH的随机选择过程不会主动触发存储层数据搬迁。要触发重新分布需要ceph osd reweight-by-utilization或手动将某个OSD标记为out再重新in或者通过ceph osd reweight-by-pg类型的命令强制重算。在我们实践中最稳妥的做法是先ceph osd reweight-by-utilization 110把超过集群平均利用率10%以上的高负载盘重新计算权重观察一段时间如果还不均衡再手动设置REWEIGHT。直接粗暴地改权重容易导致其他盘过载因为权重是全局变量牵一发动全身。另一个关于straw2的要点是它对不同权重的处理逻辑。straw2算法不会像简单加权轮询那样按权重依次排队分配而是会尽量让每个OSD的分布概率与权重成比例同时添加随机性来降低多个PG聚集在同一个目标上的概率。但这里的“随机”受一个隐性的随机种子影响这就是为什么同一个集群、甚至是相同配置的两个集群最终PG分布图看起来也不完全一样。你没办法控制这个随机性只能通过增加PG总数来平滑它。3.2 故障域设计一个rule字符串引发的“鸡生蛋”问题CRUSH规则的最大工程价值就是让PG在选择副本时天然避开同一故障域。举个例子rule replicated_rule { id 0 type replicated min_size 2 max_size 3 step take default step chooseleaf firstn 0 type host step emit }这里chooseleaf firstn 0 type host的含义是对每个PG的每个副本在host这个层级选择一个叶子节点。因为叶子节点是OSD所以选择时会在不同host上挑选OSD从而保证同一PG的3个副本不会落在同一台机器上。你可能以为这个规则写对了就万事大吉。但实际运维中我见过step chooseleaf firstn 0 type rack的写法结果当整个rack的OSD权重差异一大CRUSH就无法保证PG在rack内分布均衡导致明明有三个rack数据量却集中在两个rack里。这个问题的幕后推手其实是“故障域数量小于副本数”。比如集群只有2个rack却要求3副本分布在3个不同rack上——Ceph无法满足只能允许同rack内存在两个副本这个场景下故障域设计形同虚设。所以在规划rule之前建议先做一件事梳理物理拓扑明确“能容忍哪个层级故障”。如果你的核心容灾要求是“一个机柜断电不影响数据完整性”那rule里的type必须选rack如果只要求“一台服务器宕机不影响”选host就够了。把故障域层级设成更高层级会导致PG选择范围变小、数据均衡粒度变差性能也会受影响。此外新版本支持的bulk池可以配合ceph osd pool set pool_name bulk true让CRUSH在新池创建时更快地从整体分布角度分配PG避免新池数据长期集中在一小撮OSD上。这种“快速均匀分布”的机制对创建大量临时池的场景帮助很大但如果你一个池要长期使用且数据随机写入bulk的价值就不那么明显。3.3 权重与故障域的联动你以为的“均匀”不是算法眼里的“均匀”有一类很经典的故障管理员手动把集群里所有OSD的权重统一乘了一个系数比如原来2.0的都改成1.03.0的都改成1.5结果发现PG分布反而更歪了。这是因为CRUSH在计算叶子节点概率时不光看OSD自身权重还看父级桶的权重总和以及桶内所有兄弟节点的权重相对大小。把每个OSD权重整体缩小但桶和子桶的比例关系未变分布概率不会变化——可由于总权重变小后PG数量不变每个PG之间的相对位置关系被重排短暂的不均匀几乎必然出现。再提一个高级用法ceph osd primary-affinity。这个值可以降低某个OSD成为PG主OSD的概率但让它仍然参与副本存储。对一台老盘或性能较差的盘最适合的调整就是它。我们会把一块旧SATA盘的primary-affinity从1.0降到0.5读写热点明显避开但复制数据仍能正常落盘。这个方法比调低权重更精准因为权重的变化会直接影响数据量分布而primary-affinity只影响“谁当主”不改变RAW容量承载关系。因此设计和运维CRUSH时我给自己定了一条检查清单每个故障域层级下的OSD数量是否满足“副本数≤故障域数量”。同一规则下各底层桶的权重比例是否与实际容量和使用率匹配。主OSD压力分配是否通过primary-affinity做过二次微调。是否需要为性能差异明显的OSD单独建host桶然后在规则中step chooseleaf顺序上做区分。如果这些点都没问题再谈PG分布才是有意义的。4. 扩容缩容时的“数据洪峰”为什么算法聪明但还是让人心颤4.1 新OSD加入后的背填风暴一步步逼平分布想象这样一个场景你有30个OSD每个盘上平均分布着约1000个PG的副本。突然加入6个OSD每个权重和现有盘差不多。CRUSH算法意识到新成员加入后需要重新计算PG到OSD的映射让部分PG迁移到新OSD上以实现权重均衡。迁移量大概是多少我们按最常见的经验法则来估集群总数据量为120TB新增OSD带来的总可用容量占新总容量的比例约为6/36 16.7%因此大约会有 16.7% × 120TB ≈ 20TB 的数据在不同的OSD之间迁移。如果单个OSD的复制带宽按100MB/s算主动backfill完成20TB大约需要 20×1024×1024 / (6×100) ≈ 34,952秒也就是大约9.7小时。实际过程中带宽还要被业务读写抢占用时翻倍很常见。这个数学过程没有bug但从运维角度看“9.7小时”本身就是需要管理的风险。所以扩容前我会按下面的顺序操作先把新OSD按照物理位置和类型加入正确的bucket而不是直接加根节点。这样可以保证新盘直接进入正确的故障域避免后续reweight时跨rack搬运数据。设置osd_max_backfills为较小的值比如1或2避免同时backfill的PG太多再把osd_recovery_max_active也对应调低。检查并调节两个重要参数osd_recovery_sleep和osd_backfill_sleep。新版Ceph中适当增加sleep比如0.25秒可以显著降低恢复任务对业务I/O的竞争。观察ceph df的%RAW USED和ceph pg stat的activeclean比例当所有新建OSD的利用率与老盘接近时再把限速参数调回正常。这个过程听着不难但压力在于“PG迁移数量是否符合预期”这件事很难提前精确预测。因为CRUSH的映射选择依赖于现有桶的子树结构、每个bucket里的叶子顺序、以及具体的规则参数。好在ceph balancer模式提供了upmap玩法它可以精细调整特定PG的OSD组合让分布更均衡。我们在新盘加入后通常会开启ceph balancer mode upmap让集群自动优化但是只对独立池生效而且要求PG数量充足。4.2 缩容时更容易被忽视的常见坑OSD out后为什么有些PG不见了和扩容相比缩容更考验理解。当你执行ceph osd out osd_id后该OSD不再承担新PG写入但它自身保存的数据还在等待被迁移走随后ceph osd crush remove osd_id会把它从CRUSH树中摘除此时所有PG的映射都必须重新计算。我曾遇到团队同事直接把OSD从CRUSH树里摘下结果因为那个OSD上仍有大量PG重映射压力把所有在线盘打满业务请求超时率一度超过20%。复盘后发现我们漏掉了关键的智能缩容顺序先用ceph osd out让集群进入正常迁移流程等该OSD上的PG数量降到很低、且所有PG都activeclean后才执行从CRUSH树的删除。顺带说一个不少人都踩过的坑ceph osd out和ceph osd down的语义很容易混淆。out表示该OSD不参与新数据分布和读取映射但仍可存有旧数据down表示该OSD进程不在线数据不可访问。强行down一个还在提供服务的OSD会导致数据可访问性降低甚至触发部分PG变成degraded。所以缩容的正常路径是 out → 等待迁移完成期间该OSD也允许读取→ 确认该OSD上PG数量趋近于零 → 移除CRUSH节点 → 停止进程。4.3 从“迁移完成”到“分布均衡”的最后一公里很多管理员看到PG状态全部activeclean就以为扩容成功了。但在生产视角里activeclean只说明数据没有损坏、所有副本完整不代表PG分布已经足够均衡。你可以通过下面的命令快速确认ceph osd df tree | sort -k5 -n观察USE列和VAR列VAR是实际用量相对平均用量的偏差百分比。我们通常要求VAR最大不超过1.15也就是15%的偏差如果超过1.2就会触发手动优化。另一种快速定位手段是ceph pg dump 2/dev/null | awk {print $1, $16} | sort -k2 -n | tail -n 20查看PG id和对应的OSD集合找出那些明显偏向某个OSD的PG。如果确实有零星的PG分布不理想且集群开启了balancer可以让upmap慢慢修正。但要注意upmap修改的是特定PG的OSD组合它像手工补丁一样会改变局部映射也意味着这些PG在后续的权重重算中可能会被再次移动。所以开启upmap后建议持续观察直到集群在无出现新OSD变化的情况下保持稳定。5. 一例典型事故复盘权重配错导致的热点倾斜5.1 现场为什么一个“看起来很健康”的集群越来越慢去年某个业务集群出现了典型的“看起来健康但性能很差”的故障。所有OSD都是upPG全是activeclean监控页面上没有红色告警。但客户端持续出现高延迟尤其是小文件写入到了令人难以忍受的地步。我们第一反应是网络或硬件问题排查后全部排除。直到ceph osd perf看到某些OSD的commit/apply延迟到了200毫秒而其他OSD只有几毫秒才意识到这大概率是分布问题。用ceph osd df检查时发现有两块大容量盘的USE已经超过80%而其余的只有30%~50%。进一步检查PG分布图那两块盘上承载的PG数量确实只比平均值多了一点点但它们承载的PG中恰好包含若干个热点池的副本。热点池的数据量在整体占比里并不算高但请求频率极高导致这两块盘成了事实上的瓶颈。5.2 排查链路把“算法如何思考”反过来用这次排查真正有意思的部分是我们如何反向使用CRUSH算法来定位问题。先把所有PG按OSD归属列出来提取出那两块高延迟盘承载的全部PG的ID然后计算每个PG所属池。结果发现热点池的120个PG中有34个被映射到了这两块盘上。为什么同样是热点池其他OSD每块只承载10~12个而这两块承载了17个我回到CRUSH规则重新检查了这两个OSD的权重。原来它们在初始化时因为容量更大权重被手动调成了3.0而其他盘是2.0。看起来权重比例对得上但问题出在它们所在host桶底下只有它们两个OSD而其他host桶里可能挂了三四个OSD。按straw2的逻辑大权重在子桶内获得了更高的选择概率同时由于host桶层级的存在跨host迁移时还会叠加父桶权重的影响最终让这两个“大个儿”被更频繁地选中。至此问题本质清晰了不是CRUSH不均衡而是“容量权重导致的概率放大”超过了实际数据访问模式的需求。我们不需要把它们变成热点盘我们需要降低它们在整个映射里被选中的概率——权重可以降低一点但更关键的是让热点池的数据能更均匀地散落在更多OSD上。5.3 修复动作与效果验证最终的修复方案分三步手动把这两块OSD的primary-affinity降为0.5避免太多读请求继续直接打到主副本上同时它们的副本数据不会减少数据安全性不受影响。开启ceph balancer mode upmap并保持每30分钟一次自动调度让热池个别PG从这两块盘上移出。针对业务的核心池开启pg_autoscale_mode并观察池的PG数量是否需要增加。该池从512调整为1024后每个PG的平均数据量下降热点请求的集中度也随之降低。完成后大概4小时ceph osd perf里这两块盘的延迟回落到了个位数毫秒级别客户端侧的高峰延迟从500ms降到80ms以内。后续再观察一周分布偏移值维持在1.1以内。这次事故也验证了我常做的一件额外工作建议在部署阶段就把“承载峰值性能需求”和“承载容量数据”的OSD分别打上不同的class或bucket标记并在存储池的规则中分开选择。例如高性能池的rule只选SSD class大容量池选HDD class。CRUSH对class的过滤是在选择叶子前完成的所以不会破坏故障域的约束但能精准控制每一类数据落盘的位置。6. 用数据说话几种分布均衡检查方法对比6.1 从命令到指标哪些输出值得盯检查数据分布是否健康不能只看一两个命令。我自己常用这样一组“组合拳”按优先级排列ceph osd df tree看每块OSD的RAW USED和VAR偏差这直接反映容量分布。ceph pg dump | ceph pg dump --format json-pretty看每个PG的ACTING集合判断PG组合的分散度和故障域隔离。ceph osd tree配合ceph osd class ls确认OSD的class标记正确否则CRUSH的choose类型可能选了错误的盘。ceph balancer status查看当前balancer模式和upmap是否在运行以及最近优化的成果。ceph pg ls-by-pool pool_name针对某一个池单独统计PG的OSD分布避免池间数据差异掩盖局部不均。这些命令其实都是基础难在如何解读。我通常会把ceph osd df tree中的使用率按“最大/最小”比值做监控阈值。例如最大使用率除以最小使用率超过2就说明极不均衡超过1.5需要警惕安全水位一般是1.2以内。指标方面除了Prometheus收集的ceph_osd_used_bytes和ceph_osd_pg_count我更建议关注ceph_pg_status_count中activeclean那么多以及ceph_recovery_backfill_activated这类恢复相关的指标。它们能在问题演变成延迟故障前提前暴露分布变化趋势。6.2 表格对比三种均衡手段的适用场景和成本为了让你一眼搞清楚什么情况下用什么手段我整理了下面这个表均衡手段适用场景成本与风险备注调OSD权重OSD容量差异导致的分布不均低但会触发PG重新映射和迁移改动谨慎先小步挪primary-affinity调整某盘读写压力过高但容量尚可低不迁移数据只影响主副本选择可快速降压建议磁盘性能不均时优先balancer upmapPG分布局部偏差且已开启自动化中由系统修改特定PG映射可能引起少量数据迁移依赖PG数量多才能生效且建议后期使用手动reweight-by-utilization批量盘使用率偏差过大中行为近似OSD out/in迁移量可能较大我们在清洗盘或容量升级后使用每种手段都有时效性。比如reweight-by-utilization适合“某一瞬间”的均衡修复长时间运行后数据访问模式又会打破平衡这时候主要靠upmap或周期性巡检。6.3 用“模拟迁移量”来预判风险线上改配置最怕的就是“改完才知道迁移量巨大”。好在Ceph提供了一个可以离线试算的方式.osd_crush_update_on_start和osd_pool_default_crush_rule不能直接预测但我们可以借助crush map的导出分析工具。常见做法是备份当前运行图和期望调整后的图用crushtool在本地直接计算两种映射下每个PG落在哪些OSD上对比差异即可知道会有多少PG发生迁移。比如# 获取当前map并反编译 ceph osd getcrushmap -o /tmp/crush.map crushtool -d /tmp/crush.map -o /tmp/crush.txt # 手动编辑crush.txt中你想调整的权重或结构比如osd.5 weight 2.0改1.5 # 然后重新编译并模拟 crushtool -c /tmp/crush-new.txt -o /tmp/crush-new.map crushtool -i /tmp/crush-new.map --test --rule 0 \ --num-rep 3 --max-x 1024 --output-name /tmp/generated通过对比旧map和新map下--test输出的PG映射差异你能估算出此次变更会让多少PG发生跨OSD移动。这个方法我一直很推荐特别是对关键生产集群做结构性变更之前。花五分钟模拟省去一次半夜升级事故的折腾。7. 关于Ceph数据分布算法几件我希望早点知道的事文章到这里核心的实操内容已经差不多了。最后说几句大实话。Ceph的数据分布算法再精巧也只是个“目标函数”和“随机选择器”。它的输出是否符合你的预期很大程度取决于你有没有把集群的物理拓扑、容量比例、故障域边界转换成它认识的CRUSH语言。我见过太多团队把OSD一股脑加到default桶里让所有PG在整集群范围内随机洒表面看数据分布很均匀但一旦两个机柜断电或一块网卡故障数据可靠性就会打折扣——这些都不是算法的问题是使用方式的问题。另外两件事需要时时记住第一任何形式的权重调整和reweight操作本质上都是在跟CRUSH的随机性博弈不可能做到“绝对均匀”只能追求“相对稳定”。所以不要一看到VAR超过1.2就立刻改权重先看看是不是业务写入模式造成的短期偏移硬调反而可能引入抖动。第二数据分布是否合理永远要放在“存储池规则故障域OSD性能”四维框架里看单看PG数量没有任何意义。如果你正在做集群扩容或新集群规划我的建议是先把拓扑和规则想清楚再算PG数量最后才动OSD权重。顺序反了后面所有的“均衡优化”都是在给当初的懒惰还债。这套认知我是在踩了无数个工单后一点点磨出来的希望这篇能帮你少走几段弯路。