大数据生态圈学习--HDFS分布式文件系统

  • HDFS介绍
  • HDFS的命令行使用
  • hadoop的基准测试
  • HDFS架构
  • NameNode元数据管理
  • HDFS文件的读写过程
  • HDFS java api操作


HDFS介绍

HDFS 是 Hadoop Distribute File System 的简称,意为:Hadoop 分布式文件系统。是 Hadoop 核心组件之一,作为最底层的分布式存储服务而存在。
可以存储海量数据。

hdfs的特性:
master/slave架构:主从架构

namenode:主节点,主要用于存储元数据,处理用户的请求

datanode:从节点,主要用于存储数据,说白了就是出磁盘的

分块存储:把一个大的文件,化成一个个的小的block块,在2.x当中一个block默认是128M大小

统一的命名空间:对外提供统一的文件访问地址:hdfs://node01:8020

datanode数据存储:出磁盘的,用于存储文件数据

副本机制:比如1280M的文件需要拆成10个block块,每个blcok有三个副本

一次写入,多次读取:hdfs文件系统,适用于频繁的读取,不适合频繁的写入 改变文件涉及元数据的改变

HDFS的命令行使用

1.ls查看文件

hdfs dfs -ls /user/hadoop/file1

2.lsr递归查看文件
Usage: hdfs dfs -lsr
相当于 ls -R
3.mkdir创建文件夹

hdfs dfs -mkdir /user/hadoop/dir1 /user/hadoop/dir2
hdfs dfs -mkdir hdfs://nn1.example.com/user/hadoop/dir hdfs://nn2.example.com/user/hadoop/dir

4.moveFromLocal上传
hdfs dfs -moveFromLocal 1.txt 指定的路径
移动本地磁盘的文件到hdfs目录中,移动后会删除源文件

5.moveTolocal下载
hdfs dfs -moveFromLocal 2.txt 1.txt
将hdfs上的文件下载到本地磁盘
好像这个命令还没实现不能用

6.mv hdfs中文件的移动

hdfs dfs -mv /user/hadoop/file1 /user/hadoop/file2
hdfs dfs -mv hdfs://nn.example.com/file1 hdfs://nn.example.com/file2 hdfs://nn.example.com/file3 hdfs://nn.example.com/dir1

7.put 从本地文件复制到hdfs上

hdfs dfs -put localfile /user/hadoop/hadoopfile
hdfs dfs -put localfile1 localfile2 /user/hadoop/hadoopdir
hdfs dfs -put localfile hdfs://nn.example.com/hadoop/hadoopfile
hdfs dfs -put - hdfs://nn.example.com/hadoop/hadoopfile Reads the input from stdin.

8.appendToFile 追加一个或多个本地文件到hdfs上

hdfs dfs -appendToFile localfile /user/hadoop/hadoopfile
hdfs dfs -appendToFile localfile1 localfile2 /user/hadoop/hadoopfile
hdfs dfs -appendToFile localfile hdfs://nn.example.com/hadoop/hadoopfile
hdfs dfs -appendToFile - hdfs://nn.example.com/hadoop/hadoopfile Reads the input from stdin.

9.cat 查看内容

hdfs dfs -cat hdfs://nn1.example.com/file1 
hdfs://nn2.example.com/file2
hdfs dfs -cat file:///file3 /user/hadoop/file4

10.cp 复制文件(夹),可以覆盖,可以保留原有权限信息

hdfs dfs -cp /user/hadoop/file1 /user/hadoop/file2
hdfs dfs -cp /user/hadoop/file1 /user/hadoop/file2 /user/hadoop/dir

11.rm 删除文件或者文件夹
hdfs dfs -rm -r 递归删除
12.chmod 修改权限
hdfs dfs -chmod -R 777 /XXXX
13.chown 修改文件的用户所属组
hdfs dfs -chown -R hadoop:hadoop /xxx
14.expunge 清空回收站

15.HDFS文件限额配置

数量限额

hdfs dfs -mkdir -p /user/root/lisi     #创建hdfs文件夹
hdfs dfsadmin -setQuota 2 lisi      # 给该文件夹下面设置最多上传两个文件,上传文件,发现只能上传一个文件
hdfs dfsadmin -clrQuota /user/root/lisi    # 清除文件数量限制

空间大小限额

hdfs dfsadmin -setSpaceQuota 4k /user/root/lisi   # 限制空间大小4KB
hdfs dfs -put  /export/softwares/zookeeper-3.4.5-cdh5.14.0.tar.gz /user/root/lisi

#上传超过4Kb的文件大小上去提示文件超过限额
hdfs dfsadmin -clrSpaceQuota /user/root/lisi #清除空间限额
查看hdfs文件限额数量
hdfs dfs -count -q -h /user/root/lisi

hadoop的基准测试

实际生产环境当中,hadoop的环境搭建完成之后,第一件事情就是进行压力测试,测试我们的集群的读取和写入速度,测试我们的网络带宽是否足够等一些基准测试,hadoop自带的测试文件
测试写入速度
向HDFS文件系统中写入数据,10个文件,每个文件10MB,文件存放到
/benchmarks/TestDFSIO中

hadoop jar /export/servers/hadoop-2.6.0-cdh5.14.0/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.6.0-cdh5.14.0.jar TestDFSIO  -write -nrFiles 10 -fileSize 10MB

查看写入结果
hdfs dfs -text /benchmarks/TestDFSIO/io_write/part-00000

测试读取速度
测试hdfs的读取文件性能
在HDFS文件系统中读入10个文件,每个文件10M

hadoop jar /export/servers/hadoop-2.6.0-cdh5.14.0/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.6.0-cdh5.14.0.jar TestDFSIO -read -nrFiles 10 -fileSize 10MB

查看读取结果
hdfs dfs -text /benchmarks/TestDFSIO/io_read/part-00000
清除测试数据
hadoop jar /export/servers/hadoop-2.6.0-cdh5.14.0/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-2.6.0-cdh5.14.0.jar TestDFSIO -clean

HDFS架构

架构图:

hdfs平衡datanode hdfs load data_hdfs

  • NameNode 记录着每一台机器存储文件的元数据信息,并提供抽象目录树给客户端访问。
  • DataNode 负责文件内容的读写。
  • 文件在DataNode中以文件块的方式存储,称之为Block;在hadoop2.x中默认是128M。为了解决Block丢失的问题,引入了副本机制;读取文件时NameNode尽量让用户先读取最近的副本。
  • NameNode 与 DataNode 之间存在着心跳机制,DataNode 周期性往NameNode 发送心跳信号和状态报告,状态报告包含了该 DataNode 上存储的数据列表。

  1. NameNode总结:
  • 存储元数据(内存/磁盘)
  • 保存文件、block、DataNode之间的映射关系
  1. DataNode 总结:
  • 存储文件内容(磁盘)。
  • 维护blockid到 DataNode 本地文件的映射关系。
  1. 分布式文件系统: Hadoop提供了FileSystem接口给用户操作HDFS集群,该接口有很多实现类,如HDFS,Local,WebHDFS,HAR等。

NameNode元数据管理

  1. fsimage 存放的是一份最完整的元数据信息,内容比较大。其数据存储在dfs.namenode.name.dir(hdfs-site.xml)指定的文件路径中。
  2. 使用命令 hdfs oiv可以查看fsimage的数据,如:hdfs oiv -i fsimage_0000000000000000864 -p XML -o hello.xml
  3. edits 记录了最近一段时间的元数据信息操作日志,内容相对较小。其数据存储在dfs.namenode.edits.dir (hdfs-site.xml)指定的文件路径中。
  4. 查看命令 hdfs oev可以查看edits数据,如:hdfs oev -i edits_0000000000000000865-0000000000000000866 -p XML -o myedit.xml
  5. 为了控制edits日志不断膨胀太大,引入SecondaryNameNode的checkpoint机制。它的主要职责是合并fsimage与edits并清空旧的edits。
  6. SecondaryNameNode的checkpoint步骤:
  1. SNN通知NN切换edits日志。
  2. NN接收到通知后,所有的操作日志往一个新的edits文件写入。
  3. SNN获取fsimage与edits两个文件,把他们加载到内存中合并成一个新的fsimage。
  4. SNN将新的fsimage发送给NN并替换掉旧的。
  1. SNN默认触发checkpoint的条件有2种:1.edits文件达到64M 2.一个小时合并一次,也可以在hdfs-site.xml中修改fs.checkpoint.period,fs.checkpoint.size: edits的值。

HDFS文件的读写过程

HDFS写文件过程:

  1. client发起文件上传请求,NameNode检查元数据(目标文件是否已存在,父目录是否存在),返回是否可以上传。
  2. 如可以上传,client请求上传第一个block。
  3. NameNode根据配置文件中指定的备份数及机架感知原理进行文件分配,返回可用的DataNode的地址如:A,B,C。
  • 如果副本数是三个,NN会找三台机器。
  • 第一台:离客户端最近的一台机器。
  • 第二台:最近一台机器的相同路由器下面的一台机器。
  • 第三台:不同路由器下面找一台机器。
  1. client 与ABC机器建立pipeline。
  2. client开始以packet为单位(默认64K)往A上传第一个block,A收到packet后会传给B,B传C; A每传一个packet就会放入一个应答队列等待应答。
  3. 通过ack机制判断一个block是否上传成功,当一个block传输完成之后,client再次请求NameNode上传第二个block到服务器,直到所有block上传完毕。

HDFS读文件过程:

  1. client发起文件读取请求,NameNode检查元数据(有没读取权限,文件是否存在),返回是否可以读取。
  2. 如可以读取,NameNode会视情况返回文件的部分或者全部block列表(多线程)。
  • 对于每个block,NameNode 都会返回含有该 block 副本的 DataNode 地址;
  • 返回的DN根据网络拓扑图的远近进行排序,离client越近的DN地址越前。
  • 心跳机制中超时汇报的 DN 状态为 STALE,这样的排靠后。
  1. client 选取排序靠前的 DataNode 来读取 block。
  • 如果客户端本身就是DataNode, 那么将从本地直接获取数据(短路读取特性);
  • 如果读取过程中断,客户端需要重新请求DN读取block。
  1. 所有的block 块传输完毕后,在客户端拼接成一个完整的文件。

HDFS java api操作

1.创建maven工程并导入jar包
由于cdh版本的所有的软件涉及版权的问题,所以并没有将所有的jar包托管到maven仓库当中去,而是托管在了CDH自己的服务器上面,所以我们默认去maven的仓库下载不到,需要自己手动的添加repository去CDH仓库进行下载,以下两个地址是官方文档说明,请仔细查阅
https://www.cloudera.com/documentation/enterprise/release-notes/topics/cdh_vd_cdh5_maven_repo.html https://www.cloudera.com/documentation/enterprise/release-notes/topics/cdh_vd_cdh5_maven_repo_514x.html

<repositories>
    <repository>
        <id>cloudera</id>
     <!-- 制定我们去哪个网站下载jar包 -->
     <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
    </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>2.6.0-mr1-cdh5.14.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>2.6.0-cdh5.14.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-hdfs</artifactId>
        <version>2.6.0-cdh5.14.0</version>
    </dependency>

    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-mapreduce-client-core</artifactId>
        <version>2.6.0-cdh5.14.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>RELEASE</version>
    </dependency>
</dependencies>
<build>
    <plugins>
    <!-- 打包的插件 我们项目当中的其他jar包都打到一起去-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
                <!--    <verbal>true</verbal>-->
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.4.3</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <minimizeJar>true</minimizeJar>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

2.使用url的方式访问数据

@Test
public void demo1()throws  Exception{
    //第一步:注册hdfs 的url,让java代码能够识别hdfs的url形式
    URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());

    InputStream inputStream = null;
    FileOutputStream outputStream =null;
    //定义文件访问的url地址
    String url = "hdfs://192.168.52.100:8020/test/input/install.log";
    //打开文件输入流
    try {
        inputStream = new URL(url).openStream();
        outputStream = new FileOutputStream(new File("c:\\hello.txt"));
        IOUtils.copy(inputStream, outputStream);
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        IOUtils.closeQuietly(inputStream);
        IOUtils.closeQuietly(outputStream);
    }
}

3.使用文件系统方式访问数据
在 java 中操作 HDFS,主要涉及以下 Class:
Configuration:该类的对象封转了客户端或者服务器的配置; FileSystem:该类的对象是一个文件系统对象,可以用该对象的一些方法来对文件进行操作,通过 FileSystem 的静态方法 get 获得该对象。
FileSystem fs = FileSystem.get(conf)
get 方法从 conf 中的一个参数 fs.defaultFS 的配置值判断具体是什么类型的文件系统。如果我们的代码中没有指定 fs.defaultFS,并且工程 classpath下也没有给定相应的配置,conf中的默认值就来自于hadoop的jar包中的core-default.xml , 默 认 值 为 : file:/// , 则 获 取 的 将 不 是 一 个DistributedFileSystem 的实例,而是一个本地文件系统的客户端对象
4.获取FileSystem的几种方式
第一种方式获取FileSystem

@Test
public void getFileSystem() throws URISyntaxException, IOException {
   Configuration configuration = new Configuration();
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), configuration);
    System.out.println(fileSystem.toString());
}

第二种方式获取FileSystem

@Test
public void getFileSystem2() throws URISyntaxException, IOException {
    Configuration configuration = new Configuration();
    configuration.set("fs.defaultFS","hdfs://192.168.52.100:8020");
    FileSystem fileSystem = FileSystem.get(new URI("/"), configuration);
    System.out.println(fileSystem.toString());
}

第三种方式获取FileSystem

@Test
public void getFileSystem3() throws URISyntaxException, IOException {
    Configuration configuration = new Configuration();
    FileSystem fileSystem = FileSystem.newInstance(new URI("hdfs://192.168.52.100:8020"), configuration);
    System.out.println(fileSystem.toString());
}

第四种:

@Test
public void getFileSystem4() throws  Exception{
    Configuration configuration = new Configuration();
    configuration.set("fs.defaultFS","hdfs://192.168.52.100:8020");
    FileSystem fileSystem = FileSystem.newInstance(configuration);
    System.out.println(fileSystem.toString());
}

5.递归遍历文件系统当中的所有文件

@Test
public void listFile() throws Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    FileStatus[] fileStatuses = fileSystem.listStatus(new Path("/"));
    for (FileStatus fileStatus : fileStatuses) {
        if(fileStatus.isDirectory()){
            Path path = fileStatus.getPath();
            listAllFiles(fileSystem,path);
        }else{
            System.out.println("文件路径为"+fileStatus.getPath().toString());

        }
    }
}
public void listAllFiles(FileSystem fileSystem,Path path) throws  Exception{
    FileStatus[] fileStatuses = fileSystem.listStatus(path);
    for (FileStatus fileStatus : fileStatuses) {
        if(fileStatus.isDirectory()){
            listAllFiles(fileSystem,fileStatus.getPath());
        }else{
            Path path1 = fileStatus.getPath();
            System.out.println("文件路径为"+path1);
        }
    }
}

递归遍历官方提供的API版本

@Test
public void listMyFiles()throws Exception{
    //获取fileSystem类
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    //获取RemoteIterator 得到所有的文件或者文件夹,第一个参数指定遍历的路径,第二个参数表示是否要递归遍历
    RemoteIterator<LocatedFileStatus> locatedFileStatusRemoteIterator = fileSystem.listFiles(new Path("/"), true);
    while (locatedFileStatusRemoteIterator.hasNext()){
        LocatedFileStatus next = locatedFileStatusRemoteIterator.next();
        System.out.println(next.getPath().toString());
    }
    fileSystem.close();
}

6.下载文件到本地

程序执行的main方法
/**
 * 拷贝文件的到本地
 * @throws Exception
 */
@Test
public void getFileToLocal()throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    FSDataInputStream open = fileSystem.open(new Path("/test/input/install.log"));
    FileOutputStream fileOutputStream = new FileOutputStream(new File("c:\\install.log"));
    IOUtils.copy(open,fileOutputStream );
    IOUtils.closeQuietly(open);
    IOUtils.closeQuietly(fileOutputStream);
    fileSystem.close();
}

8.hdfs文件上传

@Test
public void putData() throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    fileSystem.copyFromLocalFile(new Path("file:///c:\\install.log"),new Path("/hello/mydir/test"));
    fileSystem.close();
}

9.HDFS的小文件合并
由于hadoop擅长存储大文件,因为大文件的元数据信息比较少,如果hadoop集群当中有大量的小文件,那么每个小文件都需要维护一份元数据信息,会大大的增加集群管理元数据的内存压力,所以在实际工作当中,如果有必要一定要将小文件合并成大文件进行一起处理
在我们的hdfs 的shell命令模式下,可以通过命令行将很多的hdfs文件合并成一个大文件下载到本地,命令如下
cd /export/servers
hdfs dfs -getmerge /config/*.xml ./hello.xml