在HDFS客户端实现中,最重要也是最复杂的一部分就是文件的读写操作。
打开文件
当客户端读取一个HDFS文件时,首先会调用DistributedFileSystem.open()方法打开这个文件,open方法首先会调用DFSCklient.open()方法创建HDFS文件对应的DFSInputStream输入流对象,然后构建一个HDFSDataInputSream对象包装DFSInputStream,最后将这个HDFSDataInputStream对象返回给客户端代码。
open()操作调用了两个RPC方法,分别是ClientProtocol.getBlockLocations()方法, 用于获取文件对应的所有数据块的位置信息;CientDatanodeProtocol.getReplicaVisibleLength()方法,用于获取Datanode上存储的某个数据块的真实长度。
读操作—–DFSInputStream实现
HDFS目前实现的读操作有三个层次,分别是网络读、短路读以及零拷贝读,他们的效率依次递增。
1.网络读:网络读是最基本的一种HDFS读,DFSClient和Datanode通过建立Socket连接传输数据。
2.短路读:当DFSClient和保存目标数据块的Datanode在同一个物理节点上时,DFSClient可以直接打开数据块副本文件读取数据,而不需要Datanode进程的转发。
3.零拷贝读:当DFSClient和缓存目标数据块的Datanode在同一个物理节点上时,DFSClient可以通过零拷贝的方式读取该数据块,大大提高了效率,而且即使在读取过程中该数据块被Datanode从缓存中移除了,该操作还可以退化为本地短路读,非常方便。
本地短路读取
当客户端和Datanode在同一个机器上时,客户端就可以绕过Datanode进程,直接从本地磁盘上读取数据。
当客户端向Datanode请求数据时,Datanode会打开文件以及该文件的元数据文件,将这两个文件的文件描述符通过domainSocket传给客户端,而不是将路径传给客户端。客户端拿到文件描述符后构造输入流,之后通过输入流直接读取磁盘上的块文件。通过这种方式直接绕过了Datanode进程的转发,提供了更好的读取性能。由于文件描述符时只读的,所以客户端不能修改收到的文件,同时由于客户端自身无法访问块文件所在的目录,所以它也就无法访问数据目录的其他文件了,从而提高了读数据的安全性。
UNIX domain Socket时一种进程间的通信方式,它使得同一台机器上的两个进程能够以Socket的方式进行通信。他带来的一大好处就是,两个进程除了可以传递普通数据外,还可以在进程间传递文件描述符。
短路读共享内存
了解了短路读取的概念之后,下边看看HDFS是如何实现这种模式的。在DFSClient中,使用ShortCircuitReplica类封装可以进行短路读取的副本。ShortCircuitReplica对象中包含了短路读取副本的数据块文件的输入流、校验文件输入流、短路读取副本在共享内存的槽位以及副本引用次数等信息。
当DFSClient和Datanode在同一台机器上时,需要一个共享内存段来维护所有的短路读取副本的状态,共享内存段中会有很多个槽位,每个槽位都记录了一个短路读取副本的信息,例如当前副本是否有效、锚的次数(缓存中的引用计数次数)等。
共享内存机制是由DFSClient和Datanode对同一个文件执行内存映射操作实现的。
因为MappedByteBuffer对象能让内存与物理文件的数据实时同步,所以DFSClient和Datanode进程会通过中间文件来交换数据,中间文件使得这两个进程的内存区域得到及时的同步。