经典算法深度解析|纠删码(实战篇):Ceph、HDFS 与 Azure LRC 的实现取舍
前面三篇我们一直在讲两层内容:
- EC 在数学上为什么成立
- EC 在工程上为什么会贵
但真正把 EC 用到系统里以后,你会发现还有第三层现实:
同样都是纠删码,不同存储系统会做出完全不同的设计。
原因并不神秘。
因为系统要优化的目标本来就不同:
- 有的系统偏大对象和冷数据
- 有的系统需要兼顾覆盖写
- 有的系统最怕跨机架修复流量
- 有的系统最怕元数据膨胀
- 有的系统能接受更复杂的后台整理流程
- 有的系统则更强调在线路径简单和稳定
所以这篇不再重复推导 RS、MDS 或 LRC 的原理,而是专门回答一个更接地气的问题:
当 Ceph、HDFS 和 Azure 这种真实系统做 EC 时,它们到底在优先优化什么,又分别牺牲了什么?
先把系列文章列出来,方便前后跳转:
- 纠删码(一):从多副本到 Reed-Solomon、有限域与 MDS 本质
- 纠删码(二):故障恢复、更新放大、修复带宽与 LRC 演进
- 纠删码(三):条带布局、故障域、降级读与分布式存储里的工程落地
- 纠删码(实战篇):Ceph、HDFS 与 Azure LRC 的实现取舍
- 纠删码(实现篇):GF 运算、SIMD、full-stripe write 与小写更新路径
- 纠删码(源码篇):最小 RS encoder/decoder 与 read-modify-write 伪代码
- 纠删码(性能篇):benchmark、NUMA、线程模型与 repair 限流
- 纠删码(案例排障篇):repair 打满网络、degraded read 尾延迟飙升与 partial write 放大
- 纠删码(架构选型篇):什么时候该用三副本、RS、LRC,什么时候根本不该上 EC
1. 先给结论:系统之间的差异,首先来自“工作负载模型”
很多人刚接触 EC 选型时,会自然地问:
- 谁用的是 RS
- 谁用的是 LRC
k+m具体是多少
这些当然重要。
但真实世界里,通常更重要的是下面这些前置问题:
- 数据是大对象还是小文件
- 写入后是否频繁覆盖更新
- 元数据是否集中管理
- 是否需要 POSIX 风格的文件语义
- 是否允许冷热分层和后台转换
- 故障时最怕的是容量损失、尾延迟,还是跨网络修复流量
如果这些前提不同,那么即便大家都叫“EC”,最后的形态也会非常不一样。
可以先给三类系统一个非常粗略的画像:
- HDFS:更像“面向大文件和批处理”的条带化文件系统,愿意把 EC 放到冷数据、顺序访问和后台修复这类更可控场景里。
- Ceph:更像“统一对象存储内核”,既想要 EC 的容量收益,又要在对象、块、文件三个上层接口下维持尽量一致的分布式抽象,所以设计上更强调布局灵活性和池级策略。
- Azure LRC:更像“超大规模对象存储平台”的运维优化结果,它面对的是极端规模下的高频单节点故障,因此重点不是只看存储效率,而是优先把最常见修复流量降下来。
如果这三个画像先不建立起来,后面看具体实现时很容易只剩一堆特性清单。
2. 先做一张总表:三种路线到底在优化什么
| 维度 | HDFS EC | Ceph EC | Azure LRC |
|---|---|---|---|
| 主要数据模型 | 大文件、块存储、顺序访问 | RADOS 对象,上层可做对象/块/文件 | 超大规模对象存储 |
| 典型优化目标 | 降低冷数据冗余成本 | 在统一分布式存储内核中提供更高容量效率 | 降低常见单故障修复带宽 |
| 对小随机覆盖写的态度 | 不擅长,倾向避免 | 可以支持,但要付出额外复杂度 | 更偏对象与分层后台处理 |
| 恢复关注点 | NameNode 感知、机架感知重建 | CRUSH 放置、回填与恢复调度 | 局部修复、跨网络流量控制 |
| 元数据策略 | 元数据集中且仍高度依赖副本 | 集群元数据与对象索引分层管理 | 控制平面与数据面大规模分离 |
| EC 的主要代价 | 条带读写和恢复复杂度上升 | 小 IO、覆盖写、恢复编排更复杂 | 额外局部校验带来的存储开销 |
这张表最重要的作用,不是帮你背结论,而是提醒你:
不同系统的 EC 不是同一个设计模板拷过去,而是围绕不同瓶颈重新取舍。
3. HDFS:为什么它很适合把 EC 用在“冷的大文件”上
HDFS 的很多前提天然会影响它使用 EC 的方式。
3.1 HDFS 的基本世界观
HDFS 的经典前提是:
- 文件通常比较大
- 读写往往偏顺序
- append 和批处理很常见
- 元数据集中由 NameNode 管理
- DataNode 负责真实块存储
这个模型对副本很友好,对 EC 也并不冲突,但它会自然引导出一种使用方式:
不要让 EC 破坏 HDFS 原本擅长的大块、顺序、批处理路径。
3.2 HDFS 为什么不是一开始就全面 EC
因为 HDFS 最早的成功建立在“三副本 + 大块流式写入 + 简单可靠”的组合上。
这套模型的问题主要是容量成本,而不是写路径本身。
所以 HDFS 后来引入 EC 时,最自然的策略并不是“把所有块立刻改成 EC”,而是:
- 优先用于冷数据
- 优先用于写入完成、较少变更的数据
- 优先用于大文件
这是非常理性的。
因为一旦数据已经稳定:
- full-stripe write 更容易达成
- 小写入更新放大问题显著减轻
- 后台重建可以更接近纯吞吐问题
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 来说,这样做有两个关键收益:
- 仍然保持“文件被切成大块”的整体心智模型
- 但块组内部已经能利用 EC 提升容量效率
3.4 HDFS 里的恢复意味着什么
HDFS 的恢复不只是解码函数调用,它还涉及:
- NameNode 发现缺失或低冗余块组
- 调度重建任务
- DataNode 从多个源节点拉取幸存单元
- 解码后写入新的目标节点
- 同时继续保证机架感知与副本放置约束
所以 HDFS 的关键不是“有没有 RS 库”,而是:
它能否在 NameNode 主导的元数据和块调度模型下,把条带化恢复做得足够稳定。
3.5 HDFS 的优势和代价分别是什么
优势:
- 对大文件和冷数据特别合适
- 容量收益直接
- 和已有块式文件系统模型结合自然
代价:
- 小文件和非顺序访问收益不明显
- 条带读写逻辑更复杂
- 恢复时读放大与网络开销明显高于副本
所以你会发现,HDFS 对 EC 的态度非常克制:
它并不试图让 EC 统治一切,而是把 EC 放在最能发挥价值的那层数据上。
4. Ceph:为什么它更像“可编排的 EC 平台”
如果说 HDFS 的 EC 很大程度上依附于“大文件 + 块组 + NameNode”的框架,那么 Ceph 的出发点明显不同。
4.1 Ceph 的基本世界观
Ceph 的内核是 RADOS,对外可以承载:
- 对象存储
- 块存储
- 文件系统
这意味着它在使用 EC 时,必须面对更复杂的需求组合:
- 不同上层接口的访问模式不同
- 对象粒度和块设备语义并不一样
- 恢复与回填逻辑需要和 CRUSH 放置体系协同
所以 Ceph 的 EC 更像一种 池级能力,而不是某个单一文件系统的专属优化。
4.2 Ceph 为什么适合把 EC 做成池策略
因为 Ceph 原本就很强调:
- 数据放置由策略描述
- 副本数、故障域、设备类可以按池划分
- 不同 workload 可以放到不同 pool
于是 EC 很自然地就变成:
- 某些 pool 走 replicated
- 某些 pool 走 erasure coded
- 元数据和小而热的数据仍然可以放在副本池
- 大而冷的数据放进 EC 池
这种设计和我们前三篇讲的“混合冗余”原则高度一致。
4.3 Ceph 真正难的地方不在 encode,而在覆盖写
Ceph 如果只做 append 风格的大对象写,EC 相对直接。
麻烦的是:
- 块设备会有覆盖写
- 文件系统上层会有小 IO
- 应用并不总能提供 full-stripe write
于是系统就必须面对:
- read-modify-write
- 小写放大
- 对齐与日志化
- 崩溃恢复一致性
这也是为什么在 Ceph 里,EC 不是简单“省空间开关”。
它需要和对象存储引擎、写入路径、校验更新机制一起看。
4.4 Ceph 为什么常把元数据和数据分开
不管是 CephFS 还是其他上层接口,元数据通常都比大对象数据更热、更小、更频繁变动。
所以一个非常自然的架构就是:
- 元数据继续放 replicated pool
- 大数据对象放 erasure-coded pool
这是 Ceph 能把 EC 实用化的关键之一。
因为它承认了一件事:
不是所有数据都该用 EC,只有最适合它的那部分才值得用。
4.5 Ceph 的恢复为什么和放置策略强绑定
Ceph 的另一个特点是数据放置高度依赖 CRUSH。
这意味着恢复时要同时满足两件事:
- 算法上能把缺块解出来
- 布局上能把新块重新放回符合故障域约束的位置
所以 Ceph 的恢复问题从来不是单机矩阵求逆,而是:
- 从哪些 OSD 读
- 读多少
- 写回哪个 OSD
- 是否会造成热点
- 回填和前台 IO 怎么抢资源
这也是 Ceph 的 EC 特别能体现“系统工程味道”的地方。
4.6 Ceph 的优势和代价分别是什么
优势:
- 池级策略灵活,适合混合部署
- 与对象模型天然契合,适合大对象和容量敏感场景
- 能把故障域、设备类和数据池策略统一编排
代价:
- 覆盖写和小 IO 场景复杂度高
- 恢复与回填调度容易成为性能瓶颈
- 上层接口差异使得“同一套 EC”并不等价于同样的体验
如果用一句话概括 Ceph 的思路,大概是:
它不是先问“哪种码最优”,而是先问“在一个统一分布式存储内核里,哪些池值得承受 EC 的复杂度”。
5. Azure LRC:为什么它优先优化“最常见故障的修复流量”
Azure LRC 经常被拿来当作一个很好的反例,说明现实系统并不会只追求纯 MDS 最优。
5.1 它面对的问题是什么
在超大规模对象存储里,最常见、最频繁、最影响运营成本的,往往不是“大面积同时丢很多块”,而是:
- 单节点故障
- 单盘故障
- 某些局部不可达
- 需要持续不断做修复,但又不能把跨网络带宽打满
如果你用纯 RS,每次单块修复都要跨较大范围读很多块,那么在超大规模平台上,这会变成持续性的网络压力。
5.2 LRC 的动机在 Azure 场景里特别强
LRC 的核心思路我们在前一篇已经讲过:
- 除了全局校验,再加局部校验
- 让高频单故障优先在局部完成修复
- 从而减少平均修复带宽和修复时延
这对超大规模云存储尤其重要。
因为在这种规模下,系统真正追求的不是“偶尔一次恢复理论最优”,而是:
每天都在发生的大量小恢复,必须足够便宜。
5.3 它牺牲了什么
LRC 不是没有代价。
它通常要比纯 MDS RS 多引入一部分局部校验信息,也就意味着:
- 存储效率略有回退
- 编码布局更复杂
- 放置和恢复逻辑也更复杂
但 Azure 这类系统愿意接受这个代价,因为它换来的收益非常大:
- 常见单故障修复流量更低
- 重建时间更短
- 对前台业务冲击更小
- 整个平台的长期运营成本更可控
5.4 Azure LRC 代表的真正思想是什么
不是“LRC 比 RS 高级”,而是:
在极端规模下,平均修复成本往往比纸面最优冗余率更重要。
这是一种非常工程化、也非常云平台化的思路。
它更接近这样的问题:
- 一天要修多少块
- 这些修复会打掉多少东西向流量
- 每次节点故障会不会触发大范围级联拥塞
- 平台是否能把最常见修复约束在更局部范围内
如果你理解了这一点,就会明白为什么 LRC 在大规模对象存储里这么有吸引力。
6. 三个系统真正的分歧,不在“会不会用 RS”
很多人做系统对比时,喜欢把焦点放在:
- HDFS 用了什么 policy
- Ceph 用了什么 plugin
- Azure LRC 具体几组 local parity
这些当然有意义,但更本质的分歧其实在别处。
6.1 HDFS 在意的是“别破坏大文件和顺序 IO 世界观”
所以它会倾向于:
- 用条带化块组管理 EC
- 把 EC 放到冷数据
- 继续保持比较清晰的顺序式数据面
6.2 Ceph 在意的是“统一内核下的多池编排能力”
所以它会倾向于:
- 通过 pool 策略把 replicated 和 EC 并存
- 让上层接口按 workload 选池
- 把故障域和回填调度纳入统一控制面
6.3 Azure LRC 在意的是“最常见修复必须足够便宜”
所以它会倾向于:
- 接受一点额外冗余
- 换低修复带宽
- 把局部修复效率作为平台级目标
这三个取向没有谁绝对更好,它们只是分别对准了不同的主瓶颈。
7. 从这三者身上,可以抽出哪些通用经验
如果你不是在做 HDFS、Ceph 或 Azure 本身,而是在设计自己的分布式存储,那么这三种路线至少给出七条很稳的经验。
7.1 不要先问“什么码最先进”,先问“什么故障最常见”
如果系统最常见的是单节点故障,那么修复带宽就非常关键。
如果系统最常见的是小随机覆盖写,那么更新放大更关键。
7.2 不要假设所有数据都适合同一种冗余模型
元数据、热数据、冷数据、大对象、小对象,通常应该分开看。
7.3 只看存储开销会误导选型
真正要看的往往是总成本:
- 容量成本
- 网络成本
- CPU 成本
- 运维复杂度
- 恢复窗口风险
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. 最后给一个更接近选型会议的结论
如果你在做系统设计评审,真正有价值的问题通常不是:
- Ceph 用了 EC,我们是不是也该上
- Azure 有 LRC,我们是不是直接抄
- HDFS 支持 EC,我们是不是应该默认开启
而应该是:
- 我们的数据以大对象还是小对象为主
- 覆盖写比例高不高
- 故障时最怕的是容量损失、业务延迟,还是修复带宽
- 是否具备冷热分层和后台整理能力
- 是否能运营复杂的重建与巡检体系
如果这些问题没有先答清楚,直接讨论“上哪种码”,大概率会把问题讨论反。
所以从 Ceph、HDFS 和 Azure LRC 身上学到的最重要一课,不是某个参数配置,而是这句看起来很朴素的话:
纠删码不是一个孤立算法选择,而是一个必须和数据模型、故障模型、资源模型、运维模型一起收敛的系统决定。
这也是为什么真正成熟的存储系统,几乎都不会只问“理论上最优是什么”,而会继续追问:
- 这个方案对我们的数据真的合适吗
- 故障时它会不会把系统拖慢
- 我们有能力把它长期运营好吗
只有这几个问题都能回答得比较扎实,EC 才会从一篇漂亮的技术文章,变成一个真正能在生产里活下去的设计。
如果你继续往下追,会发现很多最终决定体验的细节,其实不在码型名字上,而在实现路径本身:
- 有限域乘法到底怎么做快
- 为什么 SIMD 和查表优化那么关键
- full-stripe write 为什么常常决定写路径是否优雅
- 小写更新为什么总会把系统拉回 read-modify-write
这些问题我放到下一篇实现篇单独展开: