HDFS写数据的流程源码浅析

1 . 创建流指向指定路径下文件

FSDataOutputStream fsDataOutputStream = fileSystem.create(new Path("/user.txt"));

最后调用到DistributeFileSystem实现类内的create方法

创建一个DFSOutputStream,进行初始化

       final DFSOutputStream dfsos = dfs.create(getPathName(p), permission

 1创建流系列操作

       final DFSOutputStream result = DFSOutputStream.newStreamForCreate(this,

    1向目录树添加InodeFile,记录元数据日志并添加契约【需要跟namenode服务端进行交互(NamenodeRPCServer)

    【此处是重试的代码结构【while (shouldRetry){ 】,因为这一段代码涉及到网络相关的RPC调用,所以多执行几次确保其不会因为突发网络问题而失败

          stat = dfsClient.namenode.create(src, masked, dfsClient.clientName,

       检查namenode的启动状态   

  checkNNStartup();

       创建文件的核心代码

          status = namesystem.startFile(src, perm, clientName, clientMachine,  -> startFileInt

等待加载元数据

    waitForLoadingFSImage();

重要

    toRemoveBlocks = startFileInternal(

1( 向文件目录树内添加INodeFile节点【dir就是FSDirectory

        iip = dir.addFile(parent.getKey(), parent.getValue(), permissions,

     创建一个INodeFile节点

  INodeFile newNode = newINodeFile(allocateNewInodeId(), permissions, modTime,

     添加一个文件

  newiip = addINode(existing, newNode);

后续即查询到父目录,添加节点,如果多级目录就继续添加等等步骤

2( 添加契约(lease翻译为契约)

      leaseManager.addLease(newNode.getFileUnderConstructionFeature()

          .getClientName(), src);

此处涉及添加契约以及续约,没有契约则添加契约,存在契约则修改心跳时间并保存而达成续约

     2初始化DataStreamer,是写数据流程的重要对象

      final DFSOutputStream out = new DFSOutputStream(dfsClient, src, stat,

          flag, progress, checksum, favoredNodes);

计算packet大小

    computePacketChunkSize(dfsClient.getConf().writePacketSize, bytesPerChecksum);

创建DataStreamer

    streamer = new DataStreamer(stat, null);

     3启动DataStreamer【run方法执行

      out.start();

  刚刚启动的DataStreamer线程没有数据,所以进入下面的while

(此时dataQueue.size() == 0)

            while ((!streamerClosed && !hasError && dfsClient.clientRunning

                && dataQueue.size() == 0 &&

                (stage != BlockConstructionStage.DATA_STREAMING ||

                 stage == BlockConstructionStage.DATA_STREAMING &&

                 now - lastPacket < dfsClient.getConf().socketTimeout/2)) || doSleep )

       dataqueue没有数据,就会阻塞在此处等待向队列添加数据

   dataQueue.wait(timeout);

 2开启续约

      beginFileLease(result.getFileId(), result);   -> put

创建了一个后台线程(客户端针对每一个文件都会创建一个后台线程)

        daemon = new Daemon(new Runnable() {

进行契约的续约

        LeaseRenewer.this.run(id);

间隔一定周期(一秒)进行一次检查如果当前时间减去上次续约的时间大于30秒,即有30秒未进行续约了,即执行续约操作

renew();

1( 获取namenode代理(跳转查看)

        namenode.renewLease(clientName);

调用leaseManager里的续约方法【后续删除旧契约,保存添加新契约并修改最后一次契约更新的时间值

      leaseManager.renewLease(holder);

    执行到此处,我们猜想一下可能存在的契约扫描机制,LeaseManager内肯定还有一个线程在周期性的检查契约的情况

于是查看LeaseManager的run

run内的检查契约代码

      needSync = checkLeases();

从存储契约的数据结构里拿出第一个(注:这个数据结构内重写了compareable方法,会将契约从老到新排序,第一个就是最老的)

leaseToCheck = sortedLeases.first();

对这个契约进行判断,如果未超时,则后续的系列契约均不可能超时,直接return

if (!leaseToCheck.expiredHardLimit()) {

    break;

}

最老契约过期,则移除该契约

removeLease(leaseToCheck, p);

2( 修改上一次的续约时间

        updateLastLeaseRenewal();

2 . 使用流向指定文件写入

fsDataOutputStream.write("fsfsfsfsfs".getBytes());

fsDataOutputStream对象其实是实现类HdfsDataOutputStream对象,该实现类没有重写write方法,故查看FSDataOutputStream父类内对write方法的实现

out.write(b);

对于out变量,经过对create方法的研究,我们发现out是被封装进入HdfsDataOutputStream里的DFSOutputStream对象,于是进入该对象进行查看

结果DFSOutputStream里面又没有write方法,于是进入其父类FSOutputSummer类内,找到对应的write方法

    方法内的写文件操作

flushBuffer();

    核心代码(此处是一个chunk一个chunk的去写的)     注:HDFS文件 -》 Dlock文件块128MB -》 packet64K == 127chunk -》 chunk 512 + chucksum 4 = 516

 writeChecksumChunks(buf, 0, lenToFlush);

      1 . 计算出chunk校验和(即上文4kb大小的chucksum)

    sum.calculateChunkedSums(b, off, len, checksum, 0);

      2 . 按照chunk的大小遍历数据

    for (int i = 0; i < len; i += sum.getBytesPerChecksum()) {

      3 . 逐个chunk的写数据(此方法重写于DFSOutputStream子类)

    writeChunk(b, off + i, chunkLen, checksum, ckOffset, getChecksumSize() );

写chunk【DFSOutputStream类内

     writeChunkImpl(b, offset, len, checksum, ckoff, cklen);

创建packet

      currentPacket = createPacket(packetSize, chunksPerPacket,

          bytesCurBlock, currentSeqno++, false);

向packet里写 chunk校验和 4 byte

    currentPacket.writeChecksum(checksum, ckoff, cklen);

向packet里写 一个chunk 512 byte

    currentPacket.writeData(b, offset, len);

累计的chunk数量  ->  packet 写满127个chunk就是一个 packet

    currentPacket.incNumChunks();

Block  ->  packet  Block  —>  128M 就写满了一个文件块

    bytesCurBlock += len;

两个条件

    1) 写满了一个packet(127 chunk)

    2) 写满了一个文件块Block(128M) (2048 packet)

       if (currentPacket.getNumChunks() == currentPacket.getMaxChunks() ||

           bytesCurBlock == blockSize) {

写满一个packet,将packet放入dataqueue队列

      waitAndQueueCurrentPacket();

  如果队列满了,就等待

     dataQueue.wait();

  把当前packet写入队列dataqueue

     queueCurrentPacket();

 向dataQueue内添加一个packet

      dataQueue.addLast(currentPacket);

在上文我们创建流的过程中启动了一个DataStreamer线程,DataStreamer监视dataQueue,在没有数据写入 的情况下一直wait,所以此处向DataQueue添加过packet后,就通过notifyAll唤醒了正在等待的线程。下一步要继续去查看DataStreamer类的run方法

dataQueue.notifyAll();

DataStreamer的run内:

从队伍里取出packet

     one = createHeartbeatPacket();

1) 建立数据管道

            /**

             * nextBlockOutputStream完成了两个事

             * 1 向namenode申请block

             * 2 建立数据管道

             * 3 管道的容错

             */

            // 进入nextBlockOutputStream()

            setPipeline(nextBlockOutputStream());

     1 存储有问题的主机的集合

        DatanodeInfo[] excluded =

            excludedNodes.getAllPresent(excludedNodes.asMap().keySet())

            .keySet()

            .toArray(new DatanodeInfo[0]);

     2 向namenode申请block

        /**

         * 服务端此处的操作:

         *   1): 创建一个block

         *   2): 在磁盘上记录元数据信息

         *   3): 在BlockManager内记录block元数据信息

         */                                          // 将存储有问题主机的集合excluded一并传去,再次申请时将不再从他们里面申请block了

        lb = locateFollowingBlock(excluded.length > 0 ? excluded : null);

RPC调用NameNode服务端代码

            return dfsClient.namenode.addBlock(src, dfsClient.clientName,

                block, excludedNodes, fileId, favoredNodes);

   添加一个block

      /**

       * 1) 选择三台DataNode副本机器

       * 2) 修改目录树

       * 3) 储存数据信息【磁盘

       */

    LocatedBlock locatedBlock = namesystem.getAdditionalBlock(src, fileId,

        clientName, previous, excludedNodesSet, favoredNodesList);

1 . 选择存放block的datanode主机(负载均衡): chooseTarget4NewBlock【根据HDFS 机架感知

    final DatanodeStorageInfo targets[] = getBlockManager().chooseTarget4NewBlock(

        src, replication, clientNode, excludedNodes, blockSize, favoredNodes,

        storagePolicyID);

根据block放置的策略

    final DatanodeStorageInfo[] targets = blockplacement.chooseTarget(src,

        numOfReplicas, client, excludedNodes, blocksize,

        favoredDatanodeDescriptors, storagePolicy);

2 .修改内存里的目录树(修改内存里的元数据)

      saveAllocatedBlock(src, inodesInPath, newBlock, targets);

添加block

    BlockInfoContiguous b = dir.addBlock(src, inodesInPath, newBlock, targets);

BlockManager里记录了这个block的信息

      getBlockManager().addBlockCollection(blockInfo, fileINode);

 在内存里记录新的block信息

    return blocksMap.addBlockCollection(block, bc);

新产生的block,放到文件节点下

      fileINode.addBlock(blockInfo);

3 .把元数据写入磁盘

      persistNewBlock(src, pendingFile);

      3 建立HDFS数据管道(建立的过程中传输的是一个空文件)

        success = createBlockOutputStream(nodes, storageTypes, 0L, false);

创建一个socket

          s = createSocketForPipeline(nodes[0], nodes.length, dfsClient);

此处创建socket,最后return

    final Socket sock = client.socketFactory.createSocket();

创建输出流(把数据写到DataNode上)

          OutputStream unbufOut = NetUtils.getOutputStream(s, writeTimeout);

创建输入流(读取响应结果)

          InputStream unbufIn = NetUtils.getInputStream(s);

注意此处的一个socket

          IOStreamPair saslStreams = dfsClient.saslClient.socketSend(s,

            unbufOut, unbufIn, dfsClient, accessToken, nodes[0]);

这个输出流把客户端数据写到DataNode内【包装流1】

          out = new DataOutputStream(new BufferedOutputStream(unbufOut,

              HdfsConstants.SMALL_BUFFER_SIZE));

通过输入流读取DataNode返回的信息【包装流2】

          blockReplyStream = new DataInputStream(unbufIn);

发送数据请求【接收到socket请求后,datanode创建DataXceiver来接收socket请求

          new Sender(out).writeBlock(blockCopy, nodeStorageTypes[0], accessToken,

              dfsClient.clientName, nodes, nodeStorageTypes, null, bcs,

              nodes.length, block.getNumBytes(), bytesSent, newGS,

              checksum4WriteBlock, cachingStrategy.get(), isLazyPersistFile,

            (targetPinnings == null ? false : targetPinnings[0]), targetPinnings);

写数据,此处的写操作都是write_block

    send(out, Op.WRITE_BLOCK, proto.build());

将socket的数据写到datanode,查看DataXceiver的run方法

    out.flush();

   读取此次数据的请求类型

          op = readOp();

   根据操作类型处理我们的数据

        processOp(op);

case WRITE_BLOCK:

      opWriteBlock(in);

   写数据(查看实现类的方法)   

      writeBlock

  创建BlockReceiver(查看run)【对ackQueue进行管控

        blockReceiver = new BlockReceiver(block, storageType, in,

如果不是数据管道内的最后一个节点

            if (type != PacketResponderType.LAST_IN_PIPELINE && !mirrorError) {

读取下游数据的处理结果

              ack.readFields(downstreamIn);

向上游节点发送处理结果

          sendAckUpstream(ack, expected, totalAckTimeNanos,

            (pkt != null ? pkt.offsetInBlock : 0),

            PipelineAck.combineHeader(datanode.getECN(), myStatus));

如果下游数据处理成功,当前datanode就会从ackQueue内移除packet

            removeAckHead();

  下游还存在服务器的话,继续连接下游服务器

        if (targets.length > 0) {

  mirror镜像,其实就是副本,就是datanode

        mirrorSock = datanode.newSocket();

  向下游发送socket链接(自上文writeBlock继续向下执行,形成循环)

        new Sender(mirrorOut).writeBlock(originalBlock, targetStorageTypes[0],

  接收block【内部启动PacketResponder

        blockReceiver.receiveBlock(mirrorOut, mirrorIn, replyOut,

            mirrorAddr, null, targets, false);

  返回响应

        writeResponse(SUCCESS, null, replyOut);

4 数据管道建立不成功,就放弃这个block

          dfsClient.namenode.abandonBlock(block, fileId, src,

              dfsClient.clientName);

5 获取有问题的这个机器存入excludedNodes

          excludedNodes.put(nodes[errorIndex], nodes[errorIndex]);

       注意此处的wrile,上述代码仍然使用重试的理念

2) 启动ResponceProcessor线程监听package是否发送成功

            initDataStreaming();

由此处start可知ResponseProcessor为线程,查看其中run方法

      response.start();

读取下游的处理结果

      ack.readFields(blockReplyStream);

如果发送成功就把ackQueue内的packet备份删除

              ackQueue.removeFirst();

3) 从dataQueue内把要发送的这个packet移除

              dataQueue.removeFirst();

4) 向ackQueue内添加packet

              ackQueue.addLast(one);

5) 写数据的代码(查看DataXceiver的run)

              blockStream.flush();

针对步骤 5 的异常处理机制:

上述写数据的代码受下列catch内的代码进行异常捕获

进一步查看捕捉到这个异常的位置的代码    // 该方法将errorIndex赋值为0

            tryMarkPrimaryDatanodeFailed();

向外抛异常,查看外层的catch

            throw e;

 】

其外层catch:

          捕获到了异常,将hasError标识修改后,再进行while循环时就可以进入上文的if语句

          hasError = true;

 】

两个将会执行的if子句:

     关闭对象

response.close();

打断当前线程,主要目的是让线程快速退出

        this,interrupt();

让线程暂停

     doSleep = processDatanodeError();

关闭流(此方法内关闭了所有相关的流)

      closeStream();

将数据从ackQueue内重新加入DataQueue(恢复到出问题前的数据状态)

        dataQueue.addAll(0, ackQueue);

清空ackQueue

        ackQueue.clear();

重新建立管道

      boolean doSleep = setupPipelineForAppendOrRecovery();

    记录下刚刚出现问题的那个dataNode服务器(such hadoop1)

           failed.add(nodes[errorIndex]);

    当我们有一半以上的服务器出问题了,那么我们不能用剩下的节点直接建立管道,需要构建新的数据管道

          if (dfsClient.dtpReplaceDatanodeOnFailure.satisfy(blockReplication,

            nodes, isAppend, isHflushed)) {

addDatanode2ExistingPipeline();

申请新的datanode节点

      final LocatedBlock lb = dfsClient.namenode.getAdditionalDatanode(

重新保存数据管道的信息

      setPipeline(lb);

建立新的数据管道

      transfer(src, targets, targetStorageTypes, lb.getBlockToken());

   

    当我们有一半以内的服务器出问题时,则修改原有的数据管道

         LocatedBlock lb = dfsClient.namenode.updateBlockForPipeline(block, dfsClient.clientName);

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
第1章 HDFS 1 1.1 HDFS概述 1 1.1.1 HDFS体系结构 1 1.1.2 HDFS基本概念 2 1.2 HDFS通信协议 4 1.2.1 Hadoop RPC接口 4 1.2.2 流式接口 20 1.3 HDFS主要流程 22 1.3.1 HDFS客户端读流程 22 1.3.2 HDFS客户端流程 24 1.3.3 HDFS客户端追加流程 25 1.3.4 Datanode启动、心跳以及执行名字节点指令流程 26 1.3.5 HA切换流程 27 第2章 Hadoop RPC 29 2.1 概述 29 2.1.1 RPC框架概述 29 2.1.2 Hadoop RPC框架概述 30 2.2 Hadoop RPC的使用 36 2.2.1 Hadoop RPC使用概述 36 2.2.2 定义RPC协议 40 2.2.3 客户端获取Proxy对象 45 2.2.4 服务器获取Server对象 54 2.3 Hadoop RPC实现 63 2.3.1 RPC类实现 63 2.3.2 Client类实现 64 2.3.3 Server类实现 76 第3章 Namenode(名字节点) 88 3.1 文件系统树 88 3.1.1 INode相关类 89 3.1.2 Feature相关类 102 3.1.3 FSEditLog类 117 3.1.4 FSImage类 138 3.1.5 FSDirectory类 158 3.2 数据块管理 162 3.2.1 Block、Replica、BlocksMap 162 3.2.2 数据块副本状态 167 3.2.3 BlockManager类(done) 177 3.3 数据节点管理 211 3.3.1 DatanodeDescriptor 212 3.3.2 DatanodeStorageInfo 214 3.3.3 DatanodeManager 217 3.4 租约管理 233 3.4.1 LeaseManager.Lease 233 3.4.2 LeaseManager 234 3.5 缓存管理 246 3.5.1 缓存概念 247 3.5.2 缓存管理命令 247 3.5.3 HDFS集中式缓存架构 247 3.5.4 CacheManager类实现 248 3.5.5 CacheReplicationMonitor 250 3.6 ClientProtocol实现 251 3.6.1 创建文件 251 3.6.2 追加文件 254 3.6.3 创建新的数据块 257 3.6.4 放弃数据块 265 3.6.5 关闭文件 266 3.7 Namenode的启动和停止 268 3.7.1 安全模式 268 3.7.2 HDFS High Availability 276 3.7.3 名字节点的启动 301 3.7.4 名字节点的停止 306 第4章 Datanode(数据节点) 307 4.1 Datanode逻辑结构 307 4.1.1 HDFS 1.X架构 307 4.1.2 HDFS Federation 308 4.1.3 Datanode逻辑结构 310 4.2 Datanode存储 312 4.2.1 Datanode升级机制 312 4.2.2 Datanode磁盘存储结构 315 4.2.3 DataStorage实现 317 4.3 文件系统数据集 334 4.3.1 Datanode上数据块副本的状态 335 4.3.2 BlockPoolSlice实现 335 4.3.3 FsVolumeImpl实现 342 4.3.4 FsVolumeList实现 345 4.3.5 FsDatasetImpl实现 348 4.4 BlockPoolManager 375 4.4.1 BPServiceActor实现 376 4.4.2 BPOfferService实现 389 4.4.3 BlockPoolManager实现 396 4.5 流式接口 398 4.5.1 DataTransferProtocol定义 398 4.5.2 Sender和Receiver 399 4.5.3 DataXceiverServer 403 4.5.4 DataXceiver 406 4.5.5 读数据 408 4.5.6 数据(done) 423 4.5.7 数据块替换、数据块拷贝和读数据块校验 437 4.5.8 短路读操作 437 4.6 数据块扫描器 437 4.6.1 DataBlockScanner实现 438 4.6.2 BlockPoolSliceScanner实现 439 4.7 DirectoryScanner 442 4.8 DataNode类的实现 443 4.8.1 DataNode的启动 444 4.8.2 DataNode的关闭 446 第5章 HDFS客户端 447 5.1 DFSClient实现 447 5.1.1 构造方法 448 5.1.2 关闭方法 449 5.1.3 文件系统管理与配置方法 450 5.1.4 HDFS文件与操作方法 451 5.1.5 HDFS文件读方法 452 5.2 文件读操作与输入流 452 5.2.1 打开文件 452 5.2.2 读操作――DFSInputStream实现 461 5.3 文件短路读操作 481 5.3.1 短路读共享内存 482 5.3.2 DataTransferProtocol 484 5.3.3 DFSClient短路读操作流程 488 5.3.4 Datanode短路读操作流程 509 5.4 文件操作与输出流 512 5.4.1 创建文件 512 5.4.2 操作――DFSOutputStream实现 516 5.4.3 追加操作 543 5.4.4 租约相关 546 5.4.5 关闭输出流 548 5.5 HDFS常用工具 549 5.5.1 FsShell实现 550 5.5.2 DFSAdmin实现 552

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

MisakiMei释光

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

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

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

打赏作者

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

抵扣说明:

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

余额充值