打怪升级之小白的大数据之旅(四十五)<认识HDFS与常用操作>

打怪升级之小白的大数据之旅(四十五)

认识HDFS与常用操作

上次回顾

  • 上一章常见异常就不说了,我就大概说一下整个Hadoop的搭建吧,首先我们先对单台服务器进行配置
  • 第一步:我们需要创建一台最小软件的CentOS系统,并进行一些基本配置,例如IP设置,创建用户,主机名与hosts文件设置等,然后进行服务器的克隆,我们安装Hadoop最小要求,准备三台服务器,然后重复前面的基本配置
  • 需要下载一些常见的插件,如VIM以及安装一下JDK与Hadoop并设置好环境变量
  • 第三步: 我们需要进行SSH无密登录的配置,然后对我们的JDKHadoop等文件进行分发
  • 第四步:我们要进行集群配置前,需要进行配置文件的修改 ,并对我们的集群进行规划,例如一台服务器运行NameNode服务,一台服务器运行ResourceManager。一台服务器运行SecondaryNameNode
  • 第五步:格式化一下hdfs,然后在NameNode服务器上启动HDFS,在ResourceManager上启动yarn
  • 第六步:接下来就是进行测试,利用官方的案例和web连接测试一下是否正常,然后再进行一些工作中需要用到的一些服务器配置,例如历史服务器、日志的聚集以及时间同步等
  • 好了,整个Hadoop集群成功搭建完,接下来就是对它的组成模块进行挨个讲解,首先是HDFS

HDFS概述

HDFS整体架构

介绍HDFS前,我先放一组图片,便于大家理解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面的图是星爷鹿鼎记电影中的截图,我觉得它正好能说明HDFS的架构:

  • 图中所说的目录对应的就是HDFS中的NameNode,它用于存储文件的元数据,元数据就是记录文件的名称、文件属性(生成时间、大小、文件权限等)、文件目录结构等信息,有了它,我们就可以找到我们需要的数据
  • 每一个功法分为上中下部,在DateNode就是在本地文件系统中存储文件的块数据,它会将一个文件进行切割,分别存储在不同的服务器中
  • 每过一段时间,就会有大牛对武功秘籍进行修改优化,在HDFS中就是SecondaryNameNode,它每隔一段时间就会对NameNode元数据进行备份

HDFS的定义与优缺点

定义

  • 知道了HDFS的基本概念,我就要相对官方的解释一下HDFS是什么了,它是众多分布式文件管理系统中的一种
  • 分布式文件管理系统就是指,将多个服务器当作一个整体;HDFS就是这个整体的管理员,当我们需要对数据进行存储时,就会将该文件进行分割成一块一块的数据,分别存储再各个服务器中
  • 正因为这个特点,HDFS最大的用途就是适合一次写入,多次读取的场景,经常用于数据分析

优缺点
正因为它是分布式的文件管理系统,它的优缺点就比较明显

  • 优点
    • 高容错性,它在存储文件时,默认会创建三个副本,用于备份数据,因此其中一个服务器奔溃,并不会导致整个数据的丢失
    • 适合处理大数据
      • 在数据规模层次,它能够处理数据的规模可以达到TB甚至PB级别
      • 在文件规模层级,它能够处理百万规模以上的文件数量
    • 因为它是分布式存储,所以对硬件的要求比较低,我们可以构建在廉价的机器上,通过多副本机制提高可靠性
  • 缺点
    • 因为它有一个目录NameNode,并且数据都是分块存储在不同服务器上,所以它做不到毫秒级数据访问
    • 同样的,它存储小文件时,NameNode同样会存储目录和块信息,小文件会占用NameNode的内存,是一种资源浪费
    • 在HDFS中有一个规定,文件存储的寻址时间越接近读取时间,性能越好,而小文件的寻址时间会超过读取时间,它违反了HDFS的设计目标
    • 不支持并发写入,文件随机修改
      • 因为要对文件进行分块,所以不能使用多个线程同时写
      • HDFS只支持数据的追加,不支持文件的随机修改

再议HDFS的整体架构

首先看一下几乎介绍HDFS都要发的架构图
在这里插入图片描述
优缺点以及整体的架构理解了,下面就要完整的将HDFS各个组件的功能进行说明

NameNode(nn)

NameNode是一个管理者,管理整个HDFS

  • NameNode管理HDFS的名称空间
  • 配置副本策略
  • 管理数据块(Block)映射信息
  • 处理客户端读写请求

DataNode(dn)

DataNode是小弟,它主要执行实际的操作

  • 存储实际的数据库
  • 执行数据块的读/写操作

SecondaryNameNode(2nn)

SecondaryNameNode就是秘书,主要更新元数据(例如目录等信息)

  • 辅助NameNode,主要工作是定期合并Fsimage和Edits,并推送给NameNode
  • 在紧急情况下,可以辅助恢复NameNode
  • 当NameNode挂掉的时候,并不能马上替换NameNode提供服务(因为它是秘书)

Client

它是客户端,是用户,主要是上传下载数据

  • 文件切分,文件长传HDFS时,Client会将文件切分成一个一个的Block,然后进行上传
  • 与NameNode交互,获取文件的位置信息
  • 与DataNode交互,读取或写入数据
  • Client提供一些命令来管理HDFS,比如HDFS的增删改查操作

HDFS文件块大小

  • HDFS中的文件在服务器上是分块存储数据的(block),块的大小默认是128M,如果该文件大小超过128m,就会分成两块,还是超128,就继续切割
  • 块的大小可以通过配置参数来规定,一般我们默认设置就好了

设置块大小的原理

  • HDFS中,如果寻址时间约为10ms,即查找到目标数据块的时间为10ms
  • 寻址时间为传输时间的1%时,就是最佳状态,因此传输时间=10ms/0.01 = 1s,就是说,传输文件的时间是1s,寻址时间为10ms时,就是HDFS的最佳状态
  • 目前的磁盘传输速率普遍是100MS/s
  • 所以1s*100MB=100MB
  • 上面的这个知识点就是为了说明HDFS块的大小设置取决于磁盘的传输速率
  • 当我们的HDFS块设置的太小,那么就会增加寻址的时间,程序会一直在找块的开始位置
  • 如果块设置的太大,从磁盘传输数据的时间就会明显大于这个块开始位置的所需时间,这就会导致程序在处理这块数据时变得非常慢

这个不理解没关系,后面接触多了就懂了,我们就需要记住一句话:HDFS块的大小设置主要取决于磁盘的传输速率

HDFS的Shell操作

  • 其实在前面我们使用过类似的操作,例如在本地运行模式时,我们使用的hadoop jar ....还有我们集群搭建完测试时的 hadoop fs xxx,接下来就正式学习一下HDFS的shell操作
  • 基本语法
hadoop fs 具体的命令 或者 hdfs dfs 具体命令
  • 我建议使用hadoop fs,因为它里面还 包含了hadoop的一些命令,更加全面,当我们输入 hadoop fs时就会输出所有的具体的命令,我们可以进行查看,这里就不演示了
  • 在进行下面常用命令前,一定记得启动了Hadoop集群
  • 我所有的操作都是hadoop102服务器哈,并且下面的命令都是在~家目录下进行测试
  • 另外下面的/input指的是我们HDFS下的/input文件夹

常用命令

上传

-moveFromLocal 从本地剪切文件并粘贴到HDFS中

# 创建一个本地文件
touch wukong.txt
# 将本地文件剪贴到HDFS中
hadoop fs -moveFromLocal ./wukong.txt /input
# 此时源文件在本地系统中就会消失

-copyFromLocal从本地复制文件并粘贴到HDFS中

# 创建一个本地文件
touch wukong2.txt
# 将本地文件剪贴到HDFS中
hadoop fs -copyFromLocal./wukong.txt /input
# 此时源文件在本地系统中就不会消失

-appendToFile追加一个文件到已经存在的文件末尾,

# 创建一个本地文件
touch hello.txt
# 编写一个测试数据
hello big data!
# 将本地文件剪贴到HDFS中
hadoop fs -appendToFile./hello.txt /input/wukong2.txt
# 此时我们可以在HDFS中看到wukong2.txt中有了hello.txt的数据

-put等同于copyFromLocal

# 将本地文件剪贴到HDFS中
hadoop fs -put./hello.txt /input
# 此时/input目录下就会有hello,txt并且本地文件也存在
下载

了解了文件如何上传到HDFS,接下来就是如何从HDFS下载文件,我们先将本地的文件全部删除了

-copyToLocal从HDFS拷贝到本地

# 将HDFS中的wukong.txt下载到当前位置
hadoop fs -copyToLocal /input/wukong.txt ./

-get等同于copyToLocal,就是从HDFS下载文件到本地

# 将HDFS中的wukong.txt下载到当前位置
hadoop fs -get /input/wukong2.txt ./

-getmerge合并下载多个文件,比如HDFS的目录/input下有多个文件hello.txt,wukon2.txt…

# 将HDFS中input中的所有文件中的数据合并到merge_txt.txt并下载到本地
hadoop fs -getmerge /input/* ./merge_txt.txt
HDFS直接操作

直接操作就和我们Linux的命令操作一样,只不过它操作的是HDFS中的文件
-ls 显示目录信息

# 显示HDFS的目录信息

-mkdir 在HDFS上创建目录

# 在HDFS中创建一个文件夹
hadoop fs -mkdir test

-cat 显示文件内容

# 查看HDFS中指定文件的数据
hadoop fs -cat /input/wukong2.txt

-chmod/-chown/-chgrp Linux文件系统中的用法一样,修改文件所属权限,我就不一 一举例了

# 修改HDFS中wukong.txt的权限
hadoop fs  -chmod  666 wukong.txt

-cp从HDFS的一个路径拷贝到HDFS的另一个路径

# 复制wukong.txt 并起名叫wukon3.txt
hadoop fs -cp wukong.txt wukong3.txt

-mv在HDFS目录中移动文件

# 移动wukong.txt到HDFS根目录下
hadoop fs -mv wukong.txt /

-tail显示一个文件的末尾

# 查看wukong2.txt末尾的数据
hadoop fs -tail wukong2.txt

-rm删除文件或文件夹

# 删除wukong3.txt文件
hadoop fs -rm wukong3.txt

-rmdir删除空目录

# 删除我们前面创建的HDFS的test目录
hadoop fs -rmdir test

-du统计文件夹的大小信息

# 查看input文件夹的大小信息
hadoop fs -du -s -h /input
# 不加-s -h指的是显示hdfs对应路径下每个文件夹和文件的大小
# -h指的是是否换算成最大的单位,例如一个文件是100mb,它就会显示100mb
# -s指的是显示hdfs对应路径下所有文件和的大小

-setrep设置HDFS中文件的副本数量

# 设置wukong.txt的副本数量为10
hadoop fs -setrep 10 /input/wukong.txt

注意:-setrep设置副本数量如果大于当前我们集群中节点数据,默认的副本数量还是我们当前集群数量,当我们增加新的节点后,就会自动创建副本

HDFS的客户端操作

  • 前面我们学习了如何在shell中对HDFS进行操作,接下来就是如何通过java代码对HDFS进行操作
  • HDFS在客户端的操作就和我们学习JDBC的时候很像,它的流程也是:
    • 建立连接
    • 具体操作
    • 关闭资源
  • 通过Java来操作HDFS,我们当然是通过windows的IDEA来操作了,所以,我们的Windows也需要安装Hadoop

操作HDFS前的准备工作

安装Hadoop的方式和Maven一样,我们直接下载windows版本的依赖包,然后解压,设置环境变量即可

第一步:下载windows版本的Hadoop包
大家可以自行度娘下载哈,Hadoop官方没有提供windows版本,需要自己下载源码然后编译,官网里有介绍编译的方法,或者后台私信我哈

第二步:设置环境变量, HADOOP_HOME 然后设置path %HADOOP_HOME%\bin
在这里插入图片描述
在这里插入图片描述
第三步:在windows命令行测试
在这里插入图片描述
注意:如果上述操作后还有问题可以将bin目录下hadoop.dllwinutils.exe放到 C:/windows/system32目录下

创建项目并初始化HDFS所需配置

第一步: 创建一个Maven工程HDFSDemo,并导入相应的依赖坐标+日志添加,创建Maven工程我就不演示了,创建好之后,直接在pom.xml中添加所需要的依赖

  • pom.xml配置
    <dependencies>
    <!--        单元测试-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
    <!--        log4j :日志管理jar包-->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-slf4j-impl</artifactId>
                <version>2.12.0</version>
            </dependency>
    <!--        hadoop依赖的jar包-->
            <dependency>
                <groupId>org.apache.hadoop</groupId>
                <artifactId>hadoop-client</artifactId>
                <version>3.1.3</version>
            </dependency>
        </dependencies>
    

第二步:日志信息配置,在main文件夹下的resources中创建一个log4j2.xml的配置文件,内容如下

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" strict="true" name="XMLConfig">
    <Appenders>
        <!-- 类型名为Console,名称为必须属性 -->
        <Appender type="Console" name="STDOUT">
            <!-- 布局为PatternLayout的方式,
            输出样式为[INFO] [2018-01-22 17:34:01][org.test.Console]I'm here -->
            <Layout type="PatternLayout"
                    pattern="[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n" />
        </Appender>

    </Appenders>

    <Loggers>
        <!-- 可加性为false -->
        <Logger name="test" level="info" additivity="false">
            <AppenderRef ref="STDOUT" />
        </Logger>

        <!-- root loggerConfig设置 -->
        <Root level="info">
            <AppenderRef ref="STDOUT" />
        </Root>
    </Loggers>
</Configuration>

第三步: 创建包名: com.company.hdfs
第四步: 创建HDFSDemo类,最终结构如下
在这里插入图片描述
第五步:开始正式编写代码操作HDFS前,我们先要了解一下操作步骤:

  • 1.获取文件系统
  • 2.具体操作
  • 3.释放资源
  • 记得在下面操作之前,虚拟机中的Hadoop集群要正常运行

HDFS的常用API操作

我在这里直接使用单元测试的方法来对API进行操作,这样方便大家查看,另外,里面的异常我都主动抛出了,大家在编写的时候需要自己捕获一下异常

HDFSDemo的通用编写
public class HDFSDemo {
    private  FileSystem fs;
    /*
        在执行单元测试方法前先执行
     */
    @Before
    public void before() throws Exception {
         /*
           get(final URI uri, final Configuration conf,
                final String user)
            uri : NameNode的地址
            conf : 配置文件的内容
            user : 操作HDFS的用户名
         */
        Configuration conf = new Configuration();
        //1.获取文件系统对象
        fs = FileSystem.get(new URI("hdfs://hadoop102:9820"),
                conf, "hadoopuser");
    }
    /*
        在执行单元测试方法后再执行
     */
    @After
    public void after(){
        //3.关闭资源
        try {
            if (fs != null) {
                fs.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
... 后面的代码就是我们的API
}
copyFromLocalFile 文件上传
 /*
        文件上传
     */
    @Test
    public void test() throws URISyntaxException, IOException, InterruptedException {
        /*
        copyFromLocalFile(boolean delSrc, boolean overwrite,
                                Path src, Path dst)
          delSrc : 是否删除源文件
          overwrite : 如果目标文件已经存在,是否覆盖
                如果不覆盖,那么目标文件如果已经存在则报错。
          src :源文件路径(本地)
          dst :目标文件路径(HDFS)
         */
        fs.copyFromLocalFile(true,true,
                new Path("D:\\io\\hdfs\\aa.txt"),
                new Path("/input2"));

    }
copyToLocalFile 文件下载
/*
        文件下载
     */
    @Test
    public void test2() throws IOException {
        /*
        copyToLocalFile(boolean delSrc, Path src, Path dst,
                        boolean useRawLocalFileSystem)
          delSrc : 是否删除源文件(HDFS)
          src : 源文件路径(HDFS)
          dst : 目标文件路径(本地)
          useRawLocalFileSystem : 是否使用RawLocalFileSystem
         */
        fs.copyToLocalFile(false,new Path("/input2/aa.txt"),
                new Path("D:\\io\\hdfs"),false);
    }
delete 删除文件或目录
 /*
        删除文件或目录
     */
    @Test
    public void test3() throws IOException {
        /*
        delete(Path f, boolean recursive)
        f : 文件或目录的路径
        recursive :如果删除的是文件true和false都可以。如果删除的是目录必须是true否则抛异常
            注意:如果是空目录true和false也都可以
         */
        fs.delete(new Path("/txt/sanguo.txt"),true);
    }
rename 修改文件名或移动文件
/*
        修改文件名或移动文件
     */
    @Test
    public void test4() throws IOException {
        /*
            rename(Path src, Path dst)
            src : 源文件路径
            dst : 目标文件路径
         */
        //改名
//        fs.rename(new Path("/txt/xiyouji.txt"),
//                new Path("/txt/xiyou.txt"));

        //移动文件
        fs.rename(new Path("/txt/xiyou.txt"),
                new Path("/input"));
    }
RemoteIterator 文件详情查看
/*
        文件详情查看
     */
    @Test
    public void test5() throws Exception {
        /*
             listFiles(final Path f, final boolean recursive)
             f : 目标文件路径
             recursive : 是否递归
         */
        RemoteIterator<LocatedFileStatus> iterator
                = fs.listFiles(new Path("/"), true);
        //遍历
        while(iterator.hasNext()){
            LocatedFileStatus fileStatus = iterator.next();
            //获取文件的名字
            System.out.println("==============文件的名字:"
                    + fileStatus.getPath().getName() + "===================");

            System.out.println("副本数量:" + fileStatus.getReplication());
            //获取块信息
            BlockLocation[] blockLocations = fileStatus.getBlockLocations();
            System.out.println(Arrays.toString(blockLocations));
        }
    }
FileStatus 判断文件或目录
/*
        判断文件或目录
     */
    @Test
    public void test6() throws IOException {
        FileStatus[] status = fs.listStatus(new Path("/input"));

        for (FileStatus fileStatus : status) {
            if (fileStatus.isDirectory()){
                System.out.println(fileStatus.getPath().getName() + "是一个目录");
            }else if(fileStatus.isFile()){
                System.out.println(fileStatus.getPath().getName() + "是一个文件");
            }
        }
    }
FSDataOutputStream/FSDataInputStream 通过流的形式实现上传和下载
/*
        通过流的形式实现上传和下载

        上传
     */
    @Test
    public void test7() throws Exception {
        //读取 --- 本地读(文件输入流)
        FileInputStream fis = new FileInputStream("D:\\io\\hdfs\\long.txt");
        //写 --- 向HDFS写数据的流
        FSDataOutputStream fos = fs.create(new Path("/input2/long.txt"));
        //一边读一边写
        /*
        copyBytes(InputStream in, OutputStream out,
                               int buffSize, boolean close)
          in : 输入流
          out : 输出流
          buffSize : 缓冲区大小
          close : 是否关闭流
         */
        IOUtils.copyBytes(fis,fos,1024,false);
        IOUtils.closeStream(fis);
        IOUtils.closeStream(fos);
    }
    /*
        下载
     */
    @Test
    public void test8() throws IOException {
        //读 --- 从HDFS上读
        FSDataInputStream fis = fs.open(new Path("/input2/long.txt"));
        //写 --- 向本地写(文件输出流)
        FileOutputStream fos = new FileOutputStream("D:\\io\\hdfs\\long.txt");
        //文件对拷(一边读一边写)
        IOUtils.copyBytes(fis,fos,1024,true);
    }

在IDEA中设置HDFS的用户名

  • 在前面我们的通用配置时,我们自己提供了user这个值,当我们的Maven工程很多的时候,每创建一次工程都需要提供一次user,我们可以通过IDEA的设置来传递,这样可以防止因用户名问题导致一些错误
  • 前面我们是通过测试方法进行测试,这次我们使用Main方法进行测试
    package com.company.hdfs;
    
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    
    import java.io.IOException;
    
    /*
        创建FileSystem对象
     */
    public class HDFSDemo2 {
        /*
            在 EditConfigurations中
                    VM Options : -DHADOOP_USER_NAME=atguigu
            注意:如果找不到当前类请先运行一次。
         */
        public static void main(String[] args) throws IOException {
            System.out.println("hello");
            //用来设置配置内容
            Configuration conf = new Configuration();
            //配置NameNode的地址
            conf.set("fs.defaultFS","hdfs://hadoop102:9820");
            //创建文件系统对象
            FileSystem fs = FileSystem.get(conf);
            //上传操作
            fs.copyFromLocalFile(false,true,
                    new Path("F:\\IO\\hdfs\\aa.txt"),
                    new Path("/input"));
            //关闭资源
            fs.close();
    
        }
    }
    
  • 在我们创建文件对象时,没有传递user用户,它就会报下面这个错误
    Exception in thread "main" org.apache.hadoop.security.AccessControlException: Permission denied: user=m, access=WRITE, inode="/input":atguigu:supergroup:drwxr-xr-x
    
  • 解决方法:
  • 在 EditConfigurations中 VM Options : -DHADOOP_USER_NAME=atguigu 注意:如果找不到当前类请先运行一次。
    在这里插入图片描述
    在这里插入图片描述

在客户端中,配置文件的优先级

建立一个HadooopDemo3,然后在hadoop102中复制一份hdfs-site.xml配置文件到Main的resources中,整体结构如下
在这里插入图片描述


package com.company.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

import java.io.IOException;

/*
    配置参数:
        方式1 : xxx-default.xml (服务器 --- 默认配置文件)
        方式2 : xxx-site.xml(服务器)
        方式3 : 在代码中配置(客户端)
        方式4 :  在工程中的配置文件(客户端)

     优先级 : 在代码中配置(客户端) > 在工程中的配置文件(客户端) > xxx-site.xml(服务器)
                    > xxx-default.xml (服务器 --- 默认配置文件)
 */
public class HDFSDemo3 {

    public static void main(String[] args) throws IOException {
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS","hdfs://hadoop102:9820");
        //配置副本的数量
        conf.set("dfs.replication","4");

        FileSystem fs = FileSystem.get(conf);
        fs.copyFromLocalFile(false,true,
                new Path("D:\\io\\hdfs\\aa.txt"),
                new Path("/input2"));
        fs.close();

    }
}

我们在代码中设置副本的数量为4,然后在Main中复制的core-site.xml设置副本数量为2,hadoop102的副本数量是默认的3,执行后,在web中查看
在这里插入图片描述
我们会发现,执行的结果是代码中修改的配置参数,因此我们可以得出如下结论:

  • 代码中的设置配置文件优先级最高
  • 代码中没有修改配置文件时,当前工程中的配置文件优先级最高
  • 当代码中,工程中都没有设置配置文件,就会从集群服务器的xxx-site.xml配置文件中寻找配置
  • 最后的优先级就是集群的默认配置文件xxx.default.xml

总结

  • 本章节介绍了HDFS的组成架构以及使用Shell与代码操作HDFS
  • HDFS的重点就是使用Shell和代码操作HDFS,了解架构和下一章我要讲的HDFS各模块原理都是为了辅助我们理解HDFS
  • 好了,如果对本章内容有疑问,需要windows上的Hadoop安装包,欢迎后台私信我
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值