
三副本的存储成本有多痛做大数据的人心里都有数。vivo这边业务线多用户行为日志、埋点数据、算法特征、订单归档哪个都在涨。机房里最贵的往往不是CPU和内存而是那些为了耐住万一而多出来的副本盘。所以当HDFS EC纠删码在Hadoop 3.x里成熟起来之后我们很快就盯上了它。把冷数据的冗余从三副本降到RS-6-3的1.5倍账面上省下来的空间非常可观。这篇文章把我们从评估、选型、迁移到上线后调优的完整过程写下来重点不是EC原理的堆砌而是那些真正影响成败的细节和坑。1. 三副本时代留下的成本账本EC落地的真实动因1.1 数据增长背后的存储账单先看看我们当时的处境。线上HDFS集群从几十台一路扩到上千台日均新增数据量大概在几百TB到PB级浮动碰上大促或者营销活动埋点日志会再冲一波。这种体量下三副本机制带来的成本问题已经不是有点心疼而是坐不住了。很多人潜意识里觉得三副本就是多买两块盘的问题但实际算下来不是这么回事。三副本意味着存储开销是逻辑数据量的3倍也就是100GB的业务数据在集群里实际占用300GB。我们线上大量归档类和特征类数据写入后一个月都不一定被读一次但你不能删——算法回溯分析、运营报表、合规审计都有可能用到。为了这以防万一每份数据都在磁盘上躺了三份这笔账怎么算都不划算。当时我们也评估过传统压缩方案。数据压缩确实能把单副本的物理占用压掉一大截但日志和特征类数据本身就是相对规整的结构化内容压缩比已经接近极限再往下压空间不大还增加了读写时的CPU解压开销。所以真正的问题是怎么在不牺牲可靠性的前提下把冗余倍数从3降到1.x。1.2 EC解决问题的本质用CPU置换空间而非简单压缩HDFS EC做的事情通俗点说就是把多放几份改成算几份校验。以我们最常用的RS-6-3为例把一个文件按条带切成6个数据单元再用Reed-Solomon编码算出3个校验单元一共9个单元分散落在不同的DataNode上。这9个单元里丢任意3个剩下的6个都能通过解码把原始数据完整算回来。空间账很好算6份数据 3份校验总开销只有原来的9/6 1.5倍对比三副本的3倍直接省一半空间。RS-10-4更极端10份数据 4份校验总开销1.4倍容错能力也能扛4个单元丢失。这里有必要把HDFS EC和磁盘阵列RAID的纠删码区分开。RAID的XOR或RS编码作用在单机多块盘之间对象是磁盘扇区HDFS的EC作用在文件系统逻辑层条带单元分布在整个集群的不同DataNode上天然还能扛住单节点故障这是两种完全不同的容错尺度。换句话说RAID防的是盘坏HDFS EC防的是机器坏、机架断电甚至批量磁盘故障。vivo选择EC而不是无限扩副本核心逻辑也在于此EC把耐故障能力从复制多份文件变成了数学计算冗余用CPU算力和一点网络开销换回大量存储空间。1.3 为什么不靠删数据或者迁移到对象存储解决有人会问空间不够了删数据不就行了我前面说了这些数据不是不想删是真的不能删。业务分析、算法训练、用户追溯随时可能翻出一两个月前甚至两三年前的数据。还有一层原因删数据的流程审批非常重业务方宁可让你扩容也不敢拍板删历史数据。另一个选项是把冷数据整体搬到对象存储。我们也认真评估过这个路线但vivo内部很多任务链路还是围绕HDFS生态转的Hive、Spark、Flink、Impala、Presto要接对象存储得改造数据源、权限体系、临时目录管理工程量和业务改动太大。相比之下HDFS EC是原生能力上层引擎只要走FileSystem API就天然兼容改造路径平滑太多。这也是最终选EC作为冷数据降本主路径的根本原因。2. 从评估到定调EC改造前的几个关键抉择2.1 纠删码策略RS-6-3还是RS-10-4EC策略的选型是第一步这个决定直接决定了后续的空间收益、容错能力和运维复杂度。Hadoop默认带了几个策略最常用的就是RS-6-3-1024k和RS-10-4-1024k。两者该怎么选我列一张实测对比表对比项RS-6-3-1024kRS-10-4-1024k存储开销150%140%容错能力任意3个单元丢失任意4个单元丢失恢复单单元需拉取节点数最多8个最多13个编码/解码CPU开销中高适合场景通用的冷数据、归档数据超大文件、极冷数据我们的选择是主力上RS-6-3RS-10-4只在少数超大文件的极冷目录试用。原因很现实RS-10-4虽然省了10个百分点的空间但恢复一个丢失单元时需要从更多节点拉取数据重建时的网络峰值和CPU开销明显上涨。对于HDFS这种可能会同时坏多块盘的大规模集群63的容错已经够扎实没必要为了多省那点空间把运维边界拉得太紧张。2.2 冷热数据识别哪些目录能进EC哪些不能碰EC不是银弹不是所有目录都能无脑切我们当时按数据特征做了一个明确的分类。适合进EC的目录通常具备这几个特征文件平均大小在几十MB以上、写入后极少修改、读取频率低、可以容忍写入变慢。举vivo这边几个实际例子离线用户特征快照每天全量覆盖写一次之后被稀疏扫描、历史订单归档超过90天的订单明细一个月可能被跑一次报表、各类业务日志的月分区回溯分析时才会打开。不适合进EC的目录也有明显共性小文件占比高、append写入频繁、实时链路会高频读取。比如Kafka落地的实时明细表Flink任务每一两分钟就会读一次最近分区这种目录用EC反而会把延迟和CPU开销转嫁给实时链路又比如Hive的临时表目录文件数量多且文件小EC的条带跨节点寻址效率不如副本。我们的落地方式不是靠人去判断每个目录而是按存储策略分层热数据目录继续走三副本冷数据目录打上EC的存储策略标记中间加一层定时任务把超过N天或满足特定分区的目录自动切换策略。这个机制在后面第5章会细说。2.3 条带大小与客户端兼容性测试EC策略里的1024k指的是cell大小每个条带单元的大小条带总大小是cell大小乘以数据单元数。默认的RS-6-3-1024k每个条带约6MB对vivo线上这些百MB到GB级的冷数据文件来说是完全合适的。如果你要处理的文件平均只有一两MB那cell大小可以适当调小比如创建RS-6-3-256k或者512k的自定义策略但cell太小会增加cell索引和元数据开销反而得不偿失。客户端兼容性是我们上线前花时间最多的一块。不要想当然认为HDFS 3.x支持EC客户端也一定没问题实际上要分两层看第一层是标准FileSystem API读写Hadoop 3.x的客户端通过DFSStripedOutputStream自动处理条带写入对上层应用透明。我们测试Hive on Tez、Spark读写EC目录SQL语义完全不变这层很稳。第二层是底层文件操作EC文件不支持append和truncate文件一旦落盘就不能追加写。我们排查了一圈线上任务发现有几个流式任务确实在用append往同一个文件里续写日志这种就必须在切换前改成按新文件归档。还有一个隐藏问题EC目录里的文件不能执行setReplication操作否则会直接报错相关的定时任务脚本也要提前扫一遍。2.4 应用侧的代码改造量评估这是管理层最爱问的一个问题改造量多大要不要停业务。我们如实评估后结论是纯读取类任务零改造写入类任务只要不涉及append也基本零改造个别任务要做小改动。整体上EC这个方案在应用侧的侵入性非常小这也是它能快速落地的关键原因。但零改造不等于零验证。我们专门拉了一张兼容性测试清单覆盖了高频的读写组件Hive 3.1.2、Spark 3.1.1、Flink 1.13、Impala 3.4、DataX每个都跑了基准测试和全链路SQL确认读路径和写路径都没有问题之后才放量。这一步花的时间不比选型少但非常值得。3. 数据迁移与业务平滑衔接的上线路线3.1 前置体检升级NameNode与DataNode的基础配置EC真正上线前先要给集群做一个全面的体检。这里最大的隐患在NameNode。EC模式下一个文件的block location数量比三副本模式多得多——三副本是3个locationEC(6,3)是9个location每个条带单元都分布在不同节点。文件数量不变的情况下NameNode的BlockInfo内存占用会明显上涨。我们实际迁移过程中观察到FsImage体积在EC目录超过一定规模后等比增加了30%~50%。所以前置动作有两件一是给NameNode堆内存留足余量我们直接把主备NN的JVM堆从32GB调到了48GB并把GC从CMS切成G1重点盯了Full GC频率二是DataNode的磁盘布局和网络带宽因为EC写要同时往多个节点写数据单节点带宽不够会拖慢整个条带写入。体检还包含集群版本检查。我们当时的版本是Hadoop 3.2.xEC已经算比较稳定了ISA-L本地加速库也能正常检测到。如果还在用Hadoop 2.x或者3.0、3.1这种早期版本不建议直接上EC升级版本带来的收益远大于迁移成本。3.2 分层迁移如何从最冷的目录逐步铺开我们总结出来的迁移节奏是试点一个最小目录验证全链路再切一个大目录观察性能最后才按分区粒度自动化铺开。千万不要一上来就想把所有冷数据都切到EC那是给自己找事故。第一步是给目标目录设置EC策略。命令很简单# 查看集群当前可用的EC策略 hdfs ec -listPolicies # 启用系统自带的RS-6-3-1024k策略 hdfs ec -enablePolicy -policy RS-6-3-1024k # 为目录设置EC策略后续新建文件默认走EC写入 hdfs ec -setPolicy -path /data/cold/2023 -policy RS-6-3-1024k # 确认目录当前生效的策略 hdfs ec -getPolicy -path /data/cold/2023注意一个关键点-setPolicy对已经存在的文件不会改变存储布局它只影响后续新写入的文件。所以存量数据要切到EC绕不开两步——拷贝过去再删掉原来。我们用DistCp做数据搬迁从三副本目录读到EC目录目标目录因为已经设置了EC策略新写入的文件就自动落成了EC布局。hadoop distcp -pb -m 40 -bandwidth 200 \ hdfs://nameservice/data/cold/2023 \ hdfs://nameservice/data/cold-ec/2023-bandwidth参数限的是每个map的带宽单位是MB/s这里给了200整体40个map并发峰值写入带宽约8GB/s。这个值不是拍脑袋定的而是结合迁移期间DataNode的带宽余量和在线业务峰值算出来的。宁可慢一点也不能把正常任务的网络打爆。3.3 迁移节奏控制限速、低峰和回滚机制迁移过程我们坚持了一个原则数据可以慢慢搬但线上任务绝对不能抖。实际操作有几个细节值得记下来迁移窗口选低峰期我们vivo线上任务凌晨两点到六点流量最低DistCp作业多数排在这个窗口。低峰期迁移除了避开读写竞争还有个好处出了问题有白天的时间去排查。分目录分批做按月份分区拆成多个批次每批次迁移完成、校验通过之后再启动下一批。批次之间留出至少一天的观察期。保留双写缓冲迁移完成并验证后我们不立即删除原三副本目录而是保留一到两周。这样一旦发现EC数据读取异常或者业务反馈问题能随时把读取源切回来数据安全兜底。容量释放确认最后删除原目录时确认EC数据已经被所有主要任务正常读取过才执行hdfs dfs -rm -skipTrash释放空间。回滚路径也要提前设计。EC目录如果中途出现严重问题回滚方案是把EC目录用DistCp拷回一个新的三副本目录再切换路径。这个动作的耗时取决于数据量所以我们在灰度期间每次只放一个中型目录就是为了把最坏回滚时间控制在几小时以内。3.4 迁移后的校验与清理迁移完不能只是跑一下文件数对得上就完事我们做了几层校验第一层用hdfs fsck检查EC目录的文件块是否健康看看有没有UNDER_CONSTRUCTION或者CORRUPT块。 第二层抽样跑业务查询挑几个业务方最常用的SQL在EC镜像目录上执行一遍对比结果行数和关键指标。 第三层对比源文件和目标文件的字节数、文件数、目录结构确保没有任何遗漏。hdfs fsck hdfs://nameservice/data/cold-ec/2023 -files -blocks -locations确认无误后把原来三副本目录的读取路径在任务配置里切换到EC目录。后续的capacity调度、Quota配额、冷热分层策略都按照EC目录重新设置一遍这步别省漏一个会导致后续写入落到错误策略上。4. 稳定运行期的性能账单CPU、内存与延迟的真实代价4.1 写入路径多出来的编码开销EC写数据不是像三副本那样复制分块而是要先对数据做编码运算再把数据块和校验块分发到不同DataNode。我们在实测中观察到在开启ISA-L加速的前提下RS-6-3的编码CPU开销大约占单核的10%~20%写吞吐会下降10%~30%具体取决于文件大小和数据分布。这个代价不是均匀分布在整个写入过程的。文件越大分摊到每个字节上的编码开销越低小文件则相反条带不满就要补零编码效率肉眼可见地变差。我们内部的经验是低于32MB的文件放进EC目录不划算既损失了写入性能又没有省下多少空间反而增加了块管理负担。写入变慢这件事对在线任务来说可能是个问题但对我们选定的冷数据完全不是问题——冷数据本来就是批量写入、写入频率极低、对写入时延不敏感。这也是什么场景适合EC的一个重要判断标准。4.2 读取路径什么时候会触发全条带解码和很多人直觉相反EC读文件在大多数情况下并不比副本慢因为读取并不总是需要全条带解码。HDFS的EC客户端只会读取客户端实际请求范围内的数据单元比如Spark要扫描某个文件的第3块数据它直接到对应的DataNode读那个cell就行了不需要汇总所有9个单元再解码。只有两种情况会触发解码一是某个数据单元所在的DataNode不可用需要读取同一stripe中的其他数据单元和校验单元来重建二是文件被损坏的单元超过阈值需要启动完整重建。所以我们观察到的读取性能绝大多数时候和副本模式基本持平甚至因为数据单元分布在更多节点上并行读的节点分散度更高大查询场景下还有一点吞吐优势。4.3 数据恢复的带宽真相这是EC落地后最容易被低估的一笔账。三副本模式下一个块坏了后台只要从任意一个存有副本的节点拉回整块数据就行EC(6,3)模式下恢复一个丢失单元理论上需要从其他8个节点分别读取对应的数据单元和校验单元再做解码计算。这意味着EC集群的数据恢复带宽消耗比三副本模式大了好几倍。我们在故障演习中做过对比同样一个120MB的块三副本恢复大约拉取120MB数据网络IO峰值约几百MB/sEC恢复到同样健康状态要拉取的数据量接近2倍而且要同时从多个节点拉峰值带宽更高。如果集群里同一时间坏了多个节点恢复任务并发上来可能会占满部分机架的上联带宽。所以EC集群最好预留一定的网络余量并严格控制同时恢复的任务数量。参数dfs.namenode.reconstruction.ec.concurrency.margin.percent就是干这个的默认值50表示最多把50%的重建余量用于EC恢复保持另外一半给正常读写我们实测下来配合业务低峰窗口效果还可以。4.4 监控指标与告警阈值设计EC跑稳之后监控是决定你敢不敢继续铺量的底气。我们最终沉淀了一套专属指标分三层第一层是EC容量层看EC策略占用的总容量、EC目录文件数、逻辑数据量。这几个指标判断EC的降本效果到底有没有兑现每周汇总一次。第二层是NameNode层看EC block数量、EC块缓存命中率、GC耗时。EC block数量上涨过快说明可能有小文件混进来了要马上查。第三层是DataNode层看编码CPU使用率、网络IO、重建任务排队数、重建失败数。特别是重建失败数一旦连续几个周期大于0基本可以断定有节点或网络异常。指标建议阈值告警级别EC重建任务排队数 50持续5分钟WarningDataNode EC编码CPU使用率 60%持续10分钟WarningEC坏块数 0CriticalNameNode Full GC时间 2s/次CriticalEC目录可用容量 20%Warning5. 大规模落地后的避坑手册5.1 小文件治理是EC的前提EC最恨小文件这是一条铁律。HDFS的EC条带至少跨多个节点如果一个文件只有一个小cell大小它仍然要占据跨DataNode的多个单元位置存储开销完全体现不出EC的优势甚至会比副本模式更差。更麻烦的是小文件因为很少能凑满一个stripe文件元数据和cell索引的膨胀会把NameNode的内存吃掉不少。我们在给某条业务线切换EC目录时就曾因为一个归档目录里混了大量几KB的元数据小文件导致EC block数量一夜之间暴涨NameNode堆内存告警。这事的教训是迁移前先统计目录的文件大小分布把小于50MB的文件先合并或者单独拎出来走副本模式别一股脑全塞进EC。5.2 副本与EC混合生命周期管理大规模集群里我们不会把整个冷数据区都一刀切成EC而是建立了一个生命周期自动降温机制。这可能是EC落地后收益最持久的一个模块建议所有做这块的都搞一套。思路很简单新数据先按三副本模式写入热路径保证在线查询的速度等数据过了高活跃期比如超过30天、60天用定时任务把对应分区目录自动切到EC策略。# 每天凌晨2点30分执行把30天前的日期分区目录切成EC策略 30 2 * * * hdfs ec -setPolicy -path /data/warehouse/$(date -d -30 day %Y%m%d) -policy RS-6-3-1024k但要注意-setPolicy只是改变了目录的策略标记已存在的文件不会自动变成EC布局所以这个命令的真正作用是把后续新增文件落成EC写入配合一个定时DistCp任务去搬存量才能完成最终的自动降温。我们在实践里是用一个调度脚本串起来标记策略 - 时间窗内迁移 - 校验 - 清理旧文件。5.3 重建风暴与并发控制EC集群最惊险的时刻不是新增EC文件而是出现节点故障后的大规模恢复。因为EC恢复需要同时拉很多节点多个数据单元同时丢失时恢复任务会像雪崩一样涌上来。我们遇到过一次机架上两三个节点同时下线EC重建队列瞬间顶到几千DataNode的CPU和带宽全部拉满在线读任务的延迟直接飙高。后来我们做了两件事一是在NameNode侧限制EC重建并发把dfs.namenode.reconstruction.ec.concurrency.margin.percent从默认50调到30给在线读写多留一些资源二是给重建任务设置优先级让更紧急的业务目录先恢复而不是按顺序全部挤在一起。另一个缓解重建风暴的手段是把恢复操作安排在低峰期执行。可以通过集群维护窗口配合触发或者使用脚本在低峰时段手动调整重建相关的动态参数白天再恢复默认。这个手动干预说起来土但在紧急情况下非常有效。5.4 常见的文件操作不兼容坑EC落地过程中我们整理了一份EC目录下不要做的事清单分享给大家不要对EC文件执行append会直接失败流式写入的任务必须改用新文件追加。不要对EC文件执行truncate同样不支持需要修改文件大小的逻辑要提前改造。不要对EC文件设置副本因子比如hdfs dfs -setrepEC块有自己的容错机制命令会报错。不要期望所有快照操作都完美虽然HDFS 3.2对EC目录的快照已经可以用但部分边界情况比如对EC文件做快照后再回滚我们测试时遇到过异常所以重要目录要提前验证。不要在开启EC策略的目录里直接跑rename/setReplication混用的脚本最好批量扫一遍存量cron脚本避免踩雷。这些坑多数都能通过事前巡检发现。我们的办法是梳理出所有离线任务的目标路径凡是会写入EC目录的任务挨个检查它们的文件操作模式有异常的直接改任务逻辑。5.5 一点闲话EC值得大规模上吗从vivo的落地结果来看答案是肯定的。EC让我们在相近的物理机数量下多承载了将近一倍的逻辑数据量存储采购和机柜成本省了一大块而CPU多出来的开销主要集中在写入和恢复场景对整体业务影响有限。但我也要泼一盆冷水EC不是万能的降本神器它本质上是用CPU和网络换空间。如果你集群的CPU已经常年顶着跑或者网络带宽本来就紧张直接上EC会让运维压力立刻变大。另外EC的运维门槛确实比三副本高团队需要对原理理解到位、监控和故障演练做到位否则出一次重建风暴省下的钱可能都不够填故障处理的精力。我的建议是分三步走先选一个低价值的冷数据目录试点跑通全套的迁移、监控、回滚流程再逐步扩大到归档类数据验证性能账单最后才考虑把温数据也纳入EC策略并配合生命周期管理做自动化。这条路我们走了一年多踩过不少坑但每一步都算数。