Kuncle

God I pray to prosper thee.

HDFS的读写

应用程序通过创建新文件以及向新文件写数据的方式,给HDFS系统添加数据。文件关闭以后,被写入的数据就无法再修改或者删除,只有以“追加”方式重新打开文件后,才能再次为文件添加数据。HDFS采用单线程写,多线程读的模式。

  • 读文件 hdfs_read
    1. 首先调用FileSystem对象的open方法,其实是一个DistributedFileSystem的实例
    2. DistributedFileSystem通过rpc获得文件的第一批个block的locations,同一block按照replaction会返回多个locations,这些locations按照hadoop拓扑结构排序,距离客户端近的排在前面.
    3. 前两步会返回一个FSDataInputStream对象,该对象会被封装成DFSInputStream对象,DFSInputStream可以方便的管理datanode和namenode数据流。客户端调用read方法,DFSInputStream最会找出离客户端最近的datanode并连接。
    4. 数据从datanode源源不断的流向客户端。
    5. 如果第一块的数据读完了,就会关闭指向第一块的datanode连接,接着读取下一块。这些操作对客户端来说是透明的,客户端的角度看来只是读一个持续不断的流。
    6. 如果第一批block都读完了,DFSInputStream就会去namenode拿下一批blocks的location,然后继续读,如果所有的块都读完,这时就会关闭掉所有的流。
      如果在读数据的时候,DFSInputStream和datanode的通讯发生异常,就会尝试正在读的block的排第二近的datanode,并且会记录哪个datanode发生错误,剩余的blocks读的时候就会直接跳过该datanode。DFSInputStream也会检查block数据校验和checkSum,如果发现一个坏的block,就会先报告到namenode节点,然后DFSInputStream在其他的datanode上读该block的镜像
      该设计的方向就是客户端直接连接datanode来检索数据并且namenode来负责为每一个block提供最优的datanode,namenode仅仅处理block location的请求,这些信息都加载在namenode的内存中,hdfs通过datanode集群可以承受大量客户端的并发访问。

    注:DataNode中所包含的文件块备份可能会因为内存、磁盘或者网络的错误而造成损坏。为了避免这种错误的形成,HDFS会为其文件的每个数据块生成并存储一份Checksum(总和检查)。Checksum主要供HDFS客户端在读取文件时检查客户端,DataNode以及网络等几个方面可能造成的数据块损坏。当客户端开始建立HDFS文件时,会检查文件的每个数据块的checksum序列,并将其与数据一起发送给DataNode。 DataNode则将checksum存放在文件的元数据文件里,与数据块的具体数据分开存放。当HDFS读取文件时,文件的每个块数据和checksum均被发送到客户端。客户端会即时计算出接受的块数据的checksum, 并将其与接受到的checksum进行匹配。如果不匹配,客户端会通知NameNode,表明接受到的数据块已经损坏,并尝试从其他的DataNode节点获取所需的数据块。
    HDFS允许客户端从正在进行写操作的文件中读取数据。当进行这样的操作时,目前正在被写入的数据块对于NameNode来说是未知的。在这样的情况下,客户端会从所有数据块备份中挑选一个数据块,以这个数据块的最后长度作为开始读取数据之前的数据长度。
    HDFS I/O的设计是专门针对批处理系统进行优化的,比如MapReduce系统,这类系统对顺序读写的吞吐量都有很高的要求。针对于那些需要实时数据流以及随机读写级别的应用来说,系统的读/写响应时间还有待于优化,目前正在做这方面的努力。

  • 写文件 hdfs_write
    1. 客户端通过调用DistributedFileSystem的create方法创建新文件
    2. DistributedFileSystem通过RPC调用namenode去创建一个没有blocks关联的新文件,创建前,namenode会做各种校验,比如文件是否存在,客户端有无权限去创建等。如果校验通过,namenode就会记录下新文件,否则就会抛出IO异常.
    3. 前两步结束后会返回FSDataOutputStream的对象,象读文件的时候相似,FSDataOutputStream被封装成DFSOutputStream.DFSOutputStream可以协调namenode和datanode。客户端开始写数据到DFSOutputStream,DFSOutputStream会把数据切成一个个小packet,然后排成队列data quene。
    4. DataStreamer会去处理接受data quene,他先问询namenode这个新的block最适合存储的在哪几个datanode里(参考第二小节),比如重复数是3,那么就找到3个最适合的datanode,把他们排成一个pipeline.DataStreamer把packet按队列输出到管道的第一个datanode中,第一个datanode又把packet输出到第二个datanode中,以此类推。
    5. DFSOutputStream还有一个对列叫ack quene,也是有packet组成,等待datanode的收到响应,当pipeline中的所有datanode都表示已经收到的时候,这时akc quene才会把对应的packet包移除掉。如果在写的过程中某个datanode发生错误,会采取以下几步:1) pipeline被关闭掉;2)为了防止防止丢包ack quene里的packet会同步到data quene里;3)把产生错误的datanode上当前在写但未完成的block删掉;4)block剩下的部分被写到剩下的两个正常的datanode中;5)namenode找到另外的datanode去创建这个块的复制。当然,这些操作对客户端来说是无感知的。
    6. 客户端完成写数据后调用close方法关闭写入流
    7. DataStreamer把剩余得包都刷到pipeline里然后等待ack信息,收到最后一个ack后,通知datanode把文件标示为已完成。

    注:HDFS客户端需要首先获得对文件操作的授权,然后才能对文件进行写操作。在此期间,其他的客户端都不能对该文件进行写操作。被授权的客户端通过向NameNode发送心跳信号来定期更新授权的状态。当文件关闭时,授权会被回收。文件授权期限分为软限制期和硬限制期两个等级。当处于软限制期内时,写文件的客户端独占对文件的访问权。当软限制过期后,如果客户端无法关闭文件,或没有释放对文件的授权,其他客户端即可以预定获取授权。当硬限制期过期后(一小时左右),如果此时客户端还没有更新(释放)授权,HDFS会认为原客户端已经退出,并自动终止文件的写行为,收回文件控制授权。文件的写控制授权并不会阻止其他客户端对文件进行读操作。因此一个文件可以有多个并行的客户端对其进行读取。
    客户端执行write操作后,写完的block才是可见的,正在写的block对客户端是不可见的,只有调用hsync方法,客户端才确保该文件被写操作已经全部完成,当客户端调用close方法时会默认调用hsync方法。是否需要手动调用取决你根据程序需要在数据健壮性和吞吐率之间的权衡
    HDFS文件由多个文件块组成。当需要创建一个新文件块时,NameNode会生成唯一的块ID,分配块空间,以及决定将块和块的备份副本存储到哪些DataNode节点上。DataNode节点会形成一个管道,管道中DataNode节点的顺序能够确保从客户端到上一DataNode节点的总体网络距离最小。文件的则以有序包(sequence of packets)的形式被推送到管道。应用程序客户端创建第一个缓冲区,并向其中写入字节。第一个缓冲区被填满后(一般是64 KB大小),数据会被推送到管道。后续的包随时可以推送,并不需要等前一个包发送成功并发回通知(这被称为“未答复发送”——译者注)。不过,这种未答复发送包的数目会根据客户端所限定的“未答复包窗口”(outstanding packets windows)的大小进行限制。