八年资深架构师解析HDFS之DataNode:启动过程二(含源码)

  在datanode启动的主流程中,启动了多种工作线程,包括InfoServer、JVMPauseMonitor、BPServiceActor等。其中,最重要的是BPServiceActor线程,真正代表datanode与namenode通信的正是BPServiceActor线程。

  BPServiceActor#run():

  

  此处说的“通信”包括与握手、注册(BPServiceActor#connectToNNAndHandshake)和后期循环提供服务(BPServiceActor#offerService(),本文暂不讨论)。

  启动过程中主要关注BPServiceActor#connectToNNAndHandshake():

  

  通过两次握手完成了datanode的注册,比较简单,不讨论。

  重点是BPOfferService#verifyAndSetNamespaceInfo():

  

  尽管是在BPServiceActor线程中,却试图以BPOfferService为单位初始化blockpool(包括内存与磁盘上的存储结构)。如果初始化成功,万事大吉,以后同BPOfferService的其他BPServiceActor线程发现BPOfferService#bpNSInfo !=null就不再初始化;而如果一个BPServiceActor线程初始化blockpool失败了,还可以由同BPOfferService的其他BPServiceActor线程重新尝试初始化。

  DataNode#initBlockPool():

  

  此时可知,blockpool是按照namespace逐个初始化的。这很必要,因为要支持Federation的话,就必须让多个namespace既能共用BlockManager提供的数据块存储服务,又能独立启动、关闭、升级、回滚等。

  在逐个初始化blockpool之前,先以datanode整体进行初始化。这一阶段操作的主要对象是DataStorage、StorageDirectory、FsDatasetImpl、FsVolumeList、FsVolumeImpl等;后面的FsDatasetImpl#addBlockPool操作的主要对象才会具体到各blockpool。具有1-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加群。在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加群。如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的可以加群。java架构群:582505643一起交流。

  DataNode#initStorage():

  

  初始化DataStorage:DataStorage#recoverTransitionRead()

  DataStorage#recoverTransitionRead():

  

  根据Javadoc,BlockPoolSliceStorage管理着该datanode上相同bpid的所有BlockPoolSlice。然而,猴子暂时没有发现这个类与升级外的操作有关(当然,启动也可能是由于升级重启),暂不深入。

  BlockPoolSlice详见后文FsVolumeImpl#addBlockPool。

  DataStorage#recoverTransitionRead()、BlockPoolSliceStorage#recoverTransitionRead()与数据节点恢复的关系非常大

  初始化FsDatasetImpl:FsDatasetFactory#newInstance()

  FsDatasetFactory#newInstance():

  

  FsDatasetImpl.()

  

  初始化DataStorage的过程中,将各${dfs.data.dir}放入了storage(即DataNode#storage)。对于datanode来说,${dfs.data.dir}/current目录就是要添加的卷FsVolumeImpl。

  FsDatasetImpl#initPeriodicScanners()(名为初始化,实为启动):

  

  初始化并启动DataBlockScanner、DirectoryScanners。

  命名为init或许是考虑到有可能禁用了数据块和目录的扫描器,导致经过FsDatasetImpl#initPeriodicScanners方法后,扫描器并没有启动。但仍然给人造成了误解。

  FsDatasetImpl#addBlockPool()操作的主要对象具体到了各blockpool,完成blockpool、current、rbw、tmp等目录的检查、恢复或初始化:

  

  FsVolumeList#addBlockPool(),并发向FsVolumeList中的所有卷添加blockpool(所有namespace共享所有卷):

  

  正如FsVolumeList#addBlockPool(),FsVolumeList封装了很多面向所有卷的操作。

  FsVolumeImpl#addBlockPool():

  

  BlockPoolSlice是blockpool在每个卷上的实际存在形式。所有卷上相同bpid的BlockPoolSlice组合成小blockpool(概念上即为BlockPoolSliceStorage),再将相关datanode(向同一个namespace汇报的datanode)上相同bpid的小blockpool组合起来,就构成了该namespace的blockpool。

  而FsVolumeImpl#bpSlices维护了bpid到BlockPoolSlice的映射。FsVolumeImpl通过该映射获取bpid对应的BlockPoolSlice,而BlockPoolSlice再反向借助FsDatasetImpl中的静态方法完成实际的文件操作(见后续文章中的写数据块过程)。

  回到BlockPoolSlice.:

  BlockPoolSlice(String bpid, FsVolumeImpl volume, File bpDir, Configuration conf) throws IOException { this.bpid=bpid; this.volume=volume; this.currentDir=new File(bpDir,

  DataStorage.STORAGE_DIR_CURRENT); this.finalizedDir=new File( currentDir,

  DataStorage.STORAGE_DIR_FINALIZED); this.lazypersistDir=new File(currentDir,

  DataStorage.STORAGE_DIR_LAZY_PERSIST); // 检查并创建finalized目录 if (!this.finalizedDir.exists()) { if (!this.finalizedDir.mkdirs()) { throw new IOException("Failed to mkdirs " + this.finalizedDir); } }

  this.deleteDuplicateReplicas=conf.getBoolean(

  DFSConfigKeys.DFS_DATANODE_DUPLICATE_REPLICA_DELETION,

  DFSConfigKeys.DFS_DATANODE_DUPLICATE_REPLICA_DELETION_DEFAULT); // 删除tmp目录。每次启动datanode都会删除tmp目录(并重建),重新协调数据块的一致性。 this.tmpDir=new File(bpDir,

  DataStorage.STORAGE_DIR_TMP); if (tmpDir.exists()) { FileUtil.fullyDelete(tmpDir); } // 检查并创建rbw目录 this.rbwDir=new File(currentDir,

  DataStorage.STORAGE_DIR_RBW); final boolean supportAppends=conf.getBoolean(

  DFSConfigKeys.DFS_SUPPORT_APPEND_KEY,

  DFSConfigKeys.DFS_SUPPORT_APPEND_DEFAULT); // 如果不支持append,那么同tmp一样,rbw里保存的必然是新写入的数据,可以在每次启动datanode时删除rbw目录,重新协调 if (rbwDir.exists() && !supportAppends) { FileUtil.fullyDelete(rbwDir); } // 如果支持append,待datanode启动后,有可能继续append数据,因此不能删除,等待进一步确定或恢复 if (!rbwDir.mkdirs()) { if (!rbwDir.isDirectory()) { throw new IOException("Mkdirs failed to create " + rbwDir.toString()); } } if (!tmpDir.mkdirs()) { if (!tmpDir.isDirectory()) { throw new IOException("Mkdirs failed to create " + tmpDir.toString()); } } // 启动dfsUsage的监控线程(详见对hadoop fs shell中df、du区别的总结) this.dfsUsage=new DU(bpDir, conf, loadDfsUsed()); this.dfsUsage.start(); ShutdownHookManager.get().addShutdownHook( new Runnable() { @Override public void run() { if (!dfsUsedSaved) { saveDfsUsed(); } } }, SHUTDOWN_HOOK_PRIORITY); }

  可知,每个blockpool目录下的存储结构是在构造BlockPoolSlice时初始化的。

  关于du的作用及优化:

  在linux系统上,该线程将定期通过du -sk命令统计各blockpool目录的占用情况,随着心跳汇报给namenode。

  执行linux命令需要从JVM继承fork出子进程,成本较高(尽管linux使用COW策略避免了对内存空间的完全copy)。为了加快datanode启动速度,此处允许使用之前缓存的dfsUsage值,该值保存在current目录下的dfsUsed文件中;缓存的dfsUsage会定期持久化到磁盘中;在虚拟机关闭时,也会将当前的dfsUsage值持久化。

  ReplicaMap#initBlockPool()

  ReplicaMap#initBlockPool(),初始化ReplicaMap中blockpool的映射

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值