京东 HDFS EC 应用解密

2021-04-02 12:13:17 浏览数 (1)

Tech

导读

为了实现降本增效,京东HDFS 团队在 EC 功能的移植、测试与上线过程中,基于自身现状采取的一些措施并最终实现平滑上线。同时自研了一套数据生命周期管理系统,对热温冷数据进行自动化管理。在研发落地过程中还构建了三维一体的数据校验机制,为 EC 数据的正确性提供了强有力的技术保障。

本文详细介绍在研发一个复杂系统时,如何基于实际情况进行取舍,并确立行动准则。在功能上线过程中,要保持对线上系统的敬畏,确保上线与回滚不会导致元数据损坏。此外,要深刻认识系统的核心职责,对于存储系统务必加强技术保障,确保数据的安全与可靠,不能丢不能错。

数据作为京东数智战略的重要资产,随着业务的持续增长,大数据平台存储的数据量已突破 EB 级别,并且还在以每天将近 PB 的速度持续增长。传统的方法是分析数据的使用频率,把数据分为热温冷三类数据。对于温冷数据,使用压缩率更高的算法,来降低存储成本。但是无法突破三副本存储策略的固有属性:需要存储三份完整的数据块。

EC 数据相比副本模式如何提升存储效能

Hadoop 分布式存储引擎(HDFS)在 3.0 版本中发布了 EC inside HDFS 重要特性。该 EC(Erasure Coding) 是一种可通过计算降存储的技术。下面我以 HDFS 支持的一种 EC 存储策略 RS-3-2-1024k 为例简单介绍其原理。对于一个 200MB 的文件,会把数据按 1MB(1024KB) 一个条带(striped)进行切分,每切分出 3 个 1MB 的数据块条带(data striped,记为 d1-1,d2-1,d3-1),就用这 3 个数据块条带计算出两个 1MB 的校验块条带(parity striped,记为 p1-1, p2-1),依次循环处理,最后的 2MB 数据只能构成 2 个数据块条带(d1-67,d2-67),EC 算法会构建一个全零的假数据块(d3-67)计算出最后两个校验块条带(p1-67,p2-67)。实际存储在 DN 上的 5(3 个数据块加 2 个校验块) 个 EC 数据块分别由(d1-1..d1-67, d2-1..d2-67, d3-1..d3-66, p1-1..p1-67, p2-1..p2-67)组合成的数据块。如果丢失其中任意两个数据块,都可以使用剩下的 3 个块算出丢失的两个块。

一个 200MB 的文件按三副本方式存储,需要在不同 DN 上存放 3 份完整的数据块,消耗600MB的存储空间。而 RS-3-2-1024k只需要 334MB的存储空间,就能保证数据的冗余度,并且存储空间降低了 45%。因此,我们可以借助 EC inside HDFS 技术进一步降低数据存储成本,提高存储效率。

要把 EC 技术应用到生产环境,需要对我们的生产系统进行改造升级。首先,需要让生产代码支持EC 功能,我将在第一部分分析我们采用升级还是移植的权衡,以及遵循的原则。然后,介绍我们在改造过程中,通过哪些方法和技术来保障项目质量的。改造过程中涉及元数据方面的改动,因此上线需要格外注意,否则会导致数据难以恢复,在第三部分我会介绍线上系统的平滑升级方法与回滚的可行性。

我们运用 EC 的目的是存储温冷数据。对于热温冷数据,我们需要一套方案让数据在这三种场景中流转。我会在第四部分解密我们的数据生命周期管理系统。

作为数据存储平台,我们的首要职责是保证数据的安全可靠性,不能把用户的数据弄丢了,也不能把数据存错了。特别是 EC 这种基于计算的存储技术,尤为需要注意数据的完整性。在最后一部分会阐述我们在 EC 数据完整性方面所做的努力。

01

升级 or 移植

我们从 2015 年开始基于社区 2.7.1 版本,构建京东大数据生态系统,作为大数据平台的最底层基础系统,服务着成百上千个业务。

HDFS EC 是 3.0 版本正式发布的一个重要特性,为了支持 EC 做了大量的修改,接口也发生了较大变化。此外 3.0 版本还在一直迭代,存在不少缺陷,正式上线前还需要解决一些额外的接口兼容问题,会影响项目进度。再加上这几年我们基于 2.7.1 版本,做了大量的功能开发与性能优化,才能支持当前单集群万台规模的存储集群。因此我们采用移植 EC的方案。

社区EC相关的两个父任务:HDFS-7285、HDFS-8031 以及相关的 bugfix,加起来差不多300多个patch。同时,我们的移植目标版本是2.7.1,而patch是相对3.0的。如果围绕patch进行移植,我们要把3.0 相对2.7.1相关的前驱patch先移植过来,而这带来的工作量是巨大并且无法估量的。

面对如此复杂的存储系统,在开始移植前,需要确立一套移植方案和移植原则:

  • 按功能模块移植代码。
  • 移植过程中,尽可能的保持社区代码原有样式,以便于后续apply patch。
  • 对移植过程没有任何帮助的代码,不移植。
  • 对于接口,优先移植,而且与社区保持一致。
  • 测试用例必须移植并跑通。
  • 对于目前不移植或者为了简化移植工作而去掉的代码,一定不能影响现有场景的功能,并用TODO标识未来会修改。

参与移植工作的开发人员都是非常有经验的,对HDFS的整体架构比较清楚,才能保证移植的效率。经过一个多月的努力,完成了移植工作并跑通所有HDFS相关测试用例。

02

项目质量保障

这么庞大的工程,如果仅靠单元测试来验证,显然大家是不放心的,所以在启动 EC 项目时,QA 团队就参加进来,展开了自动化集成测试工作,主要目的是保证:

  • 移植工作不能改变原有接口和命令行语义,甚至接口和命令行的返回信息也要确保与 2.7.1 版本一致。
  • 一般的集群运维操作能够正常进行,比如 DN 节点上下线,NN 升级与回退。
  • 在破坏性测试中,集群的健壮性不受影响,比如磁盘故障,网络异常,数据损坏。
  • 验证集群兼容性,NN/DN/JN 逐步升级和回退不能影响集群的服务能力。
  • 校验数据正确性,在测试过程中要保证 EC 数据没有被损坏,丢块后重建的数据是正确的。
  • 性能对比测试,引入 EC 功能后,通过压测对比,确保集群,特别是 NN,性能不受影响。

为了适应千变万化的测试需求,我们定制了一套自动化测试系统。运用 ansible 编写了集群搭建系统,实现组件(NN/DN/JN),操作(安装、卸载、启动、停止、配置、切换、初始化),安装包,主机,配置修改等的参数化。测试人员能够灵活组合各种参数操作测试集群,实现测试目的。

在功能冒烟测试和回归测试中,使用 pytest 编写了 HDFS 测试框架和接口用例,以及数据校验用例等。方便构建测试数据集,按需执行测试用例,搜集测试用例的输出并对比历史数据,获取集群 JMX 指标验证正确性。

通过集成 Jenkins/Docker/makefile 等工具,贯通从开发人员提交代码,到自动化编译,到部署测试集群,再到冒烟测试并返回测试结果,一套完整的自动化测试流程。

03

升级与回滚

完成功能开发,测试也做的差不多之后,接下来就该上线了。但是上线不是换个包重启下就完事了,否则集群很可能由于一些故障和兼容性问题,导致数据损坏。因此上线要支持回退,还要在升级YARN、客户端等生态系统后,能写EC文件,同时集群还能像以前一样工作,尽可能不影响用户的使用习惯。下面我详细介绍一下 NameNode(NN) 和 DataNode(DN) 的升级过程。

为了支撑不停服务升级,就需要依赖 HDFS 集群的高可用(HA)特性。在两台 NN(Active/Standby) 滚动升级或回滚过程中,首先需要识别出 Active/Standby间的元数据兼容问题,然后逐个进行兼容性改造。

如 Editlog文件和 AddCloseOp方法中为表示当前版本支持 EC, LayoutVersion 被设置为 -64。如果使用 -64 写 Editlog会导致 NN回滚后不认识 -64 而起不来。我们的处理办法是在升级完成前,把LayoutVersion设置为老版本-63,但是内部代码可以识别 EC 元数据。还有 FsImage 文件中新增了ErasureCodingSection,需要先改造原有的老版本,保证老版本能处理新版本回传的 FsImage 文件,否则会导致 NPE 异常。

INode整体格式没有太大变化,只是重新定义了表示 replication的12bit字段。replication字段的高位为1时后边的11位表示EcPolicyId。而高位为0,后续的11位表示副本。这个变化其实没有修改布局,因此无需特殊处理。

平滑升级示意图

通过一些兼容性改造,我们发布了一个过渡性质的兼容性版本,可以识别 EC 相关元数据,但是不支持读写 EC 文件。这样可以确保每次上线都是可以回滚到前一个版本,但不要求能回滚到之前的历史版本。具体升级流程:

1. 第一次使用兼容版本升级一台standby状态的NN(LayoutVersion=-63), 并切换为active,测试运行一段时间,验证该节点服务正常。如果服务有预料之外的问题,或者性能有所下降,都可以随时进行 Active/Standby切换,并将Standby 节点回滚为老版本。

2. 第二次直接将standby节点的老版本升级到支持EC功能的新版(LayoutVersion=-64,也就是支持写EC文件),并切换为active。如果出现问题,都可以随时进行 Active/Standby切换,使用到第 1步中已经稳定运行一段时间的 NN(LayoutVersion=-63) 作为 Active 节点。

3. 第三次使用支持EC功能的新版替换第 1 步中升级的 NN。

至此,线上的服务就平滑由2.7.1升级为支持EC的HDFS了。整个过程经历3次升级,对外没有中止服务,外界无感知,并且整个过程是支持逐步回退。因此这个升级过程可以应用到生产环境。

滚动升级 DN 示意图

说完NN上线,接下来再讨论一下DN滚动上线。首先,解释下为什么要滚动升级DN(支持读写 EC块, 记为 DN(EC))。第一个很显然的原因是为了保证 HDFS的可用性,不影响用户读写副本文件;另外一个很重要的原因是想在线上验证 EC 功能的同时限制 EC 的故障域。

客户端调用addBlock选择DN去写块时,现有的选块策略无法给用户返回DN(EC),因此会出现写失败。通过在NN上新增ECClusterMap,构建一棵由 DN(EC) 组成的拓扑树。经过改造后,当客户端想要写 EC 文件,选块策略会从ECClusterMap中选取目标节点,就可以解决DN滚动升级过程中不支持 EC 写入的兼容性问题,同时也可以在线上环境中小范围验证 DN 中 EC 功能的稳定性。

04

数据生命周期管理

业界使用 DistCp或用 Hive 创建新表的方式把数据转换为 EC 存储。这个方案需要分别运维一套 YARN 集群和任务调度系统,并存在一些不足:

  • 在不修改 NN的前提下,任务调度系统无法实时转换新增数据。
  • 如果在数据拷贝过程中发生错误,只能重头开始转换。
  • 如果要对数据进行过滤,只支持对文件路径的过滤。
  • 如果把转换后的数据移到源目录时,没法进行原子交换,用户程序会在此间隙抛出找不到文件的异常。
  • 此外,HDFS 为目录和文件设置了用户组权限以及时间戳,对所有数据进行拷贝时,需要给拷贝程序赋超级权限,会引入一定的安全风险,现有方案也不能保证转换后的文件和原始文件属性保持一致。

我们实现了一套基于数据生命周期的温冷数据转存管理系统, 来解决现有方案的不足。在 NN 内部启动一个数据转换管理器,周期扫描待转换文件, 把转换任务封装成 FileConvertCommand,然后借助转换任务均衡器(ConvertTaskBalancer)选择一个 DN, 把FileConvertCommand加入到这个 DN 的待转换队列中。当 DN心跳过来时,会从待转换队列中领取一定数量的任务回去处理。详见下图。

EC 数据转换流程图

无论转换任务是否成功,DN都会通过心跳告知 NN 处理结果。当收到文件转换成功的响应,NN 读取原始文件的属性,包括用户组、时间戳、扩展属性等,设置转换后的 EC 文件。然后借助一个临时目录,对原始副本文件加读锁,并移动到临时目录,然后再把转换后的 EC 文件移动到原副本文件目录,实现副本文件和 EC 文件的原子性交换。如果这个过程没有异常发生,会在 checkpoint 中加一条操作日志。如果发生异常,把原始副本文件移回原目录,然后把该任务加入到待转换队列进行重做。

在转换执行过程中,允许人为中断。在转换功能异常的情况下,要确保文件不能被损坏, 服务不能中断。NN 侧通过 checkpoint记录操作日志,并存放在 HDFS 系统中,确保转换过程的幂等性。通过转换任务均衡器把转换任务均匀的分布到不同的 DN,避免热节点导致集群性能下降。DN 侧通过超时机制快速终止异常转换任务,释放资源处理下一个转换任务。

通过以上手段,我们可以让 HDFS 集群不依赖任何其它系统独立完成数据转换,并能对新增数据进行实时转换,利用容错机制确保数据不会被重复转换或漏转,提供了丰富的策略对待转换数据进行过滤,使用原子性操作为用户提供不间断服务,不修改转后的文件属性确保转换对于用户是透明的。

后来,我们对这套数据生命周期管理系统进行了抽象扩展,相当于在 HDFS 内部构建了一套调度系统。除了可以支持温冷数据转换,还可以对数据进行比对校验,甚至连数据清理工作也可以由这套系统进行调度。

05

全方位的数据完整性保障

HDFS-14768, HDFS-15186 还有 HDFS-15240是同行和我们在使用 EC 过程中发现导致数据损坏的情况,可见使用新兴的 EC 存在极大的数据损坏风险。正因如此,我们设计了文件级别和数据块级别的多重校验机制。

对于文件,在转换初期,我们使用 EC 与副本混合存储的策略,周期性的比对并恢复数据块,保障数据的完整性。通过在数据生命周期管理服务中,扩充 FileConvertCommand,支持数据验证模式。在上文提到的数据转换过程中,把副本文件转换为 EC 存储后,会立即使用 md5sum比对副本和 EC 文件内容,确保转换的正确性。

EC 文件的数据块组分为数据块与校验块两部分。客户端读取 EC 文件时,一般情况下只需要读取数据块部分。因此,在比对副本文件与 EC文件时,无法校验 EC 文件的校验块部分。为此,我们在文件内容比对过程中,还加入了数据块级别的验证。调用 BlockReaderFactory 获取 EC(RS-6-3-1024k) 块组中的数据块(d1-d6)与校验块(p1-p3)内容,再使用 EC 工具库中的 CodecUtil.createRawDecoder 随机选取 EC 块组中(d1,d2,d4,d5,p1,p3)计算其它块(d3’,d6’,p2’),再与前面读取到的(d3,d6,p2)块内容比对。

三阶段 EC 数据块校验

经过以上两轮比对,数据转换的结果可以保证完全无误。但是这种数据校验成本很高,我们不可能在很短的周期内,重新校验所有的数据文件。另外,副本文件不可能一直存留在集群中,否则使用EC 降存的意义也就不存在了。

为此,我们基于流式计算构建了一套实时的数据块级别的检测机制。具体流程是检测到数据块在节点间发生迁移(块重建,复制或是 balancer 都会导致块在节点间迁移),会计算新数据块的 md5sum, 并与旧数据块 md5sum 进行比对,当数据块 md5sum 发生变化后,通知集群维护人员进行处理。这套实时数据检测机制减少了数据校验成本,同时提高了时效性。

在这三维一体的数据校验与检测机制的保驾护航下,我们的 EC 功能成功上线到生产环境。经历了机房迁移、数据节点升级、618 和双十一的考验。到目前为止运用 EC 存储了上百PB 温冷数据,为公司节省上千台服务器成本。

06

总结与展望

在如此庞大的生产系统改造工程中,我们踩了不少坑。例如,HDFS 命令行输出发生变更,导致用户程序无法识别新增内容报错;修改Hadoop版本号后,一些 Hive 应用使用正则表达式解析 Hadoop版本号报错;由于接口变化导致 TeraSort 无法运行;要先改造老版本 NN,增加 BlockReportLeaseManager,否则新版本的 DN 无法向老版本的 NN 进行全量块汇报。这是我们趟过的比较典型的坑,主要是代码兼容,Hadoop 生态兼容的一些事项。

在实践过程中,我们还有一些很好的经验总结和大家分享下。移植代码时,一定要移植单元测试用例,可以帮助我们避免在移植过程中的疏忽导致代码少移漏移;另外,为了与社区代码的兼容,尽量使用一些设计模式,如装饰器、工厂模式、组合模式,进行代码的改造,方便日后引入社区新功能;还有一点非常重要,在改造 RPC 接口时,务必要保证 ProtoBuf 协议的兼容性,我们在新增自定义的字段时,会预留一部分坑位应对社区代码的扩展;对于存储系统,最重要的事情莫过于数据的完整性,大家可以参考上面第五部分内容,结合自己的场景进行优化。

Hadoop 社区为我们创造了优秀的存储系统。本着人人为我,我为人人的开源精神,在项目改造过程中,

我们向社区回馈了数十个 patch。比较典型的改进如下:

  • HDFS-14171,影响 NN 启动速度。
  • HDFS-14353, 修复 xmitsInProgress 指标异常。
  • HDFS-14523, 去除 NetworkTopology 多余锁。
  • HDFS-14849, DN 下线导致 EC 块无限复制。
  • HDFS-15240, 修复脏缓存导致数据重建错误。

接下来,为了让 EC 突破温冷数据的使用场景。我们准备在生产环境使用 native 方法加速 EC 数据的编解码效率,验证功能的稳定性,并向社区回馈我们的改造。目前的 EC inside HDFS 功能已经比较稳定了,但是问题还是存在的,我们将与社区一起努力建设更加稳定的 HDFS 存储系统。

0 人点赞