写点什么

vivo 万台规模 HDFS 集群升级 HDFS 3.x 实践

  • 2022 年 5 月 16 日
  • 本文字数:11293 字

    阅读完需:约 37 分钟

vivo 互联网大数据团队-Lv Jia


Hadoop 3.x 的第一个稳定版本在 2017 年底就已经发布了,有很多重大的改进。


在 HDFS 方面,支持了 Erasure Coding、More than 2 NameNodes、Router-Based Federation、Standby NameNode Read、FairCallQueue、Intra-datanode balancer 等新特性。这些新特性在稳定性、性能、成本等多个方面带来诸多收益,我们打算将 HDFS 集群升级到 HDFS 3.x 版本。


本篇文章会介绍我们是如何将 CDH 5.14.4 HDFS 2.6.0 滚动升级到 HDP-3.1.4.0-315 HDFS 3.1.1 版本,是业界为数不多的从 CDH 集群滚动升级到 HDP 集群的案例。在升级中遇到哪些问题?这些问题是如何解决掉的?本篇文章具有非常高的参考借鉴价值。


一、 背景


vivo 离线数仓 Hadoop 集群基于 CDH 5.14.4 版本构建,CDH 5.14.4 Hadoop 版本:2.6.0+CDH 5.14.4+2785,是 Cloudera 公司基于 Apache Hadoop 2.6.0 版本打入了一些优化 patch 后的 Hadoop 发行版。


近几年随着 vivo 业务发展,数据爆炸式增长,离线数仓 HDFS 集群从一个扩展到十个,规模接近万台。随着 HDFS 集群规模的增长,当前版本的 HDFS 的一些痛点问题也暴露出来:

  • 在当前低版本的 HDFS,线上环境 NameNode 经常出现 RPC 性能问题,用户 Hive/Spark 离线任务也会因为 NameNode RPC 性能变慢导致任务延迟。

  • 一些 RPC 性能问题在 HDFS 3.x 版本均已修复,当前只能通过打入 HDFS 高版本 patch 的方式解决线上 NameNode RPC 性能问题。

  • 频繁的 patch 合并增加了 HDFS 代码维护的复杂度,每一个 patch 的上线都需要重启 NameNode 或者 DataNode,增加了 HDFS 集群的运维成本。

  • 线上 HDFS 集群使用 viewfs 对外提供服务,公司内部业务线众多,很多业务部门申请了独立的 HDFS 客户端访问离线数仓集群。当修改线上 HDFS 配置后,更新 HDFS 客户端配置是一件非常耗时且麻烦的事情。

  • HDFS 2.x 不支持 EC,冷数据无法使用 EC 来降低存储成本。


Hadoop 3.x 的第一个稳定版本在 2017 年底就已经发布了,有了很多重大的改进。在 HDFS 方面,支持了 Erasure Coding、More than 2 NameNodes、Router-Based Federation、Standby NameNode Read、FairCallQueue、Intra-datanode balancer 等新特性。HDFS 3.x 新特性在稳定性、性能、成本等多个方面带来诸多收益

  • HDFS Standby NameNode Read、FairCallQueue 新特性以及 HDFS 3.x NameNode RPC 优化 patch 能极大提升我们当前版本 HDFS 集群稳定性与 RPC 性能。

  • HDFS RBF 替代 viewfs,简化 HDFS 客户端配置更新流程,解决线上更新众多 HDFS 客户端配置的痛点问题。

  • HDFS EC 应用冷数据存储,降低存储成本。


基于以上痛点问题与收益,我们决定将离线数仓 HDFS 集群升级到 HDFS 3.x 版本。

二、 HDFS 升级版本选择


由于我们 Hadoop 集群基于 CDH 5.14.4 版本构建,我们首先考虑升级到 CDH 高版本。CDH 7 提供 HDFS 3.x 发行版,遗憾是 CDH 7 没有免费版,我们只能选择升级到 Apache 版本或者 Hortonworks 公司提供的 HDP 发行版。


由于 Apache Hadoop 没有提供管理工具,对于万台规模的 HDFS 集群,管理配置、分发配置极其不方便。因此,我们选择了 Hortonworks HDP 发行版,HDFS 管理工具选择 Ambari。


Hortonworks 提供的最新的稳定的免费的 Hadoop 发行版为 HDP-3.1.4.0-315 版本。Hadoop 版本为 Apache Hadoop 3.1.1 版本。

三、HDFS 升级方案制定

3.1 升级方案

HDFS 官方提供两种升级方案:Express 和 RollingUpgrade

  • Express 升级过程是停止现有 HDFS 服务,然后使用新版本 HDFS 启动服务,会影响线上业务正常运行。

  • RollingUpgrade 升级过程是滚动升级,不停服务,对用户无感知。


鉴于 HDFS 停服对业务影响较大,我们最终选择 RollingUpgrade 方案。

3.2 降级方案


RollingUpgrade 方案中, 有两种回退方式:Rollback 和 RollingDowngrade 

  • Rollback 会把 HDFS 版本连同数据状态回退到升级前的那一刻 ,会造成数据丢失。

  • RollingDowngrade 只回退 HDFS 版本,数据不受影响。


我们线上 HDFS 集群是不能容忍数据丢失的,我们最终选择 RollingDowngrade 的回退方案。

3.3  HDFS 客户端升级方案


线上 Spark、Hive、Flink 、OLAP 等计算组件重度依赖 HDFS Client,部分计算组件版本过低,需要升级到高版本才能支持 HDFS 3.x,升级 HDFS Client 有较高风险。


我们在测试环境经过多轮测试,验证了 HDFS 3.x 兼容 HDFS 2.x client 读写。


因此,我们本次 HDFS 升级只升级 NameNode、JournalNode、DataNode 组件,HDFS 2.x Client 等 YARN 升级后再升级。

3.4 HDFS 滚动升级步骤

RollingUpgrade 升级的操作流程在 Hadoop 官方升级文档中有介绍,概括起来大致步骤如下:

  1. JournalNode 升级,使用新版本依次重启 JournalNode。

  2. NameNode 升级准备,生成 rollback fsimage 文件。

  3. 使用新版本 Hadoop 重启 Standby NameNode,重启 ZKFC。

  4. NameNode HA 主从切换,使升级后的 NameNode 变成 Active 节点。

  5. 使用新版本 Hadoop 重启另一个 NameNode,重启 ZKFC。

  6. 升级 DataNode,使用新版本 Hadoop 滚动重启所有 DataNode 节点。

  7. 执行 Finalize,确认 HDFS 集群升级到新版本。

四、管理工具如何共存


HDFS 2.x 集群,HDFS、YARN、Hive、HBase 等组件,使用 CM 工具管理。由于只升级 HDFS,HDFS 3.x 使用 Ambari 管理,其它组件如 YARN、Hive 仍然使用 CM 管理。HDFS 2.x client 不升级,继续使用 CM 管理。Zookeeper 使用原 CM 部署的 ZK。


具体实现:CM Server 节点部署 Amari Server,CM Agent 节点部署 Ambari Agent。



如上图所示,使用 Ambari 工具在 master/slave 节点部署 HDFS 3.x NameNode/DataNode 组件,由于端口冲突,Ambari 部署的 HDFS 3.x 会启动失败,不会对线上 CM 部署的 HDFS 2.x 集群产生影响。


HDFS 升级开始后,master 节点停止 CM JN/ZKFC/NN,启动 Ambari JN/ZKFC/NN,slave 节点停止 CM DN,启动 Ambari DN。HDFS 升级的同时实现管理工具从 CM 切换到 Ambari。

五、HDFS 滚动升级降级过程中遇到的问题

5.1 HDFS 社区已修复的不兼容问题


HDFS 社区已修复滚动升级、降级过程中关键不兼容的问题。相关 issue 号为:HDFS-13596、 HDFS-14396、 HDFS-14831


HDFS-13596】: 修复 Active NamNode 升级后将 EC 相关的数据结构写入 EditLog 文件,导致 Standby NameNode 读取 EditLog 异常直接 Shutdown 的问题。


HDFS-14396】:修复 NameNode 升级到 HDFS 3.x 版本后,将 EC 相关的数据结构写入 Fsimage 文件,导致 NameNode 降级到 HDFS 2.x 版本识别 Fsimage 文件异常的问题。


HDFS-14831】:修复 NameNode 升级后对 StringTable 的修改导致 HDFS 降级后 Fsimage 不兼容问题。


我们升级的 HDP HDFS 版本引入了上述三个 issue 相关的代码。除此之外,我们在升级过程中还遇到了其它的不兼容问题:

5.2 JournalNode 升级出现 Unknown protocol

JournalNode 升级过程中,出现的问题:

Unknown protocol: org.apache.hadoop.hdfs.qjournal.protocol.InterQJournalProtocol

org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.ipc.RpcNoSuchProtocolException): Unknown protocol: org.apache.hadoop.hdfs.qjournal.protocol.InterQJournalProtocol        at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.getProtocolImpl(ProtobufRpcEngine.java:557)        at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:596)        at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:1073)        at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:2281)        at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:2277)        at java.security.AccessController.doPrivileged(Native Method)        at javax.security.auth.Subject.doAs(Subject.java:415)        at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1924)        at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2275)        at org.apache.hadoop.ipc.Client.getRpcResponse(Client.java:1498)        at org.apache.hadoop.ipc.Client.call(Client.java:1444)        at org.apache.hadoop.ipc.Client.call(Client.java:1354)        at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:228)        at org.apache.hadoop.ipc.ProtobufRpcEngine$Invoker.invoke(ProtobufRpcEngine.java:116)        at com.sun.proxy.$Proxy14.getEditLogManifestFromJournal(Unknown Source)        at org.apache.hadoop.hdfs.qjournal.protocolPB.InterQJournalProtocolTranslatorPB.getEditLogManifestFromJournal(InterQJournalProtocolTranslatorPB.java:75)        at org.apache.hadoop.hdfs.qjournal.server.JournalNodeSyncer.syncWithJournalAtIndex(JournalNodeSyncer.java:250)        at org.apache.hadoop.hdfs.qjournal.server.JournalNodeSyncer.syncJournals(JournalNodeSyncer.java:226)        at org.apache.hadoop.hdfs.qjournal.server.JournalNodeSyncer.lambda$startSyncJournalsDaemon$0(JournalNodeSyncer.java:186)        at java.lang.Thread.run(Thread.java:748)
复制代码


报错原因:HDFS 3.x 新增了 InterQJournalProtocol,新增加的 InterQJournalProtocol 用于 JournalNode 之间同步旧的 edits 数据。


HDFS-14942 对此问题进行了优化,日志级别从 ERROR 改成 DEBUG。此问题不影响升级,当三个 HDFS 2.x JN 全部升级为 HDFS 3.x JN 时,JN 之间能正常同步数据。

5.3 NameNode 升级 DatanodeProtocol.proto 不兼容


NameNode 升级后,DatanodeProtocol.proto 不兼容,导致 Datanode BlockReport 无法进行。


(1)HDFS 2.6.0 版本

DatanodeProtocol.proto

message HeartbeatResponseProto {  repeated DatanodeCommandProto cmds = 1; // Returned commands can be null  required NNHAStatusHeartbeatProto haStatus = 2;  optional RollingUpgradeStatusProto rollingUpgradeStatus = 3;  optional uint64 fullBlockReportLeaseId = 4 [ default = 0 ];  optional RollingUpgradeStatusProto rollingUpgradeStatusV2 = 5;}
复制代码


(2)HDFS 3.1.1 版本

DatanodeProtocol.proto

message HeartbeatResponseProto {  repeated DatanodeCommandProto cmds = 1; // Returned commands can be null  required NNHAStatusHeartbeatProto haStatus = 2;  optional RollingUpgradeStatusProto rollingUpgradeStatus = 3;  optional RollingUpgradeStatusProto rollingUpgradeStatusV2 = 4;  optional uint64 fullBlockReportLeaseId = 5 [ default = 0 ];}
复制代码


我们可以看到两个版本 HeartbeatResponseProto 的第 4、5 个参数位置调换了


这个问题的原因在于,Hadoop 3.1.1 版本 commit 了 HDFS-9788,用来解决 HDFS 升级时兼容低版本问题,而 HDFS 2.6.0 版本没有 commit ,导致了 DatanodeProtocol.proto 不兼容。


HDFS 升级过程中,不需要兼容低版本 HDFS,只需要兼容低版本 HDFS client。


因此,HDFS 3.x 不需要 HDFS-9788 兼容低版本的功能,我们在 Hadoop 3.1.1 版本回退了 HDFS-9788 的修改来保持和 HDFS 2.6.0 版本的 DatanodeProtocol.proto 兼容。

5.4  NameNode 升级 layoutVersion 不兼容


NameNode 升级后,NameNode layoutVersion 改变,导致 EditLog 不兼容,HDFS 3.x 降级到 HDFS 2.x NameNode 无法启动。

2021-04-12 20:15:39,571 ERROR org.apache.hadoop.hdfs.server.namenode.EditLogInputStream: caught exception initializing XXX:8480/getJournalid=test-53-39&segmentTxId=371054&storageInfo=-60%3A1589021536%3A0%3Acluster7org.apache.hadoop.hdfs.server.namenode.EditLogFileInputStream$LogHeaderCorruptException: Unexpected version of the file system log file: -64. Current version = -60.        at org.apache.hadoop.hdfs.server.namenode.EditLogFileInputStream.readLogVersion(EditLogFileInputStream.java:397)        at org.apache.hadoop.hdfs.server.namenode.EditLogFileInputStream.init(EditLogFileInputStream.java:146)        at org.apache.hadoop.hdfs.server.namenode.EditLogFileInputStream.nextopImpl(EditLogFileInputStream.java:192)        at org.apache.hadoop.hdfs.server.namenode.EditLogFileInputStream.nextop(EditLogFileInputStream.java:250)        at org.apache.hadoop.hdfs.server.namenode.EditLogInputStream.read0p(EditLogInputStream.java:85)        at org.apache.hadoop.hdfs.server.namenode.EditLogInputStream.skipUntil(EditLogInputStream.java:151)        at org.apache.hadoop.hdfs.server.namenode.RedundantEditLogInputStream.next0p(RedundantEditLogInputStream.java:178)        at org.apache.hadoop.hdfs.server.namenode.EditLogInputStream.readop(EditLogInputStream.java:85)        at org.apache.hadoop.hdfs.server.namenode.EditLogInputStream.skipUntil(EditLogInputStream.java:151)        at org.apache.hadoop.hdfs.server.namenode.RedundantEditLogInputStream.next0p(RedundantEditLogInputStream.java:178)        at org.apache.hadoop.hdfs.server.namenode.EditLogInputStream.read0p(EditLogInputStream.java:85)        at org.apache.hadoop.hdfs.server.namenode.FSEditLogLoader.LoadEditRecords(FSEditLogLoader.java:188)        at org.apache.hadoop.hdfs.server.namenode.FSEditLogLoader.LoadFSEdits(FSEditLogLoader.java:141)        at org.apache.hadoop.hdfs.server.namenode.FSImage.loadEdits(FSImage.java:903)        at org.apache.hadoop.hdfs.server.namenode.FSImage.LoadFSImage(FSImage.java:756)        at org.apache.hadoop.hdfs.server.namenode.FSImage.recoverTransitionRead(FSImage.java:324)        at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.LoadFSImage(FSNamesystem.java:1150)        at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.LoadFromDisk(FSNamesystem.java:797)        at org.apache.hadoop.hdfs.server.namenode.NameNode.LoadNamesystem (NameNode.java:614)        at org.apache.hadoop.hdfs.server.namenode.NameNode.initialize(NameNode.java:676)        at org.apache.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:844)        at org.apache.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:823)        at org.apache.hadoop.hdfs.server.namenode.NameNode.createNameNode (NameNode.java:1547)        at org.apache.hadoop.hdfs.server.namenode.NameNode.main(NameNode.java:1615)
复制代码


HDFS 2.6.0 升级到 HDFS 3.1.1,NameNode layoutVersion 值 -60 变更成 -64。要解决这个问题,首先搞清楚 NameNode layoutVersion 什么情况下会变更?


HDFS 版本升级引入新特性,NameNode layoutVersion 跟随新特性变更。Hadoop 官方升级文档指出,HDFS 滚动升级过程中要禁用新特性,保证升级过程中 layoutVersion 不变,升级后的 HDFS 3.x 版本才能回退到 HDFS 2.x 版本。


接下来,找出 HDFS 2.6.0 升级到 HDFS 3.1.1 引入了哪一个新特性导致 namenode layoutVersion 变更?查看 HDFS-5223HDFS-8432HDFS-3107相关 issue,HDFS 2.7.0 版本引入了 truncate 功能,NameNode layoutVersion 变成 -61。查看 HDFS 3.x 版本 NameNodeLayoutVersion 代码:


NameNodeLayoutVersion

public enum Feature implements LayoutFeature {  ROLLING_UPGRADE(-55, -53, -55, "Support rolling upgrade", false),  EDITLOG_LENGTH(-56, -56, "Add length field to every edit log op"),  XATTRS(-57, -57, "Extended attributes"),  CREATE_OVERWRITE(-58, -58, "Use single editlog record for " +    "creating file with overwrite"),  XATTRS_NAMESPACE_EXT(-59, -59, "Increase number of xattr namespaces"),  BLOCK_STORAGE_POLICY(-60, -60, "Block Storage policy"),  TRUNCATE(-61, -61, "Truncate"),  APPEND_NEW_BLOCK(-62, -61, "Support appending to new block"),  QUOTA_BY_STORAGE_TYPE(-63, -61, "Support quota for specific storage types"),  ERASURE_CODING(-64, -61, "Support erasure coding");
复制代码


TRUNCATE、APPEND_NEW_BLOCK、QUOTA_BY_STORAGE_TYPE、ERASURE_CODING 四个 Feature 设置了 minCompatLV 为-61。


查看最终 NameNode layoutVersion 取值逻辑:


FSNamesystem

static int getEffectiveLayoutVersion(boolean isRollingUpgrade, int storageLV,    int minCompatLV, int currentLV) {  if (isRollingUpgrade) {    if (storageLV <= minCompatLV) {      // The prior layout version satisfies the minimum compatible layout      // version of the current software.  Keep reporting the prior layout      // as the effective one.  Downgrade is possible.      return storageLV;    }  }  // The current software cannot satisfy the layout version of the prior  // software.  Proceed with using the current layout version.  return currentLV;}
复制代码


getEffectiveLayoutVersion 获取最终生效的 layoutVersion,storageLV 是当前 HDFS 2.6.0 版本 layoutVersion -60,minCompatLV 是 -61,currentLV 是升级后的 HDFS 3.1.1 版本 layoutVersion -64。


从代码判断逻辑可以看出,HDFS 2.6.0 版本 layoutVersion -60 小于等于 minCompatLV 是 -61 不成立,因此,升级到 HDFS 3.1.1 版本后,namenode layoutVersion 的取值为 currentLV -64。


从上述代码分析可以看出,HDFS 2.7.0 版本引入了 truncate 功能后,HDFS 社区只支持 HDFS 3.x 降级到 HDFS 2.7 版本的 NameNode layoutVersion 是兼容的。


我们对 HDFS truncate 功能进行评估,结合业务场景分析,我们 vivo 内部离线分析暂时没有使用 HDFS truncate 功能的场景。基于此,我们修改了 HDFS 3.1.1 版本的 minCompatLV 为 -60,用来支持 HDFS 2.6.0 升级到 HDFS 3.1.1 版本后能够降级到 HDFS 2.6.0。


minCompatLV 修改为-60:


NameNodeLayoutVersion

public enum Feature implements LayoutFeature {  ROLLING_UPGRADE(-55, -53, -55, "Support rolling upgrade", false),  EDITLOG_LENGTH(-56, -56, "Add length field to every edit log op"),  XATTRS(-57, -57, "Extended attributes"),  CREATE_OVERWRITE(-58, -58, "Use single editlog record for " +    "creating file with overwrite"),  XATTRS_NAMESPACE_EXT(-59, -59, "Increase number of xattr namespaces"),  BLOCK_STORAGE_POLICY(-60, -60, "Block Storage policy"),  TRUNCATE(-61, -60, "Truncate"),  APPEND_NEW_BLOCK(-62, -60, "Support appending to new block"),  QUOTA_BY_STORAGE_TYPE(-63, -60, "Support quota for specific storage types"),  ERASURE_CODING(-64, -60, "Support erasure coding");
复制代码

5.5 DataNode 升级 layoutVersion 不兼容

DataNode 升级后,DataNode layoutVersion 不兼容,HDFS 3.x DataNode 降级到 HDFS 2.x DataNode 无法启动。

2021-04-19 10:41:01,144 WARN org.apache.hadoop.hdfs.server.common.Storage: Failed to add storage directory [DISK]file:/data/dfs/dn/org.apache.hadoop.hdfs.server.common.IncorrectVersionException: Unexpected version of storage directory /data/dfs/dn. Reported: -57. Expecting = -56.        at org.apache.hadoop.hdfs.server.common.StorageInfo.setLayoutVersion(StorageInfo.java:178)        at org.apache.hadoop.hdfs.server.datanode.DataStorage.setFieldsFromProperties(DataStorage.java:665)        at org.apache.hadoop.hdfs.server.datanode.DataStorage.setFieldsFromProperties(DataStorage.java:657)        at org.apache.hadoop.hdfs.server.common.StorageInfo.readProperties(StorageInfo.java:232)        at org.apache.hadoop.hdfs.server.datanode.DataStorage.doTransition(DataStorage.java:759)        at org.apache.hadoop.hdfs.server.datanode.DataStorage.LoadStorageDirectory(DataStorage.java:302)        at org.apache.hadoop.hdfs.server.datanode.DataStorage.LoadDataStorage(DataStorage.java:418)        at org.apache.hadoop.hdfs.server.datanode.DataStorage.addStorageLocations(DataStorage.java:397)        at org.apache.hadoop.hdfs.server.datanode.DataStorage.recoverTransitionRead(DataStorage.java:575)        at org.apache.hadoop.hdfs.server.datanode.DataNode.initStorage(DataNode.java:1560)        at org.apache.hadoop.hdfs.server.datanode.DataNode.initBLockPool(DataNode.java:1520)        at org.apache.hadoop.hdfs.server.datanode.BPOfferService.verifyAndSetNamespaceInfo(BPOfferService.java:341)        at org.apache.hadoop.hdfs.server.datanode.BPServiceActor.connectToNNAndHandshake(BPServiceActor.java:219)        at org.apache.hadoop.hdfs.server.datanode.BPServiceActor.run(BPServiceActor.java:673)        at java.lang.Thread.run(Thread.java:748)
复制代码


HDFS 2.6.0 DataNode layoutVersion 是 -56,HDFS 3.1.1 DataNode layoutVersion 是 -57。


DataNode layoutVersion 改变的原因:Hadoop 社区自 HDFS-2.8.0  commit HDFS-8791 后,对 DataNode 的 Layout 进行了升级,DataNode Block Pool 数据块目录存储结构从 256 x 256 个目录变成了 32 x 32 个目录。目的是通过减少 DataNode 目录层级来优化 Du 操作引发的性能问题。


DataNode Layout 升级过程:

  1. rename 当前 current 目录,到 previous.tmp。

  2. 新建 current 目录,并且建立 hardlink 从 previous.tmp 到新 current 目录。

  3. rename 目录 previous.tmp 为 previous 目录。


Layout 升级流程图:

DN Layout 升级过程中存储目录结构:


hardlink 的 link 关联模式图:


查看 DataNodeLayoutVersion 代码,定义了 32 x 32 个目录结构的 layoutVersion 是-57。说明 DataNode Layout 升级需要改变 layoutVersion。


DataNodeLayoutVersion

public enum Feature implements LayoutFeature {  FIRST_LAYOUT(-55, -53, "First datanode layout", false),  BLOCKID_BASED_LAYOUT(-56,      "The block ID of a finalized block uniquely determines its position " +      "in the directory structure"),  BLOCKID_BASED_LAYOUT_32_by_32(-57,      "Identical to the block id based layout (-56) except it uses a smaller"      + " directory structure (32x32)");
复制代码


我们在测试环境进行 DataNode Layout 升级发现有如下问题:DataNode 创建新的 current 目录并建立 hardlink 的过程非常耗时,100 万 block 数的 DataNode 从 Layout 升级开始到对外提供读写服务需要 5 分钟。这对于我们接近万台 DataNode 的 HDFS 集群是不能接受的,难以在预定的升级时间窗口内完成 DataNode 的升级。


因此,我们在 HDFS 3.1.1 版本回退了 HDFS-8791,DataNode 不进行 Layout 升级。测试发现 100~200 万 block 数的 DataNode 升级只需要 90~180 秒,对比 Layout 升级时间大幅缩短。


回退了 HDFS-8791,DataNode Du 带来的性能问题怎么解决呢?


我们梳理了 HDFS 3.3.0 版本的 patch,发现了HDFS-14313 从内存中计算 DataNode 使用空间,不再使用 Du 操作, 完美的解决了 DataNode Du 性能问题。我们在升级后的 HDFS 3.1.1 版本打入HDFS-14313,解决了 DataNode 升级后 Du 操作带来的 io 性能问题。

5.6 DataNode Trash 目录处理


上图所示,DataNode 升级过程中,DataNode 在删除 Block 时,是不会真的将 Block 删除的,而是先将 Block 文件放到磁盘 BlockPool 目录下一个 trash 目录中,为了能够使用原来的 rollback_fsimage 恢复升级过程中删除的数据。我们集群磁盘的平均水位一直在 80%,本来就很紧张,升级期间 trash 中的大量 Block 文件会对集群稳定性造成很大威胁。


考虑到我们的方案回退方式是滚动降级而非 Rollback,并不会用到 trash 中的 Block。所以我们使用脚本定时对 trash 中的 Block 文件进行删除,这样可以大大减少 Datanode 上磁盘的存储压力。

5.7  其它问题

上述就是我们 HDFS 升级降级过程中遇到的所有不兼容问题。除了不兼容问题,我们还在升级的 HDP HDFS 3.1.1 版本引入了一些 NameNode RPC 优化 patch。


HDFS 2.6.0 版本 FoldedTreeSet 红黑树数据结构导致 NameNode 运行一段时间后 RPC 性能下降,集群出现大量 StaleDataNode,导致任务读取 block 块失败。Hadoop 3.4.0 HDFS-13671 修复了这个问题,将 FoldedTreeSet 回退为原来的 LightWeightResizableGSet 链表数据结构。我们也将HDFS-13671 patch 引入我们升级的 HDP HDFS 3.1.1 版本。


升级后HDFS-13671的优化效果:集群 StaleDataNode 数量大幅减少。

六、测试与上线


我们在 2021 年 3 月份启动离线数仓集群 HDFS 升级专项,在测试环境搭建了多套 HDFS 集群进行了 viewfs 模式下多轮 HDFS 升级、降级演练。不断的总结与完善升级方案,解决升级过程中遇到的问题。

6.1 全量组件 HDFS 客户端兼容性测试


在 HDFS 升级中只升级了 Server 端,HDFS Client 还是 HDFS 2.6.0 版本。因此,我们要保证业务通过 HDFS 2.6.0 Client 能正常读写 HDFS 3.1.1 集群。


我们在测试环境,搭建了线上环境类似的 HDFS 测试集群,联合计算组同事与业务部门,对 Hive、Spark、OLAP(kylin、presto、druid)、算法平台使用 HDFS 2.6.0 Client 读写 HDFS 3.1.1,模拟线上环境进行了全量业务的兼容性测试。确认 HDFS 2.6.0 Client 能正常读写 HDFS 3.1.1 集群,兼容性正常。

6.2 升级操作脚本化


我们严格梳理了 HDFS 升级降级的命令,梳理了每一步操作的风险与注意事项。通过 CM、Ambari API 启停 HDFS 服务。将这些操作都整理成 python 脚本,减少人为操作带来的风险。

6.3 升级点检


我们梳理了 HDFS 升级过程中的关键点检事项,确保 HDFS 升级过程中出现问题能第一时间发现,进行回退,降底对业务的影响。

6.4 正式升级


我们在测试环境中进行了多次 HDFS 升级降级演练,完成 HDFS 兼容性测试相关的工作,公司内部写了多篇 WIKI 文档进行记录。


确认测试环境 HDFS 升级降级没问题之后,我们开始了升级之路。


相关的具体里程碑上线过程如下:


  • 2021 年 3~4 月,梳理 HDFS 3.x 版本新特性与相关 patch,阅读 HDFS 滚动升级降级的源码,确定最终升级的 HDFS 3.x 版本。完成 HDFS 2.x 已有优化 patch 与 HDFS 3.x 高版本 patch 移植到升级的 HDFS 3.x 版本。

  • 2021 年 5~8 月,进行 HDFS 升级降级演练,全量 Hive、Spark、OLAP(kylin、presto、druid)兼容性测试,确定 HDFS 升级降级方案没有问题。

  • 2021 年 9 月,yarn 日志聚合 HDFS 集群(百台)升级到 HDP HDFS 3.1.1,期间修复日志聚合大量 ls 调用导致的 RPC 性能问题,业务未受到影响。

  • 2021 年 11 月,7 个离线数仓 HDFS 集群(5000 台左右)升级到 HDP HDFS 3.1.1,用户无感知,业务未受到影响。

  • 2022 年 1 月,完成离线数仓 HDFS 集群(10 个集群规模接近万台)升级到 HDP HDFS 3.1.1,用户无感知,业务未受到影响。


升级之后,我们对离线数仓各个集群进行了观察,目前 HDFS 服务运行正常。

七、总结


我们耗时一年时间将万台规模的离线数仓 HDFS 集群从 CDH HDFS 2.6.0 升级到了 HDP HDFS 3.1.1 版本,管理工具从 CM 成功切换到了 Ambari。


HDFS 升级过程漫长,但是收益是非常多的,HDFS 升级为后续 YARN、Hive/Spark、HBase 组件升级打下了基础。


在此基础上,我们可以继续做非常有意义的工作,持续在稳定性、性能、成本等多个方面深入探索,使用技术为公司创造可见的价值。


参考资料

  1. https://issues.apache.org/jira/browse/HDFS-13596

  2. https://issues.apache.org/jira/browse/HDFS-14396

  3. https://issues.apache.org/jira/browse/HDFS-14831

  4. https://issues.apache.org/jira/browse/HDFS-14942

  5. https://issues.apache.org/jira/browse/HDFS-9788

  6. https://issues.apache.org/jira/browse/HDFS-3107

  7. https://issues.apache.org/jira/browse/HDFS-8791

  8. https://issues.apache.org/jira/browse/HDFS-14313

  9. https://issues.apache.org/jira/browse/HDFS-13671

发布于: 刚刚阅读数: 2
用户头像

官方公众号:vivo互联网技术,ID:vivoVMIC 2020.07.10 加入

分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。

评论

发布
暂无评论
vivo 万台规模 HDFS 集群升级 HDFS 3.x 实践_大数据_vivo互联网技术_InfoQ写作社区