HDFS读写数据过程解析及编程实现

HDFS读数据

HDFS读数据的简要过程
  1. 客户端将要读取的文件路径发送给namenode;
  2. namenode获取文件的元信息(主要是block的存放位置信息)返回给客户端;
  3. 客户端根据返回的信息找到相应datanode逐个获取文件的block;
  4. 客户端对block进行追加合并从而获得整个文件;
HDFS读数据的详细过程

  1. 客户端通过FileSystem.open()打开文件(在HDFS文件系统中DistributedFileSystem具体实现了FileSystem),创建输入流FSDataInputStream(对于HDFS而言,具体的输入流就是DFSInputStream);
  2. 在DFSInputStream的构造函数中,输入流通过ClientProtocal.getBlockLocations()远程调用名称节点,获得文件开始数据块的保存信息。对于该数据块,名称节点返回保存该数据块的所有数据节点的地址,同时根据客户端的远近对数据节点进行排序,返回给客户端;
  3. 获得输入流FSDataInputStream后,客户端调用read()函数开始读取数据,输入流根据前面的排序结果,选择距离客户端最近的数据节点建立连接并读取数据;
  4. 数据从该数据节点读取到客户端,当该数据块读取完毕时,FSDataInputStream关闭和该数据节点的连接;
  5. 输入流通过getBlockLocations()方法向名称节点查找下一个数据块,重复2、3、4步;
  6. 当客户端读取完毕数据的时候,调用FSDataInputStream的close函数,关闭输入流;

注意:在读取数据的过程中,如果客户端与数据节点通信出现错误,就会尝试连接包含此数据块的下一个数据节点。

HDFS通信协议

上面的HDFS读数据的详细过程中,客户端与名称节点,客户端与数据节点等都存在远程调用和信息交互,这都是基于HDFS提供的通信协议实现的。

HDFS中提供两种通信协议

  • Hadoop RPC接口:HDFS中基于Hadoop RPC框架实现的接口
  • 流式接口:HDFS中基于TCP或者HTTP实现的接口

我们主要介绍Hadoop RPC接口

Hadoop RPC调用使得HDFS进程能够像本地调用一样调用另一个进程中的方法,目前Hadoop RPC调用基于Protobuf实现,接口定义在org.apache.hadoop.hdfs.protocol和org.apache.hadoop.hdfs.server.protocol包中,其中包括以下几个接口

  • ClientProtocol:客户端和名字节点间的接口;
  • ClientDatanodeProtocol:客户端与数据节点的接口
  • DatanodeProtocol:数据节点与名字节点通信接口
  • InterDatanodeProtocol:数据节点与数据节点间的通信接口
  • NamenodeProtocol:第二名字节点与名字节点间的接口
HDFS常用的Java API

在下面的HDFS读数据的编程实现的代码中,我们会用到一些HDFS常用的Java API,在编程实现HDFS读数据流程之前,我们对这些Java API进行一下简单的了解

  • org.apache.hadoop.conf.Configuration:该类封装了客户端或者服务端的配置
  • org.apache.hadoop.fs.FileSystem:一个通用文件系统的抽象基类,可以被分布式文件系统继承
  • org.apache.hadoop.fs.Path:用于表示Hadoop文件系统中的一个文件或者一个目录的路径
  • org.apache.hadoop.fs.FSDataInputStream:文件输入流,用于读取Hadoop文件
编程实现
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FSDataInputStream;
     
public class ReadFile {
	public static void main(String[] args) {
        try {
            //访问工程目录下的core-site.xml和hdfs-site.xml配置项,把相关配置项加载进来
            Configuration conf = new Configuration(); 
            conf.set("fs.defaultFS","hdfs://Master:9000"); 
            conf.set("fs.hdfs.impl","org.apache.hadoop.hdfs.DistributedFileSystem");
            //通过FileSystem的静态方法get获得DistributedFileSystem对象
            FileSystem fs = FileSystem.get(conf);
            //要读取的文件路径
            Path file = new Path("/user/hadoop/input/wordfile2.txt"); 
            //调用DistributedFileSystem.open(),创建输入流DFSInputStream
            FSDataInputStream getIt = fs.open(file);
            BufferedReader d = new BufferedReader(new InputStreamReader(getIt));
            	String content = d.readLine(); //读取文件一行
            	System.out.println(content);
            	d.close(); //关闭文件
            	fs.close(); //关闭hdfs
        } catch (Exception e) {
        	e.printStackTrace();
        }
    }	

运行结果

在这里插入图片描述

HDFS写数据

HDFS写数据的简要过程

1)客户端向namenode请求上传文件,namenode检查目标文件是否已存在,客户端是否有权限创建文件,检 查通过后,namenode返回是否可以上传;

2)客户端请求第一个 block上传到哪几个datanode服务器上;

3)namenode返回3个datanode节点,分别为dn1、dn2、dn3;

4)客户端请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成, dn1、dn2、dn3逐级应答客户端;

5)客户端开始往dn1上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位,dn1收到一个packet就会传给dn2,dn2传给dn3,dn3返回确认包应答,逐级上传给客户端;

6)当一个block传输完成之后,客户端再次请求namenode上传第二个block的服务器(重复执行2—5步);

7)当数据全部写完之后,客户端调用close方法关闭输出流,当客户端收到写入分包的应答以后,就可以通知名称节点关闭文件,完成一次正常的写文件过程;

HDFS写数据的详细过程

  1. 客户端通过FileSystem.create()创建文件(在HDFS文件系统中DistributedFileSystem具体实现了FileSystem),创建输出流FSDataOutputStream(对于HDFS而言,具体的输入流就是DFSOutputStream);
  2. DistributedFileSystem对象通过RPC远程调用名称节点,在文件系统的命名空间中新建一个文件 名称节点会执行一些检查(文件是否存在,客户端权限),检查通过之后,名称节点会构造一个新文件,并添加文件信息。远程方法调用结束后,DistributedFileSystem会利用DFSOutputStream来实例化FSDataOutputStream,返回给客户端,客户端使用这个输出流写入数据;
  3. 获得输出流后,客户端调用输出流的write()方法向HDFS中对应的文件写入数据;
  4. 数据被分成一个个分包 分包被放入DFSOutputStream对象的内部队列 DFSOutputStream向名称节点申请保存数据块的若干数据节点 ,这些数据节点形成一个数据流管道。队列中的分包最后被打包成数据包 发往数据流管道中的第一个数据节点 第一个数据节点将数据包发送到第二个节点 依此类推,形成“流水线复制”;
  5. 为了保证节点数据准确,接收到数据的数据节点要向发送者发送“确认包” 确认包沿着数据流管道逆流而上,经过各个节点最终到达客户端 客户端收到应答时,它将对应的分包从内部队列移除;
  6. 完成向文件写入数据,Client在文件输出流(FSDataOutputStream)对象上调用close方法,关闭输出流,当客户端收到写入分包的应答以后,就可以调用DistributedFileSystem对象的complete方法,通知NameNode文件写入成功;
编程实现
import org.apache.hadoop.conf.Configuration;  
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.Path;
     
public class Chapter3 {    
	public static void main(String[] args) { 
		try {
			Configuration conf = new Configuration();  
             conf.set("fs.defaultFS","hdfs://Master:9000");
			FileSystem fs = FileSystem.get(conf);
             byte[] buff = "xiao tuo hao shuai\n".getBytes(); // 要写入的内容
             String filename = "test"; //要写入的文件名
             FSDataOutputStream os = fs.create(new Path(filename));
             os.write(buff,0,buff.length);
             System.out.println("Create:"+ filename);
             os.close();
             fs.close();
		} catch (Exception e) {  
         	e.printStackTrace();  
         }  
	}  
}

运行结果
在这里插入图片描述

HDFS读写数据综合实现

上面分别介绍了HDFS的读数据和写数据的过程解析及编程实现,下面我们用一个综合小案例把HDFS中读数据和写数据综合起来。

该案例要实现的功能是

  • 给定指定目录,从指定目录中过滤出所有后缀名不为“.abc”的文件;
  • 然后,对过滤的文件进行读取;
  • 最后,将这些文件的内容合并到指定的文件中

下面来直接看代码(个别地方已给上注释)

    import java.io.IOException;
    import java.io.PrintStream;
    import java.net.URI;
     
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.*;
     
    /**
     * 过滤掉文件名满足特定条件的文件 
     * 通过实现接口PathFilter中的方法accept(Path path),对Path指定的文件进行过滤
     */
    class MyPathFilter implements PathFilter {
         String reg = null; 
         MyPathFilter(String reg) {
              this.reg = reg;
         }
         public boolean accept(Path path) {
            if (!(path.toString().matches(reg)))
                return true;
            return false;
        }
    }
    /***
     * 利用FSDataOutputStream和FSDataInputStream合并HDFS中的文件
     */
    public class MergeFile {
        Path inputPath = null; //待合并的文件所在的目录的路径
        Path outputPath = null; //输出文件的路径
        
        //MergeFile类的构造函数
        public MergeFile(String input, String output) {
            this.inputPath = new Path(input);
            this.outputPath = new Path(output);
        }
        
        public void doMerge() throws IOException {
            Configuration conf = new Configuration();
            conf.set("fs.defaultFS","hdfs://Master:9000");
            conf.set("fs.hdfs.impl","org.apache.hadoop.hdfs.DistributedFileSystem");
            //两个文件系统的实例,一个用于读数据,一个用于写数据
            FileSystem fsSource = FileSystem.get(URI.create(inputPath.toString()), conf);
            FileSystem fsDst = FileSystem.get(URI.create(outputPath.toString()), conf);
            
                    //下面过滤掉输入目录中后缀为.abc的文件
            FileStatus[] sourceStatus = fsSource.listStatus(inputPath,
                    new MyPathFilter(".*\\.abc")); 
            FSDataOutputStream fsdos = fsDst.create(outputPath);
            PrintStream ps = new PrintStream(System.out);
            //下面分别读取过滤之后的每个文件的内容,并输出到同一个文件中
            for (FileStatus sta : sourceStatus) {
                //下面打印后缀不为.abc的文件的路径、文件大小
                System.out.print("路径:" + sta.getPath() + "    文件大小:" + sta.getLen()
                        + "   权限:" + sta.getPermission() + "   内容:");
                FSDataInputStream fsdis = fsSource.open(sta.getPath());
                byte[] data = new byte[1024];
                int read = -1;
     
                while ((read = fsdis.read(data)) > 0) {
                    ps.write(data, 0, read);
                    fsdos.write(data, 0, read);
                }
                //关闭输入流
                fsdis.close();          
            }
            //关闭打印流
            ps.close();
            //关闭输出流
            fsdos.close();
        }
        
        //main函数,程序入口
        public static void main(String[] args) throws IOException {
            MergeFile merge = new MergeFile(
                    "hdfs://Master:9000/user/hadoop/input",
                    "hdfs://Master:9000/user/hadoop/output/merge.txt");
            merge.doMerge();
        }
    }

运行结果
在这里插入图片描述查看merge.txt,数据已写入
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值