• 大数据入门第六天——HDFS详解


    一、概述

      1.HDFS中的角色

        Block数据:

          HDFS中的文件在物理上是分块存储(block),块的大小可以通过配置参数( dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,之前的版本中是64M 

    1. 基本存储单位,一般大小为64M(配置大的块主要是因为:1)减少搜寻时间,一般硬盘传输速率比寻道时间要快,大的块可以减少寻道时间;2)减少管理块的数据开销,每个块都需要在NameNode上有对应的记录;3)对数据块进行读写,减少建立网络的连接成本)

    2. 一个大文件会被拆分成一个个的块,然后存储于不同的机器。如果一个文件少于Block大小,那么实际占用的空间为其文件的大小

    3. 基本的读写S#x5355;位,类似于磁盘的页,每次都是读写一个块

    4. 每个块都会被复制到多台机器,默认复制3份

          NameNode:

          负责管理整个文件系统的元数据

        Secondary NameNode:

           定时与NameNode进行同步(定期合并文件系统镜像和编辑日&#x#x5FD7;,然后把合并后的传给NameNode,替换其镜像,并清空编辑日志,类似于CheckPoint机制),但NameNode失效后仍需要手工将其设置成主机——namenode的冷备份

          关于这点,可以参考http://blog.csdn.net/scgaliguodong123_/article/details/46335427

        DataNode:

          负责管理用户的文件数据块    

         文件会按照固定的大小(blocksize)切成若干块(由上传的客户端进行切块处理,这样不大于128M切块大小的实际是多少就是多少)后分布式存储在若干台datanode上

          Datanode会定期向Namenode汇报自身所保存的文件block信息,而namenode则会负责保持文件的副本数量

      详细角色信息,参考https://www.w3cschool.cn/hadoop/xvmi1hd6.html

      漫画式的讲解,参考https://www.cnblogs.com/raphael5200/p/5497218.html

    二、读写数据流程

      简要的说明参考上文漫画式讲解处

      专业深入讲解,参考https://www.cnblogs.com/codeOfLife/p/5375120.html

      1.写数据流程

    1、根namenode通信请求上传文件,namenode检查目标文件是否已存在,父目录是否存在
    2、namenode返回是否可以上传
    3、client请求第一个 block该传输到哪些datanode服务器上
    4、namenode返回3个datanode服务器ABC
    5、client请求3台dn中的一台A上传数据(本质上是一个RPC调用,建立pipeline),A收到请求会继续调用B,然后B调用C,将真个pipeline建立完成,逐级返回客户端
    6、client开始往A上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位,A收到一个packet就会传给B,B传给C;A每传一个packet会放入一个应答队列等待应答
    7、当一个block传输完成之后,client再次请求namenode上传第二个block的服务器。

      图解请参考上文

      注意:HDFS只允许修改文件名/文件追加等,无法直接修改原来的文件!

      2.读数据流程

    1、跟namenode通信查询元数据,找到文件块所在的datanode服务器
    2、挑选一台datanode(就近原则,然后随机)服务器,请求建立socket流
    3、datanode开始发送数据(从磁盘里面读取数据放入流,以packet为单位来做校验)
    4、客户端以packet为单位接收,现在本地缓存,然后写入目标文件

     三、HDFS元数据管理与解析

      1.元数据管理原理

        元数据分类:

    • 第一类是文件和目录自身的属性信息,例如文件名、目录名、父目录信息、文件大小、创建时间、修改时间等。
    • 第二类记录文件内容存储相关信息,例如文件块情况、副本个数、每个副本所在的Data Node 信息等。
    • 第三类用来记录HDFS中所有Data Node信息,用于Data Node管理。

        存储机制:

          A、内存中有一份完整的元数据(内存meta data)

          B、磁盘有一个“准完整”的元数据镜像(fsimage)文件(在namenode的工作目录中),整个运行过程中,fsimage是不会改变的!editslog的更新会同步到内存

          C、用于衔接内存metadata和持久化元数据镜像fsimage之间的操作日志(edits文件)注:当客户端对hdfs中的文件进行新增或者修改操作,操作记录首先被记入edits日志文件中,当客户端操作成功后,相应的元数据会更新到内存meta.data中

        更多参考http://blog.csdn.net/xiaming564/article/details/23165253

            http://blog.csdn.net/chenkfkevin/article/details/61196409

         这里就能很清楚的知道secondary namenode的工作原理了!

         关于元数据的存取流程与分析,参考网友的白话讲解http://blog.csdn.net/lepton126/article/details/53183037

         2.修改工作目录

        之前我们配置过hadoop.tmp.dir来设置临时目录,这里可以通过以下参数设置HDFS工作目录

    配置文件:hdfs-site.xml
    参数名:dfs.namenode.name.dir    
    格式:file://${hadoop.tmp.dir}/dfs/name    
    说明:Determines where on the local filesystem the DFS name node should store the name table(fsimage). 
    If this is a comma-delimited list of directories then the name table is replicated in all of the directories, for redundancy. 示例: <property> <name>dfs.namenode.name.dir</name> <value>/home/hadoop/name1,/home/hadoop/name2</value> </property>

      //当然,dfs.datanode.data.dir也是可以配置的,这样重新格式化后就可以重新使用工作目录了!点击查看对比

      注:如果还在使用dfs.name.dir/dfs.data.dir,请查看官网配置的deprecated properties

         3.元数据各目录解析

          详细解析参考https://www.iteblog.com/archives/967.html

                   http://blog.csdn.net/opensure/article/details/51452058

    四、HDFS的Java-API操作

       完整API参考官网:http://hadoop.apache.org/docs/current/api/

      1.基本的增删改查API:

    package com.hdfs.demo;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.*;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.net.URI;
    
    /**
     * HDFS客户端demo
     *
     * @author zcc ON 2018/1/28
     **/
    public class HdfsClientDemo {
        FileSystem fs = null;
        @Before
        public void init() throws Exception{
            // 如配置完环境变量,下行可省略
            // System.setProperty("hadoop.home.dir", "F:\work\hadoop-2.6.4");
            Configuration conf = new Configuration();
            // 配置文件系统(注意hosts的配置)
            // conf.set("fs.defaultFS","hdfs://mini1:9000");
            // 拿到一个操作的客户端实例对象(此处使用3个参数则上一行省略)
            fs = FileSystem.get(new URI("hdfs://mini1:9000"),conf,"hadoop");
        }
        @Test
        public void testUpload() throws Exception{
            // 就对应get的别名
            fs.copyFromLocalFile(new Path("F:/c.log"),new Path("/c.log.copy"));
            // 关闭
            fs.close();
        }
        @Test
        public void testDelete() throws Exception{
            // 第一个是Path,第二个为是否递归删除
            boolean b = fs.delete(new Path("/c.log.copy"), true);
            System.out.println("删除状态:" + b);
            fs.close();
        }
        @Test
        public void testList() throws Exception{
            /*
            一般而言,大数据方面使用迭代器场景居多,因为Iterator它本身并不存数据(可以查看源码)
            它只是提供了几个简单的方法帮你去取数据,而使用ArrayList则是直接把数据拿过来了,大数据
            量下不适合
             */
            RemoteIterator<LocatedFileStatus> iterator = fs.listFiles(new Path("/"), true);
            while (iterator.hasNext()) {
                LocatedFileStatus next =  iterator.next();
                System.out.println("Path:" + next.getPath());
                System.out.println("BlockSize" + next.getBlockSize());
                System.out.println("Name:" + next.getPath().getName());
            }
    
    //        Path:hdfs://mini1:9000/1.txt
    //        BlockSize134217728
    //        Name:1.txt
            fs.close();
        }
    
        /**
         * 既可以遍历文件,又可以遍历文件夹
         * @throws Exception
         */
        @Test
        public void testList2() throws Exception{
            FileStatus[] files = fs.listStatus(new Path("/"));
            for (FileStatus file : files) {
                System.out.println(file.getPath().getName());
                if (file.isFile()) {
                    System.out.println("it is a file");
                }
            }
        }
    }

       这里引入Java中迭代器的机制浅谈,供参考https://www.cnblogs.com/hasse/p/5024193.html

      更多实例,参考:http://blog.csdn.net/litianxiang_kaola/article/details/70983904

      2.流操作API

        相对那些封装好的方法而言的更底层一些的操作方式上层那些mapreduce   spark等运算框架,去hdfs中获取数据的时候,就是调的这种底层的api

    package com.hdfs.stream;
    
    import org.apache.commons.io.IOUtils;
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.*;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.net.URI;
    
    /**
     * HDFS的流API操作
     *
     * @author zcc ON 2018/1/30
     **/
    public class HdfsStreamAccess {
        FileSystem fs = null;
        @Before
        public void init() throws Exception{
            Configuration conf = new Configuration();
            fs = FileSystem.get(new URI("hdfs://mini1:9000"),conf,"hadoop");
        }
    
        /**
         * 通过流的操作,就可以为上层MR程序提供服务,例如跑任务只需要跑60M,这样就不用通过
         * 之前的直接得到一整个文件的复杂处理
         * 而建立文件夹等直接通过简单API即可!
         * @throws Exception
         */
        @Test
        public void testUpload() throws Exception{
            // 输出到HDFS
            FSDataOutputStream outputStream = fs.create(new Path("/angle"), true);
            // 本地输入流
            FileInputStream inputStream = new FileInputStream("F:\c.log");
            // 通过IOUtils进行拷贝(使用更加通用的IOUtils)
            IOUtils.copy(inputStream, outputStream);
        }
        @Test
        public void testDownload() throws Exception{
            FSDataInputStream inputStream = fs.open(new Path("/angle"));
            FileOutputStream outputStream = new FileOutputStream("F:\c-download.log");
            IOUtils.copy(inputStream, outputStream);
        }
    
        /**
         * 指定随机长度读取
         */
        @Test
        public void testRandomAccess() throws Exception{
            FSDataInputStream inputStream = fs.open(new Path("/angle"));
            inputStream.seek(12);
            FileOutputStream outputStream = new FileOutputStream("F:\c-random.log");
            // 从12字节读到末尾(也可以通过while结合自定义的count等来控制读文件的大小)
            // 后续会避免读取读到单词一半这样的问题
    //        IOUtils.copy(inputStream, outputStream);
            IOUtils.copyLarge(inputStream, outputStream, 12, 100);
        }
    
        /**
         * 以下模拟实现:获取一个文件的所有block位置信息,然后读取指定block中的内容
         * @throws IllegalArgumentException
         * @throws IOException
         */
        @Test
        public void testCat() throws IllegalArgumentException, IOException {
    
            FSDataInputStream in = fs.open(new Path("/weblog/input/access.log.10"));
            //拿到文件信息
            FileStatus[] listStatus = fs.listStatus(new Path("/weblog/input/access.log.10"));
            //获取这个文件的所有block的信息
            BlockLocation[] fileBlockLocations = fs.getFileBlockLocations(listStatus[0], 0L, listStatus[0].getLen());
            //第一个block的长度
            long length = fileBlockLocations[0].getLength();
            //第一个block的起始偏移量
            long offset = fileBlockLocations[0].getOffset();
    
            System.out.println(length);
            System.out.println(offset);
    
            //获取第一个block写入输出流
    //        IOUtils.copyBytes(in, System.out, (int)length);
            byte[] b = new byte[4096];
    
            FileOutputStream os = new FileOutputStream(new File("d:/block0"));
            while(in.read(offset, b, 0, 4096)!=-1){
                os.write(b);
                offset += 4096;
                if(offset>=length) return;
            };
            os.flush();
            os.close();
            in.close();
        }
    }

     五、案例:shell脚本日志采集

      采用shell编写的脚本如下:

    #!/bin/bash
    
    #set java env
    export JAVA_HOME=/home/hadoop/app/jdk1.7.0_51
    export JRE_HOME=${JAVA_HOME}/jre
    export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
    export PATH=${JAVA_HOME}/bin:$PATH
    
    #set hadoop env
    export HADOOP_HOME=/home/hadoop/app/hadoop-2.6.4
    export PATH=${HADOOP_HOME}/bin:${HADOOP_HOME}/sbin:$PATH
    
    
    #版本1的问题:
    #虽然上传到Hadoop集群上了,但是原始文件还在。如何处理?
    #日志文件的名称都是xxxx.log1,再次上传文件时,因为hdfs上已经存在了,会报错。如何处理?
    
    #如何解决版本1的问题
    #       1、先将需要上传的文件移动到待上传目录
    #    2、在讲文件移动到待上传目录时,将文件按照一定的格式重名名
    #        /export/software/hadoop.log1   /export/data/click_log/xxxxx_click_log_{date}
    
    
    #日志文件存放的目录
    log_src_dir=/home/hadoop/logs/log/
    
    #待上传文件存放的目录
    log_toupload_dir=/home/hadoop/logs/toupload/
    
    
    #日志文件上传到hdfs的根路径
    hdfs_root_dir=/data/clickLog/20151226/
    
    #打印环境变量信息
    echo "envs: hadoop_home: $HADOOP_HOME"
    
    
    #读取日志文件的目录,判断是否有需要上传的文件
    echo "log_src_dir:"$log_src_dir
    ls $log_src_dir | while read fileName
    do
        if [[ "$fileName" == access.log.* ]]; then
        # if [ "access.log" = "$fileName" ];then
            date=`date +%Y_%m_%d_%H_%M_%S`
            #将文件移动到待上传目录并重命名
            #打印信息
            echo "moving $log_src_dir$fileName to $log_toupload_dir"xxxxx_click_log_$fileName"$date"
            mv $log_src_dir$fileName $log_toupload_dir"xxxxx_click_log_$fileName"$date
            #将待上传的文件path写入一个列表文件willDoing
            echo $log_toupload_dir"xxxxx_click_log_$fileName"$date >> $log_toupload_dir"willDoing."$date
        fi
        
    done
    #找到列表文件willDoing
    ls $log_toupload_dir | grep will |grep -v "_COPY_" | grep -v "_DONE_" | while read line
    do
        #打印信息
        echo "toupload is in file:"$line
        #将待上传文件列表willDoing改名为willDoing_COPY_
        mv $log_toupload_dir$line $log_toupload_dir$line"_COPY_"
        #读列表文件willDoing_COPY_的内容(一个一个的待上传文件名)  ,此处的line 就是列表中的一个待上传文件的path
        cat $log_toupload_dir$line"_COPY_" |while read line
        do
            #打印信息
            echo "puting...$line to hdfs path.....$hdfs_root_dir"
            hadoop fs -put $line $hdfs_root_dir
        done    
        mv $log_toupload_dir$line"_COPY_"  $log_toupload_dir$line"_DONE_"
    done
    脚本文件

      加入定时任务调度,参考大数据之Linux基础

  • 相关阅读:
    Hive学习笔记三
    spark之RDD练习
    Python之QRCode
    Zookeeper学习笔记一
    Docker学习笔记一
    MapReduce异常:java.lang.ClassCastException: interface javax.xml.soap.Text
    Hive学习笔记二
    Hive学习笔记一
    5.线性回归算法
    4.K均值算法
  • 原文地址:https://www.cnblogs.com/jiangbei/p/8376657.html
Copyright © 2020-2023  润新知