Q先生的世界

面朝大海,春暖花开

经典算法深度解析|纠删码(实战篇):Ceph、HDFS 与 Azure LRC 的实现取舍

前面三篇我们一直在讲两层内容:

  1. EC 在数学上为什么成立
  2. EC 在工程上为什么会贵

但真正把 EC 用到系统里以后,你会发现还有第三层现实:

同样都是纠删码,不同存储系统会做出完全不同的设计。

原因并不神秘。

因为系统要优化的目标本来就不同:

  1. 有的系统偏大对象和冷数据
  2. 有的系统需要兼顾覆盖写
  3. 有的系统最怕跨机架修复流量
  4. 有的系统最怕元数据膨胀
  5. 有的系统能接受更复杂的后台整理流程
  6. 有的系统则更强调在线路径简单和稳定

所以这篇不再重复推导 RS、MDS 或 LRC 的原理,而是专门回答一个更接地气的问题:

当 Ceph、HDFS 和 Azure 这种真实系统做 EC 时,它们到底在优先优化什么,又分别牺牲了什么?

先把系列文章列出来,方便前后跳转:

  1. 纠删码(一):从多副本到 Reed-Solomon、有限域与 MDS 本质
  2. 纠删码(二):故障恢复、更新放大、修复带宽与 LRC 演进
  3. 纠删码(三):条带布局、故障域、降级读与分布式存储里的工程落地
  4. 纠删码(实战篇):Ceph、HDFS 与 Azure LRC 的实现取舍
  5. 纠删码(实现篇):GF 运算、SIMD、full-stripe write 与小写更新路径
  6. 纠删码(源码篇):最小 RS encoder/decoder 与 read-modify-write 伪代码
  7. 纠删码(性能篇):benchmark、NUMA、线程模型与 repair 限流
  8. 纠删码(案例排障篇):repair 打满网络、degraded read 尾延迟飙升与 partial write 放大
  9. 纠删码(架构选型篇):什么时候该用三副本、RS、LRC,什么时候根本不该上 EC

1. 先给结论:系统之间的差异,首先来自“工作负载模型”

很多人刚接触 EC 选型时,会自然地问:

  1. 谁用的是 RS
  2. 谁用的是 LRC
  3. k+m 具体是多少

这些当然重要。

但真实世界里,通常更重要的是下面这些前置问题:

  1. 数据是大对象还是小文件
  2. 写入后是否频繁覆盖更新
  3. 元数据是否集中管理
  4. 是否需要 POSIX 风格的文件语义
  5. 是否允许冷热分层和后台转换
  6. 故障时最怕的是容量损失、尾延迟,还是跨网络修复流量

如果这些前提不同,那么即便大家都叫“EC”,最后的形态也会非常不一样。

可以先给三类系统一个非常粗略的画像:

  1. HDFS:更像“面向大文件和批处理”的条带化文件系统,愿意把 EC 放到冷数据、顺序访问和后台修复这类更可控场景里。
  2. Ceph:更像“统一对象存储内核”,既想要 EC 的容量收益,又要在对象、块、文件三个上层接口下维持尽量一致的分布式抽象,所以设计上更强调布局灵活性和池级策略。
  3. Azure LRC:更像“超大规模对象存储平台”的运维优化结果,它面对的是极端规模下的高频单节点故障,因此重点不是只看存储效率,而是优先把最常见修复流量降下来。

如果这三个画像先不建立起来,后面看具体实现时很容易只剩一堆特性清单。


2. 先做一张总表:三种路线到底在优化什么

维度 HDFS EC Ceph EC Azure LRC
主要数据模型 大文件、块存储、顺序访问 RADOS 对象,上层可做对象/块/文件 超大规模对象存储
典型优化目标 降低冷数据冗余成本 在统一分布式存储内核中提供更高容量效率 降低常见单故障修复带宽
对小随机覆盖写的态度 不擅长,倾向避免 可以支持,但要付出额外复杂度 更偏对象与分层后台处理
恢复关注点 NameNode 感知、机架感知重建 CRUSH 放置、回填与恢复调度 局部修复、跨网络流量控制
元数据策略 元数据集中且仍高度依赖副本 集群元数据与对象索引分层管理 控制平面与数据面大规模分离
EC 的主要代价 条带读写和恢复复杂度上升 小 IO、覆盖写、恢复编排更复杂 额外局部校验带来的存储开销

这张表最重要的作用,不是帮你背结论,而是提醒你:

不同系统的 EC 不是同一个设计模板拷过去,而是围绕不同瓶颈重新取舍。


3. HDFS:为什么它很适合把 EC 用在“冷的大文件”上

HDFS 的很多前提天然会影响它使用 EC 的方式。

3.1 HDFS 的基本世界观

HDFS 的经典前提是:

  1. 文件通常比较大
  2. 读写往往偏顺序
  3. append 和批处理很常见
  4. 元数据集中由 NameNode 管理
  5. DataNode 负责真实块存储

这个模型对副本很友好,对 EC 也并不冲突,但它会自然引导出一种使用方式:

不要让 EC 破坏 HDFS 原本擅长的大块、顺序、批处理路径。

3.2 HDFS 为什么不是一开始就全面 EC

因为 HDFS 最早的成功建立在“三副本 + 大块流式写入 + 简单可靠”的组合上。

这套模型的问题主要是容量成本,而不是写路径本身。

所以 HDFS 后来引入 EC 时,最自然的策略并不是“把所有块立刻改成 EC”,而是:

  1. 优先用于冷数据
  2. 优先用于写入完成、较少变更的数据
  3. 优先用于大文件

这是非常理性的。

因为一旦数据已经稳定:

  1. full-stripe write 更容易达成
  2. 小写入更新放大问题显著减轻
  3. 后台重建可以更接近纯吞吐问题

3.3 HDFS 的条带化块组模型

HDFS 做 EC 时,不再只是“一个逻辑块对应几个完整副本”,而是把一个逻辑块组进一步拆成多个数据单元和校验单元。

你可以把它抽象成:

Logical block group
   |
   +-- data unit 0
   +-- data unit 1
   +-- data unit 2
   +-- ...
   +-- parity unit 0
   +-- parity unit 1
   +-- parity unit 2

对于 HDFS 来说,这样做有两个关键收益:

  1. 仍然保持“文件被切成大块”的整体心智模型
  2. 但块组内部已经能利用 EC 提升容量效率

3.4 HDFS 里的恢复意味着什么

HDFS 的恢复不只是解码函数调用,它还涉及:

  1. NameNode 发现缺失或低冗余块组
  2. 调度重建任务
  3. DataNode 从多个源节点拉取幸存单元
  4. 解码后写入新的目标节点
  5. 同时继续保证机架感知与副本放置约束

所以 HDFS 的关键不是“有没有 RS 库”,而是:

它能否在 NameNode 主导的元数据和块调度模型下,把条带化恢复做得足够稳定。

3.5 HDFS 的优势和代价分别是什么

优势:

  1. 对大文件和冷数据特别合适
  2. 容量收益直接
  3. 和已有块式文件系统模型结合自然

代价:

  1. 小文件和非顺序访问收益不明显
  2. 条带读写逻辑更复杂
  3. 恢复时读放大与网络开销明显高于副本

所以你会发现,HDFS 对 EC 的态度非常克制:

它并不试图让 EC 统治一切,而是把 EC 放在最能发挥价值的那层数据上。


4. Ceph:为什么它更像“可编排的 EC 平台”

如果说 HDFS 的 EC 很大程度上依附于“大文件 + 块组 + NameNode”的框架,那么 Ceph 的出发点明显不同。

4.1 Ceph 的基本世界观

Ceph 的内核是 RADOS,对外可以承载:

  1. 对象存储
  2. 块存储
  3. 文件系统

这意味着它在使用 EC 时,必须面对更复杂的需求组合:

  1. 不同上层接口的访问模式不同
  2. 对象粒度和块设备语义并不一样
  3. 恢复与回填逻辑需要和 CRUSH 放置体系协同

所以 Ceph 的 EC 更像一种 池级能力,而不是某个单一文件系统的专属优化。

4.2 Ceph 为什么适合把 EC 做成池策略

因为 Ceph 原本就很强调:

  1. 数据放置由策略描述
  2. 副本数、故障域、设备类可以按池划分
  3. 不同 workload 可以放到不同 pool

于是 EC 很自然地就变成:

  1. 某些 pool 走 replicated
  2. 某些 pool 走 erasure coded
  3. 元数据和小而热的数据仍然可以放在副本池
  4. 大而冷的数据放进 EC 池

这种设计和我们前三篇讲的“混合冗余”原则高度一致。

4.3 Ceph 真正难的地方不在 encode,而在覆盖写

Ceph 如果只做 append 风格的大对象写,EC 相对直接。

麻烦的是:

  1. 块设备会有覆盖写
  2. 文件系统上层会有小 IO
  3. 应用并不总能提供 full-stripe write

于是系统就必须面对:

  1. read-modify-write
  2. 小写放大
  3. 对齐与日志化
  4. 崩溃恢复一致性

这也是为什么在 Ceph 里,EC 不是简单“省空间开关”。

它需要和对象存储引擎、写入路径、校验更新机制一起看。

4.4 Ceph 为什么常把元数据和数据分开

不管是 CephFS 还是其他上层接口,元数据通常都比大对象数据更热、更小、更频繁变动。

所以一个非常自然的架构就是:

  1. 元数据继续放 replicated pool
  2. 大数据对象放 erasure-coded pool

这是 Ceph 能把 EC 实用化的关键之一。

因为它承认了一件事:

不是所有数据都该用 EC,只有最适合它的那部分才值得用。

4.5 Ceph 的恢复为什么和放置策略强绑定

Ceph 的另一个特点是数据放置高度依赖 CRUSH。

这意味着恢复时要同时满足两件事:

  1. 算法上能把缺块解出来
  2. 布局上能把新块重新放回符合故障域约束的位置

所以 Ceph 的恢复问题从来不是单机矩阵求逆,而是:

  1. 从哪些 OSD 读
  2. 读多少
  3. 写回哪个 OSD
  4. 是否会造成热点
  5. 回填和前台 IO 怎么抢资源

这也是 Ceph 的 EC 特别能体现“系统工程味道”的地方。

4.6 Ceph 的优势和代价分别是什么

优势:

  1. 池级策略灵活,适合混合部署
  2. 与对象模型天然契合,适合大对象和容量敏感场景
  3. 能把故障域、设备类和数据池策略统一编排

代价:

  1. 覆盖写和小 IO 场景复杂度高
  2. 恢复与回填调度容易成为性能瓶颈
  3. 上层接口差异使得“同一套 EC”并不等价于同样的体验

如果用一句话概括 Ceph 的思路,大概是:

它不是先问“哪种码最优”,而是先问“在一个统一分布式存储内核里,哪些池值得承受 EC 的复杂度”。


5. Azure LRC:为什么它优先优化“最常见故障的修复流量”

Azure LRC 经常被拿来当作一个很好的反例,说明现实系统并不会只追求纯 MDS 最优。

5.1 它面对的问题是什么

在超大规模对象存储里,最常见、最频繁、最影响运营成本的,往往不是“大面积同时丢很多块”,而是:

  1. 单节点故障
  2. 单盘故障
  3. 某些局部不可达
  4. 需要持续不断做修复,但又不能把跨网络带宽打满

如果你用纯 RS,每次单块修复都要跨较大范围读很多块,那么在超大规模平台上,这会变成持续性的网络压力。

5.2 LRC 的动机在 Azure 场景里特别强

LRC 的核心思路我们在前一篇已经讲过:

  1. 除了全局校验,再加局部校验
  2. 让高频单故障优先在局部完成修复
  3. 从而减少平均修复带宽和修复时延

这对超大规模云存储尤其重要。

因为在这种规模下,系统真正追求的不是“偶尔一次恢复理论最优”,而是:

每天都在发生的大量小恢复,必须足够便宜。

5.3 它牺牲了什么

LRC 不是没有代价。

它通常要比纯 MDS RS 多引入一部分局部校验信息,也就意味着:

  1. 存储效率略有回退
  2. 编码布局更复杂
  3. 放置和恢复逻辑也更复杂

但 Azure 这类系统愿意接受这个代价,因为它换来的收益非常大:

  1. 常见单故障修复流量更低
  2. 重建时间更短
  3. 对前台业务冲击更小
  4. 整个平台的长期运营成本更可控

5.4 Azure LRC 代表的真正思想是什么

不是“LRC 比 RS 高级”,而是:

在极端规模下,平均修复成本往往比纸面最优冗余率更重要。

这是一种非常工程化、也非常云平台化的思路。

它更接近这样的问题:

  1. 一天要修多少块
  2. 这些修复会打掉多少东西向流量
  3. 每次节点故障会不会触发大范围级联拥塞
  4. 平台是否能把最常见修复约束在更局部范围内

如果你理解了这一点,就会明白为什么 LRC 在大规模对象存储里这么有吸引力。


6. 三个系统真正的分歧,不在“会不会用 RS”

很多人做系统对比时,喜欢把焦点放在:

  1. HDFS 用了什么 policy
  2. Ceph 用了什么 plugin
  3. Azure LRC 具体几组 local parity

这些当然有意义,但更本质的分歧其实在别处。

6.1 HDFS 在意的是“别破坏大文件和顺序 IO 世界观”

所以它会倾向于:

  1. 用条带化块组管理 EC
  2. 把 EC 放到冷数据
  3. 继续保持比较清晰的顺序式数据面

6.2 Ceph 在意的是“统一内核下的多池编排能力”

所以它会倾向于:

  1. 通过 pool 策略把 replicated 和 EC 并存
  2. 让上层接口按 workload 选池
  3. 把故障域和回填调度纳入统一控制面

6.3 Azure LRC 在意的是“最常见修复必须足够便宜”

所以它会倾向于:

  1. 接受一点额外冗余
  2. 换低修复带宽
  3. 把局部修复效率作为平台级目标

这三个取向没有谁绝对更好,它们只是分别对准了不同的主瓶颈。


7. 从这三者身上,可以抽出哪些通用经验

如果你不是在做 HDFS、Ceph 或 Azure 本身,而是在设计自己的分布式存储,那么这三种路线至少给出七条很稳的经验。

7.1 不要先问“什么码最先进”,先问“什么故障最常见”

如果系统最常见的是单节点故障,那么修复带宽就非常关键。

如果系统最常见的是小随机覆盖写,那么更新放大更关键。

7.2 不要假设所有数据都适合同一种冗余模型

元数据、热数据、冷数据、大对象、小对象,通常应该分开看。

7.3 只看存储开销会误导选型

真正要看的往往是总成本:

  1. 容量成本
  2. 网络成本
  3. CPU 成本
  4. 运维复杂度
  5. 恢复窗口风险

7.4 故障域设计和码本身同等重要

如果放置策略做错,再漂亮的 MDS 性质也落不到真实可靠性上。

7.5 后台重建是数据面的常态,不是异常路径

系统必须从一开始就把限流、优先级、热点规避和队列治理设计进去。

7.6 元数据往往仍然更适合副本

这几乎是现实系统里的高频结论,而不是偶然。

7.7 混合策略通常比纯粹策略更成熟

副本和 EC 共存,不代表架构不优雅,反而往往说明系统开始真正面对 workload 差异。


8. 如果把 Ceph、HDFS、Azure LRC 放到一张“取舍坐标图”里

可以粗略把它们放到下面这个坐标系里理解:

更偏在线路径简单 -------------------------------- 更偏容量效率 / 平台规模经济

HDFS(副本热数据)  ->  HDFS(冷数据EC)
Ceph replicated   ->  Ceph EC pool
纯 RS             ->  LRC / 局部修复优化

这张图不精确,但足够说明一件事:

EC 从来不是单点技术选择,而是一条从简单到高效、从低复杂度到高运营能力的连续谱。


9. 最后给一个更接近选型会议的结论

如果你在做系统设计评审,真正有价值的问题通常不是:

  1. Ceph 用了 EC,我们是不是也该上
  2. Azure 有 LRC,我们是不是直接抄
  3. HDFS 支持 EC,我们是不是应该默认开启

而应该是:

  1. 我们的数据以大对象还是小对象为主
  2. 覆盖写比例高不高
  3. 故障时最怕的是容量损失、业务延迟,还是修复带宽
  4. 是否具备冷热分层和后台整理能力
  5. 是否能运营复杂的重建与巡检体系

如果这些问题没有先答清楚,直接讨论“上哪种码”,大概率会把问题讨论反。

所以从 Ceph、HDFS 和 Azure LRC 身上学到的最重要一课,不是某个参数配置,而是这句看起来很朴素的话:

纠删码不是一个孤立算法选择,而是一个必须和数据模型、故障模型、资源模型、运维模型一起收敛的系统决定。

这也是为什么真正成熟的存储系统,几乎都不会只问“理论上最优是什么”,而会继续追问:

  1. 这个方案对我们的数据真的合适吗
  2. 故障时它会不会把系统拖慢
  3. 我们有能力把它长期运营好吗

只有这几个问题都能回答得比较扎实,EC 才会从一篇漂亮的技术文章,变成一个真正能在生产里活下去的设计。

如果你继续往下追,会发现很多最终决定体验的细节,其实不在码型名字上,而在实现路径本身:

  1. 有限域乘法到底怎么做快
  2. 为什么 SIMD 和查表优化那么关键
  3. full-stripe write 为什么常常决定写路径是否优雅
  4. 小写更新为什么总会把系统拉回 read-modify-write

这些问题我放到下一篇实现篇单独展开:

  1. 纠删码(实现篇):GF 运算、SIMD、full-stripe write 与小写更新路径
  2. 纠删码(源码篇):最小 RS encoder/decoder 与 read-modify-write 伪代码
  3. 纠删码(性能篇):benchmark、NUMA、线程模型与 repair 限流
  4. 纠删码(案例排障篇):repair 打满网络、degraded read 尾延迟飙升与 partial write 放大
  5. 纠删码(架构选型篇):什么时候该用三副本、RS、LRC,什么时候根本不该上 EC