总结了大神们的HDFS文件读取的好文章,自己总结了并且将会继续修改。
01、RPC(远程过程调用)
1. RPC概念
远程过程指的不是同一个进程的调用。它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
不能直接拿到远程机器的服务实例:比如loginController拿不到另一台主机loginService的实例,需要远程调用。一种实现:如Soap(http+xml)
1. RPC至少有两个过程。调用方(client),被调用方(server)。
2. client主动发起请求,调用指定ip和port的server中的方法,把调用结果返回给client。
3. 客户端调用服务端的方法,意味着调用服务端的对象中的方法。
4. 如果服务端的对象允许客户端调用,那么这个对象必须实现接口。
5. 如果客户端能够调用到服务端对象的方法,那么这些方法一定位于对象的接口中。
6. RPC是hadoop构建的基础。
2. 在Hadoop上的应用例子
NameNode和DataNode之间通信,进程间的远程调用。
1. 客户端获取NameNode的文件信息。(如客户端获取元数据信息,就是客户端通过RPC获得代理对象,调用NameNode的方法)
2. 客户端写副本到DataNode,只写一个,其余的副本都是NameNode告诉DataNode去复制。
3. DataNode和NameNode之间的心跳机制;DataNode定期汇报给NameNode自身的block信息(主要是当某个block损坏时,NameNode需要在元数据中更新副本信息)
hadoop和hbase中的大部分服务都是通过hadoop.ipc.RPC这个类来实现的。hadoop.ipc.RPC类中有两个重要的函数RPC.Builder和getProxy。RPC.Builder通过接口协议实现的实体来获取真正的server,getProxy获取远程访问的本地代理。
02、HDFS文件的读写详细过程
一、HDFS文件的读过程
/**
* 读取hdfs的文件
* @throws IOException
*/
@Test
public void testRead() throws IOException {
//运行MapReduce程序前都要初始化Configuration,该类主要是读取MapReduce系统配置信息
Configuration conf = new Configuration();
//JavaApi对HDFS的操作只要或得一个fileSystem对象就可以了。
FileSystem fs = FileSystem.get(conf);
Path path = new Path("hdfs://shujuelin00/user/centos/hello.txt");
//FileSystem 使用FSDataOutputStream 及FSDataInputStream来读写流的内容
FSDataInputStream fis = fs.open(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//从一个流里读取数据输出到另外一个流
IOUtils.copyBytes(fis,baos,1024);
fis.close();
System.out.println(new String(baos.toByteArray()));
}
FileSystem.get()方法:
在使用HDFS的API进行读写操作前都会对FileSystem进行初始化。并且让客户端创建namenode的通信代理代理用于进行RPC通信。
FileSystem fs = FileSystem.get(conf);
1. FileSystem.get(conf)获取文件对象的实例对象。通过配置文件的conf信息判断,返回一个DistributedFileSystem的class,再通过反射DistributedFileSystem的实例并返回。
2. DistributedFileSystem中有一个成员DFSClient,这个成员在初始化时,就是初始化自己的ClientProtocal代理对象(名称就是namenode),ClientProtocal是使用RPC框架和NN通信的客户端代理对象。
为了更好的理解,具体过程如下图的时序图:
图解HDFS读文件的数据流:
Ps:客户端直接连接datanode来读取数据,namenode来负责为每一个block提供最优的datanode,namenode仅仅处理block location的请求,这些元数据都加载在namenode的内存中,hdfs通过datanode集群可以承受大量客户端的并发访问。
如果在读数据的时候,DFSInputStream和datanode的通讯发生异常,就会尝试正在读的block的排第二近的datanode,并且会记录哪个datanode发生错误,剩余的blocks读的时候就会直接跳过该datanode。DFSInputStream也会检查block数据校验和,如果发现一个坏的block,就会先报告到namenode节点,然后DFSInputStream在其他的datanode上读该block的镜像。
DFSOutputStream内部原理
打开一个DFSOutputStream流,Client会写数据到流内部的一个缓冲区中,然后数据被分解成多个Packet,每个Packet大小为64k字节,每个Packet又由一组chunk和这组chunk对应的checksum(校验和数据)数据组成,默认chunk大小为512字节,每个checksum是对512字节数据计算的校验和数据。当Client写入的字节流数据达到一个 Packet的长度,这个Packet会被构建出来,然后会被放到队列dataQueue中,接着DataStreamer线程会不断地从dataQueue队列中取出Packet,发送到复制Pipeline中的第一个DataNode上,并将该Packet从dataQueue队列中移到ackQueue队列中。ResponseProcessor线程接收从Datanode发送过来的ack,如果是一个成功的ack,表示复制Pipeline中的所有Datanode都已经接收到这个Packet,ResponseProcessor线程将packet从队列ackQueue中删除。在发送过程中,如果发生错误,所有未完成的Packet都会从ackQueue队列中移除掉,然后重新创建一个新的Pipeline,排除掉出错的那些DataNode节点,接着DataStreamer线程继续从dataQueue队列中发送Packet。下面是DFSOutputStream的结构及其原理,如图所示:
我们从下面3个方面来描述内部流程:
1、创建Packet
Client写数据时,会将字节流数据缓存到内部的缓冲区中,当长度满足一个Chunk大小(512B)时,便会创建一个Packet对象,然后向该Packet对象中写Chunk Checksum校验和数据,以及实际数据块Chunk Data,校验和数据是基于实际数据块计算得到的。每次满足一个Chunk大小时,都会向Packet中写上述数据内容,直到达到一个Packet对象大小(64K),就会将该Packet对象放入到dataQueue队列中,等待DataStreamer线程取出并发送到DataNode节点。
2、 发送Packet
DataStreamer线程从dataQueue队列中取出Packet对象,放到ackQueue队列中,然后向DataNode节点发送这个Packet对象所对应的数据。
3、 接收ack
发送一个Packet数据包以后,会有一个用来接收ack的ResponseProcessor线程,如果收到成功的ack,则表示一个Packet发送成功。如果成功,则ResponseProcessor线程会将ackQueue队列中对应的Packet删除。
二、HDFS写过程
/**
* 写入hdfs
* @throws IOException
*/
@Test
public void testWrite() throws IOException {
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);//FileSystem分布式文件系统
Path path = new Path("hdfs://shujuelin00/user/centos/hadoop/hello.txt");
//FSDataOutputStream是HDFS的数据输出流
FSDataOutputStream fout = fs.create(new Path("hdfs://shujuelin00/user/centos/a.txt"));
fout.write("how are you?".getBytes());
fout.close();
}
整个写流程如下: