Bootstrap

源码解析HDFS写数据流程


HDFS写数据流程

  • 1)客户端这边先创建一个的RPC客户端,发送给NameNode进行请求,NameNode对这个请求进行判断,判断文件的路径是否存在,是否有权限添加
  • 2)客户端创建一个DataStreamerDataStreamer里创建了一个队列dataQueue,并将dataQueue进行等待;同时DataStreamerNameNode通过机架感知获取block块可以写到哪些DataNode上
  • 3)紧接着客户端会向DataNode请求建立数据传输通道
  • 4)然后才真正开始写数据,先写chunkchecksum,将其封装成packet,packet要往dataQueue里面写数据;会先查看dataQueue里面是否已经满了,如果满了的话需要等待dataQueue没满的时候再往里写;如果没满,就可以往dataQueue写packet
  • 5)packetdataQueue里面已经写完之后会通知dataQueue队列已经向队列里写完数据,接下来要把数据传到DataNode
  • 6)由DataStreamer准备将dataQueue的数据往DataNode里发送,通过Socket的形式发送packet
    • 发送packet时首先将dataQueue里对应的packet发送给DataNode一份
    • 然后将dataQueue里对应的packet移除掉,再将其放到ackQueue队列的尾部
  • 7)由DataNode对应的服务来接收对应的数据,接收完数据之后,先把数据持久化到磁盘,同时将数据发送到下一个节点
    • 下一个节点同样将数据持久化到磁盘,再往下一个节点发送,如此往复;
    • 发送数据之后由后往前逐渐进行应答,直到应答到ResponseProcessorResponseProcessor收齐三个应答(假设有三个DataNode)之后,会告诉ackQueue当前应答已经全部收齐,ackQueue可以将对应的packet数据删除;如果没有收齐三个应答,就会告诉ackQueue队列保存,再将对应的packet移到dataQueue尝试从新发送数据

image-20210409005238697

create

进入到FileSystem类下面的create方法

public FSDataOutputStream create(Path f) throws IOException {
   
    return create(f, true);
}

顺着create方法一直查看下去,找到如下代码,当前的create方法是抽象方法

public abstract FSDataOutputStream create(...) throws IOException;

回退到上一步,ctrl + alt + B,查看实现类,以分布式文件系统为例,选择DistributedFileSystem

public FSDataOutputStream create(...) throws IOException {
   
    return this.create(...);
  }

继续跟进create方法

public FSDataOutputStream create(...) throws IOException {
   

    return new FileSystemLinkResolver<FSDataOutputStream>() {
   
      @Override
      public FSDataOutputStream doCall(final Path p) throws IOException {
   
        final DFSOutputStream dfsos = dfs.create(...);
        return dfs.createWrappedOutputStream(dfsos, statistics);
      }
  }

跟进dfs.create()

public DFSOutputStream create(...)
      throws IOException {
   
    return create(src, permission, flag, true,
        replication, blockSize, progress, buffersize, checksumOpt, null);
  }
public DFSOutputStream create(...)
      throws IOException {
   
    return create(src, permission, flag, createParent, replication, blockSize,
        progress, buffersize, checksumOpt, favoredNodes, null);
  }
public DFSOutputStream create(...) throws IOException {
   
    final DFSOutputStream result = DFSOutputStream.newStreamForCreate(...);
  }

走到这里,看到dfsClient.namenode.create(...)代表客户端开始向namenode发送RPC请求,选中ctrl + alt + b,可以看到NameNodeRpcServer,是namenode的一个服务,现在相当于客户端跟服务端进行通信。接下来namenode就要检查目录树是否可以创建文件

static DFSOutputStream newStreamForCreate(...)
      throws IOException {
   
    stat = dfsClient.namenode.create(...);
}

选中NameNodeRpcServer,进入到create方法

public HdfsFileStatus create(...) throws IOException {
   
	status = namesystem.startFile(...);
}

跟进namesystem.startFile(...)方法

HdfsFileStatus startFile(...) throws IOException {
   
    status = startFileInt(...);
}
private HdfsFileStatus startFileInt(...) throws IOException {
   
    stat = FSDirWriteFileOp.startFile(...);
}

会检查所要上传的目录是否存在,如果存在,并且传入的参数是可以覆盖的,则不抛出异常;否则会抛出异常;

static HdfsFileStatus startFile(...)
      throws IOException {
   
    if (overwrite) {
   
        }
      } else {
   
        // If lease soft limit time is expired, recover the lease
        throw new FileAlreadyExistsException(src + " for client " +
            clientMachine + " already exists");
      }
    iip = addFile(...);
}

紧接着会执行addFile()方法,跟进addFile()方法

private static INodesInPath addFile(...) throws IOException {
   
    // 创建一个文件
    INodeFile newNode = newINodeFile(...);
    // 执行addINode,添加目录树
    newiip = fsd.addINode(existing, newNode, permissions.getPermission());
}

addINode方法添加目录树,会根据以前的目录结构,选择一个节点往上面添加;至此,整个创建过程就结束

INodesInPath addINode(INodesInPath existing, INode child,
                        FsPermission modes)
      throws QuotaExceededException, UnresolvedLinkException {
   
    cacheName(child);
    writeLock();
    try {
   
      return addLastINode(existing, child, modes, true);
    } finally {
   
      writeUnlock();
    }
  }

下面要进行的是准备工作,DataNode会准备一个上传队列,并将其状态设置为wait,阻塞等待,等待数据的上传。

回退到最开始,从create方法再逐步地跟进

FSDataOutputStream fos = fs.create(new Path("/input"));
public FSDataOutputStream create(Path f) throws IOException {
   
    return create(f, true);
  }
public FSDataOutputStream create(Path f, boolean overwrite)throws IOException {
   
    return create(...);
}
;