HDFS元数据更新流程源码浅析

1 . 服务端进行mkdir调用 fileSystem.mkdirs(new Path( 时,调用实现类Distributefilesystem的mkdirs方法

-> mkdirsInternal方法

权限检查操作

    FsPermission masked = permission.applyUMask(dfsClientConf.uMask);

核心

    return primitiveMkdir(src, masked, createParent);

  通过hadoop的RPC,调用服务端NameNodeRPCServer代码

      return namenode.mkdirs(src, absPermission, createParent);

    调用FSNameSystem创建目录的方法

        return namesystem.mkdirs(

1)检查是否是安全模式,安全模式下不允许创建目录

      checkNameNodeSafeMode("Cannot create directory " + src);

2)创建目录(此过程内进行了日志的持久化,类似下文的元数据持久化)

      auditStat = FSDirMkdirOp.mkdirs(this, src, permissions, createParent);

    创建HDFS管理目录树

FSDirectory fsd = fsn.getFSDirectory();

    解析创建目录的路径 /user/hive/warehouse/data/mytable

        src = fsd.resolvePath(pc, src, pathComponents);

   找到已存在的最后一个INode,此处即warehouse

        final INode lastINode = iip.getLastINode();

   将需要创建的目录提取出来

List<String> nonExisting = iip.getPath(existing.length(),

        iip.length() - existing.length());

   获取需要创建的目录的层级数

        int length = nonExisting.size();

   1( 是单层目录时

        if ((existing = createChildrenDirectories(fsd, existing,

            nonExisting.subList(length - 1, length), permissions)) == null) {

对于单层目录,其实和多层目录是一样的,只是createSingleDirectory方法在for循环调用时只执行了一次,并且进行logEdit时只有一个线程参与工作

   2( 需要创建的是多级目录时走此处

        if (length > 1) {

          List<String> ancestors = nonExisting.subList(0, length - 1);

          existing = createChildrenDirectories(fsd, existing, ancestors,

一个目录一个目录的创建(children即上文的存储目录的list集合)

    for (String component : children) {

    existing = createSingleDirectory(fsd, existing, component, perm);

   1  更新文件目录树,目录树存在于内存中,用FSNameSystem进行管理

    existing = unprotectedMkdir(fsd, fsd.allocateNewInodeId(), existing,

 封装成一个目录(因为此处是将数据存储到目录树内,所以要将数据先封装成一个目录)

    final INodeDirectory dir = new INodeDirectory(inodeId, name, permission,

        timestamp);

 将目录提交到文件目录树内对应应该新增节点的位置进行节点的添加

    INodesInPath iip = fsd.addLastINode(parent, dir, true);

获取父目录 (此处为 /warehouse)

    final INodeDirectory parent = existing.getINode(pos - 1).asDirectory();

在父目录下添加一个子节点

    added = parent.addChild(inode, true, existing.getLatestSnapshotId());

添加完成后,如果子节点列表还不为空,再继续添加一个节点(INode)

    children.add(-insertionPoint - 1, node);

  2 将元数据存储在磁盘上(在上文操作成功存储数据入内存后)

    fsd.getEditLog().logMkDir(cur, newNode);

创建日志对象  注意累计

    MkdirOp op = MkdirOp.getInstance(cache.get())

      .setInodeId(newNode.getId())

      .setPath(path)

      .setTimestamp(newNode.getModificationTime())

      .setPermissionStatus(permissions);

记录日志

    logEdit(op);

分段加锁与双缓冲机制(详细源码参照FSEditLog类)

进入方法,获取独一无二的事务id,将事务id封装进入上文传入的FSEditLogOp对象内,将op对象进行写入内存缓冲区的操作,

写操作完成后重置op对象。然后进行内存判断,没有写满则执行return操作结束并释放锁,让下一个线程继续写下一个目录的信息,直到内存缓冲区写满后,isAutoSyncScheduled标志量被设置为true,程序不再return,而是执行后续的logSync代码,【此时上文wait方法让新进入的写内存线程全部sleep】。进入logSync代码后进行内存缓冲区交换,并将标志量isAutoSyncScheduled设置为false,同时启动正在wait的写内存线程,然后将写磁盘标志量isSyncRunning修改为true,调用flush方法进行磁盘刷写。flush方法刷写完成前,如果新的内存缓冲区又被写满了,这时会进入再次进入logSync方法,此时因为isSyncRunning为true,进入的线程会进入wait状态,直到flush方法执行完成后重新将isSyncRunning标志量修改为false后才停止等待开始新一轮的刷写操作。

关于logEdit方法的write和logSync的flush:

    editLogStream.write(op);    (write方法

 1 . 前提条件:

           在初始化时initJournals方法里针对本地文件和journalnode上的远程文件会分别创建并添加FileJournalnodeManager和   

           QuorumJournalManager进入List集合journals内。

 2 . editLogStream的赋值:

   观察editLogStream的赋值流程发现,该流程实现与下列的闭包代码内

    mapJournalsAndReportErrors(new JournalClosure() {

         @Override

         public void apply(JournalAndStream jas) throws IOException {

           jas.startLogSegment(txId, layoutVersion);

         }

   而此闭包正是实现于添加进list内的上述两个manager

  查看startLogSegment方法在两个manager类内的实现可知,两个manager返回并赋值给editLogStream的两个流

      EditLogFileOutputStream(写本地)

      QuorumOutputStream(写journalnode)

3 . 分别查看write方法在上述两个流内的实现

结果在两个流内的实现都是同样的双缓冲方式实现的同一段代码【ps:瞬间觉得自己上面的研究成为了小丑(悲

   flushAndSync(durable);      (flush方法

 由上文可知,针对本地和journalnode存在两个流进行交互,此处的flush方法查看两个流内的实现可以发现:

对于本地,直接的使用了javaNIO的方式

对于journalnode,则是对于每一个journalnode都生成一个AsyncLogger与之一一对应,仍然也是采用javanio的方式进行数据传输,在传输代码的下面还有一个超时审核代码在等待上述写数据方法

      loggers.waitForWriteQuorum(qcall, writeTimeoutMs, "sendEdits");

  

两个调优

1FullGC导致的写数据去journalnode的流程出现 超时误判 使程序异常退出

解决方案:

FullGC发生时,超时审核线程自身执行暂停,但是其原本的计数逻辑仍然在运行,导致FullGC结束时超时审核线程误以为是journalnode无响应,从而误判该journalnode失联,从而触发异常处理机制使得程序异常退出。所以可以添加一个计时器组件,监控超时审核代码所在线程自身的执行时间,当这个时间过长时,就说明FullGC在此流程内发生了。这时修改超时审核代码的超时计算逻辑,将journalnode执行时间这个变量减去自定义的计时器记录的FullGC暂停的时间,从而避免FullGC导致的误判失联退出现象的发生

2内存被写死为512导致某些情况下内存过小而频繁刷写磁盘使得写内存线程长时间wait而让程序执行缓慢的问题         

解决方案:

将内存修改为一个变量,添加一个以枚举常量的方式赋值的代码,常量【DFSConfigKey内】则可以通过hdfs配置文件的方式进行按需求修改

3)元数据持久化

    getEditLog().logSync();

对于logSync的执行流程,上文已经有相关描述了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MisakiMei释光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值