HDFS数据流

来源:互联网 发布:centos 移动目录 编辑:程序博客网 时间:2024/05/17 01:00

1.文件读取


   步骤一:客户端通过FileSystem对象的open()方法来打开希望读取的文件,对于HDFS来说,这个对象是分布式文件系统的一个实例。

    步骤二:DistributedFileSystem通过使用RPC来调用namenode,以确定文件起始块的位置。

    步骤三:对于每一个块,namenode返回存有该块副本的datanode地址,这些datanode根据它们与客户端的距离来排序(根据网络集群拓扑)如果该客户端本身就是一个datanode(比如,在一个MapR额udece任务中),并保存有相应数据块的一个副本时,该节点就会从本地datanode读取数据。DistributedFileSystem类返回一个FSDataInputStream对象(一个支持文件定位的输入流)给客户端读取数据。FSDataInputStream类转而封装了DFSInputStream对象,该对象管理datanode和namenode的I/O,接着客户端对这个输入流调用read()方法。

    步骤四:存储着文件的起始几个块的datanode地址的DFSInputStream随即连接最近的datanode。通过对数据流反复调用read()方法,可以将数据从datanode传输到客户端。

    步骤五:到达块的末端时,DFSInputstream关闭与该datanode的连接,然后寻找下一个最佳的datanode。客户端只需要读取连续的流,并且对客户端都是透明的。

    步骤六:客户端从流中读取数据时,块是按照DFSInputStream与datanode新建连接的顺序读取的,它会根据需要询问namenode来检索下一批数据块的datanode的位置,一旦客户端完成读取,就对FSDataInputStream调用close()方法。

 

  注意:在读取数据的时候,如果DFSInputStream在与datanode通信时遇到错误,会尝试从这个块的另一个最邻近的datanode读取数据。它会记住那个故障的datanode,以保证以后不会反复读取该节点上后续的块。DFSInputStream也会通过校验和确认从datanode发来的数据是否完整,如果发现有损坏的块,就在DFSInputStream试图从其他datanode读取其副本之前通知namenode。

  这个设计重点是:namenode告知客户端每个块中的最佳datanode,并让客户端直接连接到该datanode检索数据。由于数据流分散在集群中的所有datanode,所以这种设计可以使HDFS可扩展到大量的并发客户端。同时,namenode只需要响应块位置的请求(这些信息存储在内存中,因而非常高效)无需响应数据请求,否则随着客户端的增长,namenode会很快成为瓶颈。

                                        网络拓扑与Hadoop

  在海量数据处理中,其主要的限制因素是节点之间数据的传输速率--带宽很稀缺。hadoop为此采用一个简单的方法:把网络看做一个树,两个节点间的距离是他们到最近共同祖先的距离总和。对于数据中心,机架和正在运行的节点,通常可设为等级,可用带宽依次递减:

                同一节点上的进程

                同一机架上的不同节点

                同一数据中心的不同机架上的节点

                不同数据中心中的节点


2.文件写入


   步骤一:客户端通过对DistributedFileSystem对象调用create()函数来创建新的文件

      步骤二:DistributedFileSystem对namenode创建一个RPC调用,在文件系统的命名空间中新建一个文件,此时该文件中没有相应的数据块。

      步骤三:namenode执行各种不同的检查以确保这个文件不存在以及客户端有新建该文件的权限。如果这些检查均通过,namenode就会为创建新文件记录一条记录;否则文件创建失败并向客户端抛出一个IOEXception异常。DistributedFileSystem向客户端返回一个FSDataOutputStream对象,由此客户端可以开始写入数据。就向读取数据一样,FSDataOutputStream封装了DFSOutputStream对象,该对象负责处理datanode和namenode之间的通信。

      步骤四:在客户端写入数据时,DFSOutputStream将它分成一个个的数据包,并写入内部队列,称为“数据队列”。DataStreamer处理数据队列,它的责任是根据datanode列表来要求namenode分配合适的新块来存储数据副本。这一组datanode构成一个管线----我们假设副本数为3,所以管线中有3个节点。DataStreamer将数据包流式传输到管线中的第一个datanode,该datanode存储数据包并将它发送到管线中的第二个datanode。同理第二个datanode存储的数据包发送给管线中的第3个datanode。

      步骤五:DFSOutputStream也维护着一个内部数据包队列来等待datanode的收到确认回执,称问“确认回执”,收到管道中的所有datanode的确认信息后,该数据包才会从确认队列中删除。


  注意:如果在写入期间datanode发生故障,则执行以下操作(对写入数据的客户端是透明的)。首先关闭管线,确认把队列中的所有的数据包添加回数据队列的最前端,以确保故障节点下游的datanode不会漏掉任何一个数据包。为存储在另一正常datanode的当前数据块指定一个新的标示,并将该标示传送给namenode,以便故障的datanode在恢复后可以删除存储的部分数据块。从管线中删除故障数据节点并且把余下的数据块写入管线的另外两个正常的datanode。namenode注意到副本量不足时,会在另一个节点上创建一个新的副本,后续的数据块继续正常接受处理。

        在一个块被写入期间可能有多个datanode发生故障但是这是很少见的,只要写入了dfs.replication.min的副本数(默认为1)写入就会成功。

      步骤六:客户端完成数据写入后,就会对数据流调用close()方法

      步骤七:该操作将剩余的所有的数据包写入datanode管线,并在联系到namenode且发送文件写入完成信号之前,等待确认。

namenode已经知道文件有哪些块组成(通过DataStreamer请求分配数据块)所以它在返回成功前只需要等待数据块进行最量的复制。


                                              复本怎么放 

   Hadoop的默认的布局策略是在运行客户端的节点上放第1个复本(如果客户端运行在集群之外,就随机选取一个节点,不过系统会避免挑选那些存储太慢或太忙的节点)第2个复本存放在与第一个不同的且随机另外选择的机架中节点上(离架),第3个复本与第2个复本放在同一个机架上,且随机选择另一个节点,其他复本放在集群中随机选择的节点上,不过系统会尽量避免在同一个机架上放太多复本。


代码:

public class HDFSDemo {
    
    private FileSystem fs;

    @Before
    public void init() throws Exception {
        //首先创建FileSystem实现类(工具类)
        fs = FileSystem.get(new URI("hdfs://GY:9000"),new Configuration());
    }

    public static void main(String[] args) throws Exception {
        /**
         * 1.根据文件系统的类型找到FileSystem实现类DistributedFileSystem(从配置信息中得到的),通过反射,实例化该对象。
         * 2.DistributedFileSystem持有DFSClient的引用,DFSClient持有代理对象(ClientProtocol)引用
         *        ClientProtocol接口的实现类是在服务器端实现的(通过hadoop的RPC机制得到服务端的代理对象)
         */
        FileSystem fs = FileSystem.get(new URI("hdfs://GY:9000"),new Configuration());
        /**
         * 在open里面抓取块的信息,客户端通过hadoop的RPC机制跟Namenode的进程进行通信,将要下载的文件
         * 作为方法的参数传给Namenode进程里面的某个类,Namenode查询的元数据信息作为返回值返回给客户端
         * 客户端把块的信息作为流的成员变量
         */
        InputStream in =fs.open(new Path("/db"));
        OutputStream out = new FileOutputStream("e://db");
        IOUtils.copyBytes(in, out, 4096, true);  
    }

    @Test
    public void testUpLoad() throws Exception {
        //读取本地文件系统的文件,返回输入流
        InputStream in = new FileInputStream("c://pingback.db");
        //在HDFS上创建一个文件,返回输出流
        OutputStream out =fs.create(new Path("/db"));
        //输入-》输出
        IOUtils.copyBytes(in, out, 4096, true);
    }
    
    @Test
    public void testDownload() throws Exception {
        //从HDFS上下载到本地文件系统上
        fs.copyToLocalFile(new Path("/jdk1.7"),new Path("c://"));
    }
    
    @Test
    public void testMkdir() throws Exception {
        boolean flag = fs.mkdirs(new Path("/GY"));
        System.out.println(flag);
    }
    
    @Test
    public void testDelete() throws Exception {
        boolean flag = fs.delete(new Path("/jdk1.7"), false);
        System.out.println(flag);
    }

}

0 0
原创粉丝点击