hadoop(二)HDFS概述、shell操作、客户端操作(各种API操作)以及hdfs读写流程

hadoop系列笔记
hadoop(一)入门、hadoop架构、集群环境搭建.
hadoop(二)HDFS概述、shell操作、客户端操作(各种API操作)以及hdfs读写流程.
hadoop(三)hdfs的NameNode和DataNode工作机制.
hadoop(四)MapReduce入门及序列化实操.
hadoop(五)MapReduce框架原理及工作机制.
hadoop(六)hadoop数据压缩、yarn架构及工作原理、hadoop企业优化.

第一章HDFS概述

1.1 HDFS产生背景

  • 随着数据量越来越大,在一个操作系统管辖的范围内存不下了,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。

1.2 HDFS概念

  • HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。
  • HDFS 使用场景:HDFS的设计适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。

1.3 HDFS优缺点

1.3.1 优点

  • 1)高容错性
    (1)数据自动保存多个副本。它通过增加副本的形式,提高容错性。
    (2)某一个副本丢失以后,它可以自动恢复。
  • 2)适合大数据处理
    (1)数据规模:能够处理数据规模达到 GB、TB、甚至PB级别的数据。
    (2)文件规模:能够处理百万规模以上的文件数量,数量相当之大。
  • 3)流式数据访问,它能保证数据的一致性。
  • 4)可构建在廉价机器上,通过多副本机制,提高可靠性。

1.3.2 缺点

  • 1)不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。
  • 2)无法高效的对大量小文件进行存储
    (1)存储大量小文件的话,它会占用 NameNode大量的内存来存储文件、目录和块信息。这样是不可取的,因为NameNode的内存总是有限的。
    (2)小文件存储的寻道时间会超过读取时间,它违反了HDFS的设计目标。
  • 3)并发写入、文件随机修改
    (1)一个文件只能有一个写,不允许多个线程同时写。
    (2)仅支持数据 append(追加),不支持文件的随机修改。

1.4 HDFS组成架构

在这里插入图片描述

  • 1)Client:就是客户端。
    (1)文件切分。文件上传 HDFS 的时候,Client 将文件切分成一个一个的Block,然后进行存储。
    (2)与NameNode交互,获取文件的位置信息。
    (3)与DataNode交互,读取或者写入数据。
    (4)Client提供一些命令来管理HDFS,比如启动或者关闭HDFS。
    (5)Client可以通过一些命令来访问HDFS。
  • 2)NameNode:就是Master,它是一个主管、管理者。
    (1)管理HDFS的名称空间。
    (2)管理数据块(Block)映射信息
    (3)配置副本策略
    (4)处理客户端读写请求。
  • 3)DataNode:就是Slave,NameNode下达命令,DataNode执行实际的操作。
    (1)存储实际的数据块。
    (2)执行数据块的读/写操作。
  • 4)Secondary NameNode:并非NameNode的热备。当NameNode挂掉的时候,它并不能马上替换NameNode并提供服务。
    (1)辅助NameNode,分担其工作量。
    (2)定期合并Fsimage和Edits,并推送给NameNode。
    这时都会疑问secondary NameNode存在的意义,请看Secondary NameNode存在的意义,与NameNode的区别.

1.5 HDFS文件块大小(面试重点)

  • HDFS中的文件在物理上是分块存储(block),块的大小可以通过配置参数( dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,老版本中是64M。
    在这里插入图片描述
    在这里插入图片描述

第2章 HDFS的Shell操作(开发重点)

2.1 基本语法

hadoop fs 具体命令 OR hdfs dfs 具体命令
dfs是fs的实现类

2.2 hadoop fs常用命令

用法是hadoop fs 加下面后缀执行

  • 1.HDFS–>HDFS

    • -cp:从HDFS的一个路径拷贝到HDFS的另一个路径
    • -mv:在HDFS目录中移动文件
    • -chown、 chgrp、chmod:Linux文件系统中的用法一样,修改文件所属权限
    • -mkdir :在HDFS上创建目录
    • -du:统计文件夹的大小信息
    • -df:查看文件的使用情况和分区
    • -cat:显示文件内容
    • -rm:删除指定文件
    • -rm -R :删除文件夹
  • 2.本地–>HDFS

    • -put:上传 等同于copyFromLocal
    • -copyFromLocal:从本地文件系统中拷贝文件到HDFS路径去
    • -moveFromLocal:从本地剪切粘贴到HDFS
    • -appendToFile:追加一个文件到已经存在的文件末尾
  • 3.HDFS–>本地

    • -get:等同于copyToLocal,就是从HDFS下载文件到本地
    • -getmerge:合并下载多个文件,比如HDFS的目录 /user/liuyongjun/test下有多个文件:log.1, log.2,log.3,…
    • -copyToLocal:从HDFS拷贝到本地
  • 4.其他

    • -rm:删除指定文件
    • -ls: 显示目录信息
    • -tail:显示一个文件的末尾
    • -setrep:设置HDFS中文件的副本数量
    • 这里设置的副本数只是记录在NameNode的元数据中,是否真的会有这么多副本,还得看DataNode的数量。因为目前只有3台设备,最多也就3个副本,只有节点数的增加到10台时,副本数才能达到10。
      D:\software\hadoop-2.7.2

第3章 HDFS客户端操作(开发重点)

3.1 HDFS客户端环境准备

  • 1.根据自己电脑的操作系统拷贝对应的编译后的hadoop jar包到非中文路径(例如:D:\software\hadoop-2.7.2),如图3-4所示
    在这里插入图片描述
  • 2.配置HADOOP_HOME环境变量
    在这里插入图片描述
  • 3.配置Path环境变量
    在path中加入%HADOOP_HOME\bin;
  • 4执行winutils测试
    在这里插入图片描述
    或者这样测(都行)
    在这里插入图片描述
    测试成功一定要重启一下
  • 5.创建一个Maven工程HdfsClientDemo
  • 6.导入相应的依赖坐标+日志添加
<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.8.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-common</artifactId>
			<version>2.7.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-client</artifactId>
			<version>2.7.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-hdfs</artifactId>
			<version>2.7.2</version>
		</dependency>
		<dependency>
			<groupId>jdk.tools</groupId>
			<artifactId>jdk.tools</artifactId>
			<version>1.8</version>
			<scope>system</scope>
			<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
		</dependency>
</dependencies>

导完之后,需要在项目的src/main/resources目录下,新建一个文件,命名为“log4j.properties”,在文件中填入

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
  • 7.创建包名:com.liuyongjun.hdfs
  • 8.创建HdfsClient类
package com.liuyongjun.hdfs;

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

import java.io.IOException;
import java.net.URI;


/**
 * @author liuyongjun
 * @date 2020-06-30-16:19
 */
public class HdfsClient {
    @Test
    public void get() throws IOException, InterruptedException {
        // 1 获取文件系统(hdfs抽象封装对象)
        Configuration configuration = new Configuration();
        // 配置在集群上运行
        // configuration.set("fs.defaultFS", "hdfs://hadoop102:9000");
        // FileSystem fs = FileSystem.get(configuration);
        /*三个参数:URI:统一资源标识符,指定一下hdfs资源,hdfs://hadoop102:9000,首先hdfs是指schema(模式),是指用hdfs模式进行访问
                                                                              hadoop102:9000是访问位置
                  configuration:之前搭hadoop集群,在配置文件的configration标签中配置的
                   user:登录用户
         得到的fs就是文件系统(hdfs抽象封装对象)
        */
        FileSystem fs = FileSystem.get(URI.create("hdfs://hadoop102:9000"), configuration, "liuyongjun");

        // 2 下载到本地(用这个对象操作文件系统)
        //Path是hadoop提供的对路径进行封装的抽象类
        fs.copyToLocalFile(new Path("/test"),new Path("d:\\"));

        // 3 关闭资源
        //hdfs不支持并发写入,所以关闭资源
        fs.close();
    }
}

3.2 HDFS的API操作

  • HDFS文件名更改
@Test
    public void rename() throws IOException, InterruptedException {
        //获取文件系统
        FileSystem fs = FileSystem.get(URI.create("hdfs://hadoop102:9000"), new Configuration(), "liuyongjun");
        //操作
        fs.rename(new Path("/test"),new Path("/test2"));
        //关闭文件系统
        fs.close();
    }

结果:
在这里插入图片描述

  • 一个一个太麻烦了,其他文件操作用before,test,after一起执行,开始写before,最后写after,中间测试,头尾必执行,可以将重复命令放在那里
package com.liuyongjun.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;


import javax.swing.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;


/**
 * @author liuyongjun
 * @date 2020-06-30-16:19
 */
public class HdfsClient {
    private FileSystem fs;
    @Test
    public void get() throws IOException, InterruptedException {
        // 1 获取文件系统
        Configuration configuration = new Configuration();


        /*三个参数:URI:统一资源标识符,指定一下hdfs资源,hdfs://hadoop102:9000,首先hdfs是指schema(模式),是指用hdfs模式进行访问
                                                                              hadoop102:9000是访问位置
                  configuration:之前搭hadoop集群,在配置文件的configration标签中配置的
                   user:登录用户
         得到的fs就是文件系统(hdfs抽象对象)
        */
        FileSystem fs = FileSystem.get(URI.create("hdfs://hadoop102:9000"), configuration, "liuyongjun");

        // 2 下载
        //Path是hadoop提供的对路径进行封装的抽象类
        fs.copyToLocalFile(new Path("/test"),new Path("d:\\"));

        // 3 关闭资源
        //hdfs不支持并发写入,所以关闭资源
        fs.close();
    }
    @Before
    public void before() throws IOException, InterruptedException {
        //获取文件系统
        fs = FileSystem.get(URI.create("hdfs://hadoop102:9000"), new Configuration(), "liuyongjun");
    }
    @Test
    public void rename() throws IOException {
        //操作HDFS文件名更改
        fs.rename(new Path("/test"),new Path("/test2"));
    }
    @Test
    public void testCopyFromLocalFile() throws IOException {
        //HDFS文件上传
        fs.copyFromLocalFile(new Path("d:/2.txt"),new Path("/"));
    }
    @Test
    public void append() throws IOException {
        //HDFS文件拼接操作
        FSDataOutputStream append = fs.append(new Path("/test/1.txt"), 1024);
        FileInputStream open = new FileInputStream("d:/testhdfs/2.txt");

        //流拷贝
        IOUtils.copyBytes(open,append,1024,true);
		//上面输入是本地,输出是hdfs,所以为文件上传
		//如果输入为hdfs,输出为本地,则为文件下载
		//hdfs获取输入流用open,获取输出流用append
    }
    @Test
    public void delete() throws IOException, InterruptedException {
        //删除操作
        boolean delete = fs.delete(new Path("/test2"), true);
        if(delete){
            System.out.println("删除成功");
        }else{
            System.out.println("删除失败");
        }
    }
    @Test
    public void ls() throws IOException {
        //HDFS文件查看
        FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
        for (FileStatus fileStatus : fileStatuses) {
            //HDFS文件和文件夹判断
           if(fileStatus.isFile()){
               System.out.println("以下信息是一个文件的信息");
               System.out.println(fileStatus.getPath());
               System.out.println(fileStatus.getLen());
           }else{
               System.out.println("以下信息是一个文件夹的信息");
               System.out.println(fileStatus.getPath());
           }
        }
    }

    @Test
    public void listFiles() throws IOException {
        //查看目录下所有文件
        RemoteIterator<LocatedFileStatus> files = fs.listFiles(new Path("/"), true);
        while(files.hasNext()){
            LocatedFileStatus file = files.next();
            System.out.println("==============================");
            System.out.println(file.getPath());
            System.out.println("块信息");
            BlockLocation[] blockLocations = file.getBlockLocations();
            for (BlockLocation blockLocation : blockLocations) {
                String[] hosts = blockLocation.getHosts();
                System.out.print("块在");
                for (String host : hosts) {
                    System.out.print(host+" ");
                }
            }

        }
    }
    @After
    public void after() throws IOException {
        //关闭文件系统
        fs.close();
    }
}

  • 注意参数优先级的考虑
    • 拿上传文件进行举例
    • 首先将hdfs-site.xml拷贝到项目的根目录下

hdfs-site.xml

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration>
	<!--指定HDFS副本的数量-->
	<property>
		<name>dfs.replication</name>
        <value>2</value>
	</property>
</configuration>

上传程序

@Test
public void testCopyFromLocalFile() throws Exception {

		// 1 获取文件系统
		Configuration configuration = new Configuration();
		//设置配置文件
		configuration.set("dfs.replication", "1");
		FileSystem fs = FileSystem.get(new URI("hdfs://hadoop102:9000"), configuration, "liuyongjun");

		// 2 上传文件
		fs.copyFromLocalFile(new Path("d:/1.txt"), new Path("/1.txt"));

		// 3 关闭资源
		fs.close();

		System.out.println("over");
}

上面我们设置副本数,服务器默认是3个,ClassPath下的用户自定义配置2个,客户端代码中设置的是1个,我们看看它响应哪个,则哪个优先级高
如图,副本数为:
在这里插入图片描述
所以参数优先级排序:(1)客户端代码中设置的值 >(2)ClassPath下的用户自定义配置文件 >(3)然后是服务器的默认配置

第四章HDFS的数据流(面试重点)

4.1HDFS 写数据流程

  • 首先,两个重要概念:
NameNode:领导级别。管理数据块映射;处理客户端的读写请求;配置副本策略;管理HDFS的名称空间;
 
DataNode:员工级别。负责存储客户端发来的数据块block;执行数据块的读写操作。
  • 写流程

在这里插入图片描述

写详细步骤:
1、首先向namenode通信,请求上传文件,namenode检查目标文件是否已存在,父目录是否存在 ,还得看看是否有上传的权限,说白了,就是判断是否可以上传
2、namenode返回是否可以上传 ,如果可以,client会先对文件进行切分(逻辑切分)
3、客户端请求第一个 Block上传到哪几个DataNode服务器上。
4、NameNode返回3个DataNode节点,分别为dn1、dn2、dn3。
5、客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。
6、dn1、dn2、dn3逐级应答客户端。
7、客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
8、当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器。(重复执行3-7步)。
9、传输完毕之后,客户端关闭流资源,并且会告诉hdfs数据传输完毕,然后hdfs收到传输完毕就恢复元数据
  • 具体概念介绍

  • Distributed FileSystem:进行抽象封装,FileSystem会利用JDK的反射机制创建一个DistributedFileSystem实例(对象),然后调用它的initialize()方法

  • 逻辑切分:客户端并没有将文件真正切分,只是画了个标志线加以区分

  • 写操作,上传文件,所以本地是输入流,hdfs是输出流

  • 第三步有哪几个DataNode服务器上:这里指副本数,设置了几个副本,就返回几个DataNode(记住数据是存储在DataNode)我设置了三个副本,所以,返回三个;

  • 同时,返回的DataNode也有一定规矩,首先第一个DataNode是距离客户端最近的,后两个是根据第一个选出,产生了两个问题,如何判断最近,以及如何根据第一个选,这个请看: 拓扑距离和机架感知.

  • 第七步,packet为单位,每个64KB

  • 传输Packet:dn1收到之后,一边往本地落盘,一边传给dn2,之后的dn2同理,当dn3落盘结束之后,它将成功信息发给dn2,之后dn2需要等自己成功并且收到dn3成功信息之后,将成功信息发给dn1,同理,dn1在接收到dn2成功信息并且自己落盘成功之后发给客户端,此时一个packet就成功了;注意packet不是逐个发的,是一个队列同时发的,成功了,在队列里删除掉,这样全部packet发完,第一块就传完了,接着传第二块,第二次选择的DataNode可能和第一次一样,也可能不一样

  • 传输过程中几种失败可能:

    • 1.在建立通道时失败,这样直接上传失败,直接抛异常
    • 2.在传输数据过程中失败:
      • 1).客户端传输Packet到第一个DataNode过程中失败就上传失败
      • 2)dn1与dn2或者dn2与dn3之间的传输Packet失败,上传仍然进行,并且传出成功信号,因为即使这两个过程失败了,副本数就变成1了,hdfs有高容错性,副本丢失,第一个DataNode会触发自动备份,自动寻找两个DataNode

4.2 HDFS读数据流程

  • 读流程
    在这里插入图片描述
1)客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。
2)挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据。
3)DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)。
4)客户端以Packet为单位接收,先在本地缓存,然后写入目标文件。
5)下载完第一块,在重复上面2.3步下载

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值