初步理解HDFS数据写入过程 + Java代码实现

HDFS的写入数据过程细节上比较复杂,我们先来看一看具体代码是怎样实现写入数据过程的,然后再根据代码进行分析:

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class HDFSCreateFile {
	public static void main(String[] args){
		try{
			
			//加载配置项
			Configuration conf = new Configuration();
			conf.set("fs.defaultFS", "hdfs://localhost:9000");
			conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");

			//创建文件系统实例
			FileSystem fs = FileSystem.get(conf);
			
			//创建文件实例
			String fileName = "test";
			Path file = new Path(fileName);
			
			//创建输出流对象
			FSDataOutputStream os = fs.create(file);
			
			//写入数据
			byte[] buff = "Hello World".getBytes();
			os.write(buff, 0, buff.length);
			System.out.println("Create:"+fileName);
			
			//关闭输出流和文件系统
			os.close();
			fs.close();	
					
		} catch (Exception e){
			e.printStackTrace();
		} 
	}
}

下面我们根据以上代码来一步步分析HDFS写入数据的过程:

  1. 加载配置项:Configuration对象封装了客户端或服务器的配置,通过set()方法在程序里面进行传参,fs.defaultFS参数用于指定HDFS文件系统的访问地址,fs.hdfs.impl参数则是用于指定HDFS文件系统的具体实现类,在这里,HDFS文件系统的访问地址被指定为hdfs://localhost:9000,其中9000是端口号,HDFS文件系统的具体实现类则被指定为DistributedFileSystem类,对这个类在下面会有解释:

    Configuration conf = new Configuration(); 
    conf.set("fs.defaultFS", "hdfs://localhost:9000");
    conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
    
  2. 创建文件系统实例:将Configuration对象作为参数传入get()方法,get()方法返回的是默认文件系统,该默认文件系统由在Configuration对象中封装的地址所指定,我们这里的默认文件系统就是上一步中指定的HDFS文件系统,如果没有指定,则使用默认的本地文件系统:

    FileSystem fs = FileSystem.get(conf); 
    

    以上代码中的FileSystem类是一个通用文件系统的抽象基类,可以被分布式文件系统继承,所有可能使用Hadoop文件系统的代码都要使用这个类,Hadoop为FileSystem这个抽象类提供了多种具体实现子类,在HDFS文件系统中,就是DistributedFileSystem类具体实现了这个抽象类,也就是说,在上面的代码中,创建的其实是一个引用类型为FileSystem的DistributedFileSystem对象,如果在上面传入配置参数的时候,没有传入fs.hdfs.impl参数,我们也可以使用以下代码通过强制类型转换创建文件系统实例:

    DistributedFileSystem fs = (DistributedFileSystem) FileSystem.get(conf);
    
  3. 创建文件实例:被创建的文件的名称是test,这里并没有给出路径全称,表示采用了相对路径,实际上该文件就是当前登录Linux系统的用户在HDFS中对应的用户目录下的test文件,比如我的当前登录用户名是hadoop,那么绝对路径就是hdfs://localhost:9000/user/hadoop/test:

    String fileName = "test"; 
    Path file = new Path(fileName);
    
  4. 创建输出流对象:调用引用类型为FileSystem的DistributedFileSystem对象的create()方法,将文件实例作为参数传入,该方法会创建并返回一个输出流FSDataOutputStream对象,在HDFS文件系统中,具体的输出流就是DFSOutputStream,换句话说,就是FSDataOutputStream对象在创建以后,里面封装了一个DFSOutputStream对象:

    FSDataOutputStream os = fs.create(file); 
    

    在FSDataOutputStream对象被创建时,调用了DFSOutputStream类的构造方法,在该构造方法中,DitributedFileSystem对象通过RPC远程调用名称节点,在文件系统的命名空间中创建一个新的文件,而名称节点会执行一些检查,比如文件是否已经存在、客户端是否有权限创建文件等,检查通过之后,名称节点会构造一个新文件,并添加文件信息,远程方法调用结束后,DistributedFileSystem对象会利用DFSOutputStream对象来实例化FSDataOutputStream类,并返回给客户端,客户端使用这个输出流写入数据;

  5. 写入数据:创建字节数组,将字符串转换为字节形式装入字节数组,并通过调用输出流的write()方法向HDFS文件系统中对应的文件写入数据:

    byte[] buff = "Hello World".getBytes(); 
    os.write(buff, 0, buff.length); 
    

    在以上代码中,其实是在执行一段循环过程,客户端向输出流FSDataOutputStream对象中写入的数据会首先被分成一个个的分包,这些分包被放入DFSOutputStream对象的内部队列,输出流FSDataOutputStream对象会向名称节点申请保存文件和副本数据块的若干个数据节点,这些数据节点形成一个数据流管道,队列中的分包最后会被打包成数据包,由客户端发往数据流管道中的第一个数据节点,第一个数据节点将数据包发送给第二个数据节点,第二个数据节点将数据包发送给第三个数据节点,这样,数据包会流经管道上的各个数据节点,这就是HDFS采用的流水线复制策略,当最后文件写入完成时,数据备份也同时完成,不过需要注意的是,因为各个数据节点位于不同的机器上,数据需要通过网络发送,因此,为了保证所有数据节点的数据都是准确的,接收到数据的数据节点要向发送者发送“确认包”(ACK Packet),确认包沿着数据流管道逆流而上,从数据流管道依次经过各个数据节点并最终发往客户端,当客户端收到应答时,它将对应的分包从内部队列移除,如此循环执行上述过程,直到数据全部写完;

  6. 关闭实例对象:调用close()方法关闭输出流和文件系统:

    os.close(); 
    fs.close();	 
    

    在关闭输出流后,客户端不会再向输出流中写入数据,所以,当DFSOutputStream对象内部队列中的分包都收到应答以后,就可以使用ClientProtocol.complete()方法通知名称节点关闭文件,完成一次正常的写文件过程。

至此,HDFS的写入数据过程大概介绍完毕,如果其中我的理解有错误,请各位大佬帮忙纠正,至于HDFS的数据读取过程,具体内容请参照:初步理解HDFS数据读取过程 + Java代码实现

最后再附上一张大致的流程图:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值