HDFS的读取与写入流程 [面试重点]
一、HDFS的读取流程
-
1.先上一段简单代码,使用FileSystem读取HDFS文件
// cc FileSystemDoubleCat Displays files from a Hadoop filesystem on standard output twice, by using seek import java.net.URI; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; // vv FileSystemDoubleCat public class FileSystemDoubleCat { public static void main(String[] args) throws Exception { String uri = args[0]; //Configuration对象封装了客户端或服务器的配置,通过设置配置文件读取类路径来实现 Configuration conf = new Configuration(); //FileSystem是hadoop通用的文件系统api FileSystem fs = FileSystem.get(URI.create(uri), conf); FSDataInputStream in = null; try { //有了FileSystem实例后,通过调用open()方法获取文件的输入流 //open()方法返回的是FSDataInputStream对象,不是标准的java.io类对象 in = fs.呢个(new Path(uri)); IOUtils.copyBytes(in, System.out, 4096, false); in.seek(0); // go back to the start of the file IOUtils.copyBytes(in, System.out, 4096, false); } finally { IOUtils.closeStream(in); } } } // ^^ FileSystemDoubleCat
-
2. 客户端读取HDFS文件流程图如下:
-
3. 客户端读取HDFS文件流程详解
- ① 客户端调用 FileSystem 对象的open() 方法 打开要读取的文件。对于HDFS来说,这个对象是DistributedFileSystem的一个实例(图中的步骤1)。
- ②DistributedFileSystem 通过远程RPC请求调用namenode,确定文件起始块的位置(步骤2) 对于每一个块,namenode会返回存有该块的副本信息(datanode地址)。
- ③DistributedFileSystem类返回一个FSDataInputStream对象(一个支持定位的输入流),给客户端读取数据。FSDataInputStream类封装了DFSInputStream对象,客户端对这个输入流调用read( )方法 (步骤3)。
- ④ DFSInputStream连接距离最近的文件中的第一个datanode。通过对数据流反复调用read()方法,将数据从datanode传输到客户端(步骤4)。
- ⑤到达块的末端时,DFSInputStream关闭与该datanode的连接,然后寻找下一个块的最佳datanode(步骤5) (注:一个文件比如200M 会分成两个块128M 和 62M,两个文件一般不会在同一个datanode上面存储,当读取完一个块的数据文件时,就要去读取另一个文件块的datanode)
- ⑥一旦客户端完成读取所有数据,就对FSDataInputStream()调用close()方法(步骤6)
-
4.网络拓扑
-
两个节点"彼此近邻" 是什么意思?DFSInputStream连接距离最近的datanode,最近距离是怎么定义的?
-
在海量数据处理中,主要限制因素是节点之间数据的传输速率——带宽很稀缺。这里将两个节点之间的带宽作为距离的衡量标准。 hadoop采用一个简单的方法:把网络看成一棵树,两个节点之间的距离 是 它们到最近共同祖先的距离总和。
-
针对以下场景,可用带宽依次减少:
- 同一节点上面的进程
- 同一机架上的不同节点
- 同一数据中心不同机架
- 不同数据中心的节点
假如有数据中心d1 机架r1 中的节点 n1。改节点可以表示为 /d1/r1/n1。则上述四种表示为
- distance(/d1/r1/n1, /d1/r1/n1) = 0(同一节点上面的进程)
- distance(/d1/r1/n1, /d1/r1/n2) = 2(同一机架上的不同节点)
- distance(/d1/r1/n1, /d1/r2/n3) = 4(同一数据中心不同机架)
- distance(/d1/r1/n1, /d2/r3/n4) = 6(不同数据中心的节点)
-
二、HDFS的写入流程
- 1.先上代码,将本地文件复制到Hadoop文件系统
// cc FileCopyWithProgress Copies a local file to a Hadoop filesystem, and shows progress import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.util.Progressable; // vv FileCopyWithProgress public class FileCopyWithProgress { public static void main(String[] args) throws Exception { String localSrc = args[0]; String dst = args[1]; InputStream in = new BufferedInputStream(new FileInputStream(localSrc)); Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(URI.create(dst), conf); //create()方法返回FSDataOutputStream OutputStream out = fs.create(new Path(dst), new Progressable() { public void progress() { System.out.print("."); } }); IOUtils.copyBytes(in, out, 4096, true); } } // ^^ FileCopyWithProgress
- 2.客户端将数据写入HDFS流程图
- 客户端将数据写入HDFS流程图
-
① 客户端通过对DistributedFileSystem 对象调用create()新建文件(步骤1)。
-
② DistributedFileSystem 对namenode 创建一个RPC调用,在文件的命名空间创建一个新文件,此时文件中还没有相应的数据块(步骤2)。namenode 会执行各种不同的检查,确保这个文件是不存在的以及客户端有新建该文件的权限。检查通过后,namenode 就会为新文件记录一条记录。
-
③ DistributedFileSystem 向客户端返回一个FSDataOutputStream对象。由此客户端就可以开始写数据(步骤3)。FSDataOutputStream封装了DFSoutPutstream对象,负责处理处理datanode和namenode之间的通信。
-
④ 客户端写入数据时,DFSOutPutStream 将数据分成一个个的数据包packet,并写入内部队列(数据队列 data queue)。DataStreamer处理数据队列,负责挑选合适存储副本的一组datanode,并据此要求namenode分配新的数据块。这一组datanode构成一个管线pipeline(假设副本数为3),所以pipeline中有3个节点。 DataStreamer将数据包packet 通过pipeline中的第1个datanode,然后第1个datanode存储改数据包packet后,以同样的方式发给第2个datanode,第2个再发给第3个(步骤4)。
-
⑤ DFSOutPutStream也维护这一个**“确认队列”(ack queue),来等待datanode的收到确认回执**。收到管道pipeline中所有的datanode确认消息后,该数据包才会从确认队列删除(步骤5)。
-
⑥ 客户端完成数据的写入后,对数据流调用close()方法(步骤6)。
-
⑦ close()方法会将剩余的所有数据写入datanode的线管pipeline中,并告知namenode其文件写入完成之前,等待确认(步骤7)。namenode已经知道文件由哪些块组成(因为Datastreamer 请求分配数据块),所以namenode在返回成功前只需要等待数据块进行最小量的复制。
-