zoukankan      html  css  js  c++  java
  • 深入理解hadoop之HDFS

    深入理解hadoop之HDFS


       刚刚才写完关于mapreduce的一篇博文,趁热打铁接下来聊聊HDFS。本博文参考资料为HADOOP权威指南第3版完版,博文如有错漏之处,敬请指正。

      HDFS即Hadoop Distributed FileSystem,是hadoop旗舰机的文件系统。HDFS以流式数据访问模式来存储超大文件。有如下几个特点:超大文件;流式数据访问模式,即一次写入多次读取的访问模式;商用硬件,hadoop不需要运行在昂贵的商用硬件上面,对于庞大的集群来说,节点的故障概率是非常高的,而HDFS是为了让系统继续运行而不让用户感受到明显的中断;HDFS不适合地数据延迟的应用;不适宜大量的小文件;不支持多用户写入,任意修改文件的 操作。


    一、HDFS的一些概念

      1.block(块):文件在物理上是分块存储(block),块的大小可以通过配置参数( dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,之前的版本中是64M。分块的好处有二:1.文件的大小可以大于网络中任意一个磁盘的容量;2.使用抽象块而非整个文件作为存储单元。

      2.名称节点namenode,管理文件系统的命名空间,维护整个系统树以及整个系统树内的文件目录,这些信息就以镜像文件和编辑日志的形式存储在本地磁盘上。

      3.数据节点DataNode,存储并检索数据块,定期向namenode发送它们存储的块的列表

      4.DistributedFileSystem

             

      如上图所示, HDFS 也是按照 Master 和 Slave 的结构。分NameNode、 SecondaryNameNode、 DataNode 这几个角色。NameNode:是 Master 节点,是大领导。管理数据块映射;处理客户端的读写请求;配置副本策略;管理 HDFS 的名称空间;SecondaryNameNode:是一个小弟,分担大哥 namenode的一部分工作量;是 NameNode 的冷备份;合并 fsimage 和fsedits 然后再发给 namenode。DataNode: Slave 节点,奴隶,干活的。负责存储 client 发来的数据块 block;执行数据块的读写操作。热备份: b 是 a 的热备份,如果 a 坏掉。那么 b 马上运行代替a 的工作。冷备份: b 是 a 的冷备份,如果 a 坏掉。那么 b 不能马上代替a 工作。但是 b 上存储 a 的一些信息,减少 a 坏掉之后的损失。fsimage:元数据镜像文件(文件系统的目录树。)edits:元数据的操作日志(针对文件系统做的修改操作记录)namenode 内存中存储的是=fsimage+edits。SecondaryNameNode 负责定时默认 1 小时,从namenode 上,获取 fsimage 和 edits 来进行合并,然后再发送给 namenode。减少 namenode 的工作量。 

     


    二、java API接口访问

      在进行剖析HDFS文件系统读写之前,我们先来看看java API来访问hdfs的实例(hadoop2.x版本)。

      

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.net.URLConnection;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FSDataInputStream;
    import org.apache.hadoop.fs.FSDataOutputStream;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.IOUtils;
    import org.junit.Test;
    
    /**
     * 完成hdfs操作 
     */
    public class TestHDFS {
        /**
         * 读取hdfs文件
         */
        @Test
        public void readFile() throws Exception{
            //注册url流处理器工厂(hdfs)
            URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
            URL url = new URL("hdfs://192.168.231.201:8020/user/centos/hadoop/index.html");
            URLConnection conn = url.openConnection();
            InputStream is = conn.getInputStream();
            byte[] buf = new byte[is.available()];
            is.read(buf);
            is.close();
            String str = new String(buf);
            System.out.println(str);
        }
        
        /**
         * 通过hadoop API访问文件
         */
        @Test
        public void readFileByAPI() throws Exception{
            Configuration conf = new Configuration();
            conf.set("fs.defaultFS", "hdfs://192.168.231.201:8020/");
            FileSystem fs = FileSystem.get(conf) ;
            Path p = new Path("/user/centos/hadoop/index.html");
            FSDataInputStream fis = fs.open(p);
            byte[] buf = new byte[1024];
            int len = -1 ; 
            
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while((len = fis.read(buf)) != -1){
                baos.write(buf, 0, len);
            }
            fis.close();
            baos.close();
            System.out.println(new String(baos.toByteArray()));
        }
        
        /**
         * 通过hadoop API访问文件
         */
        @Test
        public void readFileByAPI2() throws Exception{
            Configuration conf = new Configuration();
            conf.set("fs.defaultFS", "hdfs://192.168.231.201:8020/");
            FileSystem fs = FileSystem.get(conf) ;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Path p = new Path("/user/centos/hadoop/index.html");
            FSDataInputStream fis = fs.open(p);
            IOUtils.copyBytes(fis, baos, 1024);
            System.out.println(new String(baos.toByteArray()));
        }
        
        /**
         * mkdir
         */
        @Test
        public void mkdir() throws Exception{
            Configuration conf = new Configuration();
            conf.set("fs.defaultFS", "hdfs://192.168.231.201:8020/");
            FileSystem fs = FileSystem.get(conf) ;
            fs.mkdirs(new Path("/user/centos/myhadoop"));
        }
        
        /**
         * putFile
         */
        @Test
        public void putFile() throws Exception{
            Configuration conf = new Configuration();
            conf.set("fs.defaultFS", "hdfs://192.168.231.201:8020/");
            FileSystem fs = FileSystem.get(conf) ;
            FSDataOutputStream out = fs.create(new Path("/user/centos/myhadoop/a.txt"));
            out.write("helloworld".getBytes());
            out.close();
        }
        
        /**
         * removeFile
         */
        @Test
        public void removeFile() throws Exception{
            Configuration conf = new Configuration();
            conf.set("fs.defaultFS", "hdfs://192.168.231.201:8020/");
            FileSystem fs = FileSystem.get(conf) ;
            Path p = new Path("/user/centos/myhadoop");
            fs.delete(p, true);
        }
    }

    三、HDFS剖析文件读取

      1.客户端首先通过FileSystem客户端的Open()方法来打开要进行读取的文件,然后DistributedFileSystem通过使用RPC来调用namenode以确定文件起始块的位置对于每一个块,namenode存有该块副本的datanode 的地址,DistributedFileSystem类返回一个FSDataInputStream对象给client并进行数据的读取,接下来客户端通过输入流来对这些数据节点来反复调用read()方法,第一个block读取完毕之后,寻找下一个block的最佳datanode,来读取数据,最后数据读取完毕关流,这些DataNode根据与客户端的距离来进行排序。具体过程如下图1:

            

                              (图1客户端读取HDFS数据)

      2.在读数据过程中,如果与Datanode的通信发生错误,DFSInputStream对象会尝试从下一个最佳节点读取数据,并且记住该失败节点, 后续Block的读取不会再连接该节点 读取一个Block之后,DFSInputStram会进行检验和验证,如果Block损坏,尝试从其他节点读取数据,并且将损坏的block汇报给Namenode。 客户端连接哪个datanode获取数据,是由namenode来指导的,这样可以支持大量并发的客户端请求,namenode尽可能将流量均匀分布到整个集群。Block的位置信息是存储在namenode的内存中,因此相应位置请求非常高效,不会成为瓶颈。


    四、HDFS剖析文件写入 

      1.对于文件写入的过程,我们要了解的是创建文件,写入文件,最后关闭文件。

      2.客户端通过DistributedFileSystem来调用create函数来创建新的文件;namenode来检查该文件是否存在以及是否有权限来创建该文件,检查通过就会创建文件,否则就会抛出异常,DisTributedFileSystem向客户端返回一个FSDataOutputStream对象,由此客户端就可以开始写入数据;客户端写入数据时DFSOutputStream将数据分为一个个的数据包,并将其写入数据队列,DataStreamer处理数据队列,根据DataNode列表的要求namenode来分配合适的新块来存储数据副本,这些节点存放同一个Block的副本,构成一个管道。 DataStreamer将packet写入到管道的第一个节点,第一个节点存放好packet之后,转发给下一个节点,下一个节点存放 之后继续往下传递。DFSOutputStream同时维护一个ack queue队列,等待来自datanode确认消息。当管道上的所有datanode都确认之后,packet从ack队列中移除;数据写入完毕,客户端close输出流。

                

     暂时写这么多吧,写累了,休息下。

  • 相关阅读:
    MySQL 复制表结构和表数据
    学习使用Guava Retryer
    Maven 常用工具类整理
    转 全面理解Javascript闭包和闭包的几种写法及用途
    Python的全局变量
    python的内存管理机制
    Python 隔离沙箱 virtualenv
    <script> 的defer和async
    高性能Javascript(2) DOM编程
    高性能Javascript(1)
  • 原文地址:https://www.cnblogs.com/bigdata-stone/p/9308435.html
Copyright © 2011-2022 走看看