Hadoop文件系统——HDFS读写数据流

2. HDFS数据流

2.1 文件读取流程剖析(重要)

在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1a48nDo0-1586279469073)(picture\read.jpg)]

public static void getFileFromHDFS() throws IOException, InterruptedException, URISyntaxException {
        // 1 获取文件系统
        Configuration configuration = new Configuration();
        FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), configuration, "root");
        // 2 获取输入流
        FSDataInputStream fis = fs.open(new Path("/in.txt"));
        // 3 获取输出流
        FileOutputStream fos = new FileOutputStream(new File("in.txt"));
        // 4 流的对拷
        IOUtils.copyBytes(fis, fos, configuration);
        // 5 关闭资源
        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
    }
  1. 客户端实例化一个FileSystem对象(其实就是HDFS的DistributedFileSystem实例,DistributedFileSystem继承于FileSystem),调用DistributedFileSystem.open()方法通过RPC向NameNode请求下载文件NameNode通过查询元数据,找到文件开头部分块(第一个块)所有副本所在的DataNode地址并传给客户端,并且每个块的副本都基于距离远近排序

     @Override
      public FSDataInputStream open(Path f, final int bufferSize)
          throws IOException {
        statistics.incrementReadOps(1);
        Path absF = fixRelativePart(f);
        return new FileSystemLinkResolver<FSDataInputStream>() {
          @Override
          public FSDataInputStream doCall(final Path p)
              throws IOException, UnresolvedLinkException {
            final DFSInputStream dfsis =
              dfs.open(getPathName(p), bufferSize, verifyChecksum);
            return dfs.createWrappedInputStream(dfsis);
          }
          @Override
          public FSDataInputStream next(final FileSystem fs, final Path p)
              throws IOException {
            return fs.open(p, bufferSize);
          }
        }.resolve(this, absF);
      }
    
  2. open()方法返回的是一个FSDataInputStream对象(支持文件定位的输入流)给客户端读取数据。这个FSDataInputStream其实是一个被包装的DFSInputStream对象。

  3. 客户端调用这个输入流的read()方法。文件开头部分的块的数据节点地址的DFSInputStream随即与这些块最近的数据节点相连接,数据从数据节点返回客户端(以Packet为单位来做校验,先在本地缓存,默认buffer为4096位,然后写入目标文件)。当到达块的末端时,DFSInputStream会关闭与数据节点之间的联系,然后为下一个块找到最佳的数据节点。客户端只需要读取一个连续的流,这些对于客户端都是透明的。

    /**
       * Copies from one stream to another.
       * 
       * @param in InputStrem to read from
       * @param out OutputStream to write to
       * @param buffSize the size of the buffer 
       */
      public static void copyBytes(InputStream in, OutputStream out, int buffSize) 
        throws IOException {
        PrintStream ps = out instanceof PrintStream ? (PrintStream)out : null;
        byte buf[] = new byte[buffSize];
        int bytesRead = in.read(buf);
        while (bytesRead >= 0) {
          out.write(buf, 0, bytesRead);
          if ((ps != null) && ps.checkError()) {
            throw new IOException("Unable to write to output stream.");
          }
          bytesRead = in.read(buf);
        }
      }
    
  4. 客户端从流中读取数据时,块是按DFSInputStream打开与数据节点的新连接的顺序读取的。它也会调用NameNode检索下一组需要的块的位置。一旦完成读取,就会对FSDataInputStream调用close()。

2.2 文件写入流程剖析(重要)

在这里插入图片描述

public void putFileToHDFS() throws IOException, InterruptedException, URISyntaxException {
	// 1 获取文件系统
	Configuration configuration = new Configuration();
	FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), configuration, "root");
	// 2 创建输入流
	FileInputStream fis = new FileInputStream(new File("in.txt"));
	// 3 获取输出流
	FSDataOutputStream fos = fs.create(new Path("/in.txt"));
	// 4 流对拷
	IOUtils.copyBytes(fis, fos, configuration);
	// 5 关闭资源
	IOUtils.closeStream(fos);
	IOUtils.closeStream(fis);
    fs.close();
}

  1. 客户端通过DistributedFileSystem对象调用create()向NameNode请求上传文件NameNode检查目标文件是否已存在,父目录是否存在,以及客户端是否有权限创建文件,如果检查通过NameNode就会生成一个新的文件记录**“file.copying”,并返回一个文件系统输出流**,否则会向客户端返回一个IOException异常。

  2. 客户端将所要上传的文件转换为输出流FSDataOutputStream;

  3. 当客户端开始写入文件的时候,客户端会将文件切分成多个 packets( 默认64kB ),并在内部以数据队列“data queue(数据队列)”的形式管理这些 packets,并向 namenode 申请 blocks,获取用来存储 replicas 的合适的 datanode 列表,列表的大小根据 namenode 中 replication 的设定而定,并按距离远近排序

  4. 开始以 pipeline(管道)的形式将 packet 写入所有的 replicas 中。客户端把 packet 以流的方式写入第一个 datanode,该 datanode 把该 packet 存储之后,再将其传递给在此 pipeline 中的下一个 datanode,直到最后一个 datanode,这种写数据的方式呈流水线的形式

    客户端会根据返回的三个节点和第一个节点建立一个socket连接(只会和第一个节点建立),第一个节点又会和第二个节点建立socket连接,由第二个节点又会和第三个节点建立一个socket连接,这种连接的方式叫Pipeline

  5. DataNode完成接收block块后,block的metadata(MD5校验码)通过一个心跳将信息汇报给NameNode

  6. 最后一个 datanode 成功存储之后会返回一个ack packet(确认包),在 pipeline 里传递至客户端,在客户端的开发库内部维护着**“ack queue”**,成功收到 datanode 返回的ack packet 后会从"data queue"移除相应的 packet;

  7. 如果传输过程中,有某个 datanode 出现了故障,那么当前的 pipeline 会被关闭出现故障的 datanode 会从当前的 pipeline 中移除,剩余的 block 会继续剩下的 datanode 中继续 以 pipeline 的形式传输,同时 namenode通过心跳检测机制会发现副本数不足,并分配一个新的datanode,保持replicas设定的数量

  8. 客户端完成数据的写入后,会对数据流调用 close()方法,关闭数据流。并且NameNode若收到所有DataNode的汇报,NameNode会将元数据后的.copying去掉成为正式文件。

2.3 副本节点的选择——机架感知

第一个副本选择在Client所在节点上,或者随机选一个;
第二个副本选择在第一个副本同机架的任一节点;
第三个副本选择在同集群另一个机架中的任意节点。

发布了59 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/dong_W_/article/details/105377849
今日推荐