第一章 Hadoop 分布式文件系统HDFS(下)

第一代大数据框架Hadoop由:HDFS分布式文件系统、MapReduce计算框架和YARN资源调度框架组成,本文为学习HDFS分布式文件系统时记录的学习笔记。补充了HDFS的读写流程、小文件治理等知识。


@

  • 一、HDFS的运行模式和HDFS的优缺点  
    • 1.Hadoop的运行模式
    • 2.HDFS的优缺点
  • 二、HDFS的读写流程  
    • 1.HDFS的写入流程
    • 2.HDFS的读取流程
  • 三、datanode工作机制以及数据存储
  • 四、HDFS的小文件治理  
    • 1.小文件过多导致的问题
    • 2.小文件治理方案
  • 五、HDFS的其他功能介绍  
    • 1.多个集群之间的数据拷贝
    • 2.HDFS回收站

 


前言

上一篇文章中介绍了Hadoop框架,详细描述了HDFS架构,其中block的划分以及 NameNode和SecondaryNameNode功能剖析需要重点掌握,本文将重点描述HDFS的读写流程、datanode工作机制以及数据存储和hdfs的小文件治理等一些关于HDFS的进阶知识。


一、HDFS的运行模式和HDFS的优缺点

1.Hadoop的运行模式

1. 本地运行模式

  • 无需任何守护进程,所有的程序都运行在同一个JVM上执行。在独立模式下调试MR程序非常高效方便。所以一般该模式主要是在学习或者开发阶段调试使用

2. 伪分布式运行模式

  • Hadoop守护进程运行在本地机器上,模拟一个小规模的集群,换句话说,可以配置一台机器的Hadoop集群,伪分布式是完全分布式的一个特例。

3.完全分布式运行模式(重点)

  • Hadoop守护进程运行在一个集群上,需要使用多台机器来实现完全分布式服务的安装。

## 执行自定义jar包命令:hadoop jar /jar包路径 pi [参数...]
yarn jar /jar包路径 pi [参数...]

2.HDFS的优缺点

1.HDFS的优点

  1. 容错性高

  • 数据自动保存多个副本。他通过增加副本的形式,提高容错性(上文中提到了每个block会存三个副本)。

  • 某一个副本丢失以后,它可以自动恢复,这是由HDFS内部机制实现的,不需要人为操控。

  1. 适合批量处理

  • 它是通过移动计算而不是移动数据(指在进行MR计算的时候,会把task调度在需要处理的数据的所在节点上运行)。

  • 它会把数据位置暴露给计算框架 。

  1. 适合大数据处理

  • 数据规模:能够处理的数据规模达到GB、TB升至PB级别的数据。

  • 文件规模:能够处理百万规模以上的文件数量,数量相当之大。

  • 节点规模:能够处理10K节点的规模。

  1. 流式数据访问

  • 一次写入,多次读取,不能随机修改,只能追加。

  • 它能保证数据的一致性。

  1. 可构建在廉价机器上

  • 它通过多副本机制,提高可靠性。

  • 它提供了容错和恢复机制。比如某一个副本丢失,可以通过其它副本来恢复。

1.HDFS的缺点

  1. 不适合低延时数据访问;

  • 比如毫秒级的来存储数据,这是不行的,它做不到。

  • 它适合高吞吐率的场景,就是在某一时间内写入大量的数据。但是它在低延时的情况 下是不行的,比如毫秒级以内读取数据,这样它是很难做到的。

  1. 无法高效的对大量小文件进行存储

  • 存储大量小文件的话,它会占用 NameNode大量的内存来存储文件、目录和块信息。这样是不可取的,因为NameNode的内存总是有限的。

  • 小文件存储的寻道时间会超过读取时间,它违反了HDFS的设计目标。 改进策略

  1. 并发写入、文件随机修改

  • 一个文件只能有一个写,不允许多个线程同时写。

  • 仅支持数据 append(追加),不支持文件的随机修改。

二、HDFS的读写流程

1.HDFS的写入流程

大数据环境准备hadoop集群搭建(三节点)_hadoop 文件上传流程如下:

  • 创建文件: ①HDFS client向HDFS写入数据,先调用DistributedFileSystem.create(); ②RPC调用namenode的create(),会在HDFS目录树中指定的路径,添加新文件;并将操作记录在edits.log中namenode.create()方法执行完后,返回一个FSDataOutputStream,它是DFSOutputStream的包装类。

  • 建立数据流管道 ③client调用DFSOutputStream.write()写数据(先写第一个块的数据,暂时叫blk1); ④DFSOutputStream通过RPC调用namenode的addBlock,向namenode申请一个空的数据块block; ⑤addBlock返回LocatedBlock对象;此对象中包含了当前blk要存储在哪三个datanode的信息,比如dn1、dn2、dn3; ⑥客户端,根据位置信息,建立数据流管道(图中蓝色线条);

  • 向数据流管道写当前块的数据 ⑦写数据时,先将数据写入一个检验块chunk中,写满512字节后,对此chunk计算校验和checksum值(4字节)(对同一个chunk无论计算多少次,得到的checksum值应该都是相等的,所以写入的节点在收到chunk块后再计算一次checksum值,通过判断两个checksum的值是否相等,可以判断数据是否有丢失); ⑧然后将chunk及对应校验和写入packet中,一个packet是64KB; ⑨随着源源不断的带校验和的chunk写入packet,当packet写满后,将packet写入dataqueue数据队列中; ⑩packet从队列中取出,沿pipeline发送到dn1,再从dn1发送到dn2,再从dn2发送到dn3; ⑪同时,此packet会保存一份到一个确认队列ack queue中; ⑫packet到达最后一个datanode即dn3后,做校验,将校验结果逆着pipeline方向回传到客户端,具体是校验结果从dn3传到dn2,dn2也会做校验,校验结果再传到dn1,dn1也做校验;结果再传回客户端; ⑬客户端根据校验结果,如果“成功”,则将将保存在ack queue中的packet删除;如果失败,则将packet取出,重新放回到data queue末尾,等待再次沿pipeline发送; ⑭如此,将block中的一个数据一个个packet发送出去;当此block发送完毕,即dn1、dn2、dn3都接受了blk1的完整的副本,那么三个dn分别RPC调用namenode的blockReceivedAndDeleted(),namenode会更新内存中block与datanode的对应关系(比如dn1上多了一个blk1副本);

  • 关闭dn1、dn2、dn3构建的pipeline;且文件还有下一个块时,再从④开始;直到文件全部数据写完 ⑮最终,调用DFSOutputStream的close() ⑯客户端调用namenode的complete(),告知namenode文件传输完成。

  • 容错 大数据环境准备hadoop集群搭建(三节点)_hadoop_02

  • 假设说当前构建的pipeline是dn1、dn2、dn3构成的当传输数据的过程中,dn2挂了或通信不畅了,则当前pipeline中断HDFS会如何做?

  • 先将ack queue中的所有packet全部放回到data queue中客户端RPC调用namenode updateBlockForPipeline(),为当前block(假设是blk1)生成新的版本比如ts1(本质是时间戳);

  • 故障dn2会从pipeline中删除,DFSOutputStream再RPC调用namenode的getAdditionalDatanode(),让namenode分配新的datanode,比如是dn4;

  • 输出流将原dn1、dn3与新的dn4组成新的管道,他们上边的blk1版本设置为新版本ts1,由于新添加的dn4上没有blk1的数据,客户端告知dn1或dn3,将其上的blk1的数据拷贝到dn4上新的数据管道建立好后,DFSOutputStream调用updatePipeline()更新namenode元数据。至此,pipeline恢复,客户端按正常的写入流程,完成文件的上传;

  • 故障datanode重启后,namenode发现它上边的block的blk1的时间戳是老的,会让datanode将blk1删除掉。

2.HDFS的读取流程

大数据环境准备hadoop集群搭建(三节点)_hadoop_03 一、读取流程 1、client端读取HDFS文件,client调用文件系统对象DistributedFileSystem的open方法; 2、返回FSDataInputStream对象(对DFSInputStream的包装); 3、构造DFSInputStream对象时,调用namenode的getBlockLocations方法,获得file的开始若干block(如blk1, blk2, blk3, blk4)的存储datanode(以下简称dn)列表;针对每个block的dn列表,会根据网络拓扑做排序,离client近的排在前; 4、调用DFSInputStream的read方法,先读取blk1的数据,与client最近的datanode建立连接,读取数据; 5、读取完后,关闭与dn建立的流; 6、读取下一个block,如blk2的数据(重复步骤4、5、6); 7、这一批block读取完后,再读取下一批block的数据(重复3、4、5、6、7); 8、完成文件数据读取后,调用FSDataInputStream的close方法。

二、容错 情况一:读取block过程中,client与datanode通信中断

  • client与存储此block的第二个datandoe建立连接,读取数据;

  • 记录此有问题的datanode,不会再从它上读取数据;

情况二:client读取block,发现block数据有问题

  • client读取block数据时,同时会读取到block的校验和,若client针对读取过来的block数据,计算检验和,其值与读取过来的校验和不一样,说明block数据损坏

  • client从存储此block副本的其它datanode上读取block数据(也会计算校验和)

  • 同时,client会告知namenode此情况;

三、datanode工作机制以及数据存储

大数据环境准备hadoop集群搭建(三节点)_hadoop_04

在这里插入图片描述

  • HDFS分布式文件系统也是一个主从架构,主节点是我们的namenode,负责管理整个集群以及维护集群的元数据信息

  • 从节点datanode,主要负责文件数据存储

大数据环境准备hadoop集群搭建(三节点)_hadoop_05

在这里插入图片描述

  1. datanode工作机制 1)一个数据块在datanode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。 2)DataNode启动后向namenode注册,通过后,周期性(6小时)的向namenode上报所有的块信息。 3)心跳是每3秒一次,心跳返回结果带有namenode给该datanode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个datanode的心跳,则认为该节点不可用。 4)集群运行中可以安全加入和退出一些机器。

  2. 数据完整性 1)当DataNode读取block的时候,它会计算checksum。 2)如果计算后的checksum,与block创建时值不一样,说明block已经损坏。 3)client读取其他DataNode上的block。 4)datanode在其文件创建后周期验证checksum。

  3. 掉线时限参数设置

  • datanode进程死亡或者网络故障造成datanode无法与namenode通信,namenode不会立即把该节点判定为死亡,要经过一段时间,这段时间暂称作超时时长。HDFS默认的超时时长为10分钟+30秒。如果定义超时时间为timeout,则超时时长的计算公式为:

timeout = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval。

  • 而默认的dfs.namenode.heartbeat.recheck-interval 大小为5分钟,dfs.heartbeat.interval默认为3秒。

  • 需要注意的是hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒。

<property>    <name>dfs.namenode.heartbeat.recheck-interval</name>    <value>300000</value></property><property>    <name> dfs.heartbeat.interval </name>    <value>3</value></property>

  1. DataNode的目录结构

  • 和namenode不同的是,datanode的存储目录是初始阶段自动创建的,不需要额外格式化。

  • 在**/hadoop-2.6.0-cdh5.14.2**/hadoopDatas/datanodeDatas/current这个目录下查看版本号

[root@node01 current]# cat VERSION #Thu Mar 14 07:58:46 CST 2019storageID=DS-47bcc6d5-c9b7-4c88-9cc8-6154b8a2bf39
clusterID=CID-dac2e9fa-65d2-4963-a7b5-bb4d0280d3f4
cTime=0
datanodeUuid=c44514a0-9ed6-4642-b3a8-5af79f03d7a4
storageType=DATA_NODE
layoutVersion=-56

具体解释

(1)storageID:存储id号

(2)clusterID集群id,全局唯一

(3)cTime属性标记了datanode存储系统的创建时间,对于刚刚格式化的存储系统,这个属性为0;但是在文件系统升级之后,该值会更新到新的时间戳。

(4)datanodeUuid:datanode的唯一识别码

(5)storageType:存储类型

(6)layoutVersion是一个负整数。通常只有HDFS增加新特性时才会更新这个版本号。

  1. Datanode多目录配置

  • datanode也可以配置成多个目录,每个目录存储的数据不一样。即:数据不是副本。具体配置如下:

<!--  在hdfs-site.xml中定义dataNode数据存储的节点位置,实际工作中,一般先确定磁盘的挂载目录,然后多个目录用,进行分割  --><property>   <name>dfs.datanode.data.dir</name>   <value>file:///hadoop-2.6.0-cdh5.14.2/hadoopDatas/datanodeDatas</value></property>

四、HDFS的小文件治理

1.小文件过多导致的问题

NameNode存储着文件系统的元数据,每个文件、目录、块大概有150字节的元数据;因此文件数量的限制也由NN内存大小决定,如果小文件过多则会造成NN的压力过大且HDFS能存储的数据总量也会变小。

2.小文件治理方案

HAR文件方案

本质上使用MR程序对小程序进行归档到一个.har文件中,所以需要启动yarn 大数据环境准备hadoop集群搭建(三节点)_hadoop_06

# 用法#1.创建归档文件archive -archiveName <NAME>.har -p <parent path> [-r <replication factor>]<src>* <dest>
bin/hadoop archive  -archiveName myhar.har -p /user/hadoop /user#2.查看归档文件hdfs dfs -lsr /user/myhar.har
hdfs dfs -lsr har:///user/myhar.har#3.解压归档文件hdfs dfs -mkdir -p /user/har
hdfs dfs -cp har:///user/myhar.har/* /user/har/

Sequence Files方案(重点)

大数据环境准备hadoop集群搭建(三节点)_hadoop_07

在这里插入图片描述

  • SequenceFile文件,主要由一条条record记录组成;

  • 具体结构(如上图):

    • 一个SequenceFile首先有一个4字节的header(文件版本号)

    • 接着是若干record记录

    • 其中Sync是同步点,读取SequenceFile时必须从Record的起点开始读起,否则会报错,此时需调用reader.syncSeen()方法,那么就会从读取位置的下一个同步点开始读取SequenceFile;

    • 每个record是键值对形式的;键值类型是可序列化类型,如IntWritable、Text,如图 大数据环境准备hadoop集群搭建(三节点)_hadoop_08

    • 记录间会随机的插入一些同步点sync marker,用于方便定位到记录边界

  • SequenceFile文件可以作为小文件的存储容器;

    • 每条record保存一个小文件的内容

    • 小文件名作为当前record的键;

    • 小文件的内容作为当前record的值;

    • 如10000个100KB的小文件,可以编写程序将这些文件放到一个SequenceFile文件。

  • 一个SequenceFile是可分割的,所以MapReduce可将文件切分成块,每一块独立操作。

  • 不像HAR,SequenceFile支持压缩。记录的结构取决于是否启动压缩

    • 不压缩NONE,如上图

    • 压缩RECORD,如上图

    • 压缩BLOCK,如下图,①一次性压缩多条记录;②每一个新块Block开始处都需要插入同步点

    • 支持两类压缩:

    • 在大多数情况下,以block(注意:指的是SequenceFile中的block,和HDFS中的block无关)为单位进行压缩是最好的选择

    • 因为一个block包含多条记录,利用record间的相似性进行压缩,压缩效率更高

    • 把已有的数据转存为SequenceFile比较慢。比起先写小文件,再将小文件写入SequenceFile,一个更好的选择是直接将数据写入一个SequenceFile文件,省去小文件作为中间媒介. 大数据环境准备hadoop集群搭建(三节点)_hadoop_09

  • 向SequenceFile写入数据

package com.hadoop.sequencefile;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IOUtils;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.SequenceFile;import org.apache.hadoop.io.Text;import org.apache.hadoop.io.compress.BZip2Codec;import java.io.IOException;import java.net.URI;public class SequenceFileWriteNewVersion {

    //模拟数据源;数组中一个元素表示一个文件的内容    private static final String[] DATA = {
            "The Apache Hadoop software library is a framework that allows for the distributed processing of large data sets across clusters of computers using simple programming models.",
            "It is designed to scale up from single servers to thousands of machines, each offering local computation and storage.",
            "Rather than rely on hardware to deliver high-availability, the library itself is designed to detect and handle failures at the application layer",
            "o delivering a highly-available service on top of a cluster of computers, each of which may be prone to failures.",
            "Hadoop Common: The common utilities that support the other Hadoop modules."    };

    public static void main(String[] args) throws IOException {
        //输出路径:要生成的SequenceFile文件名        String uri = "hdfs://node01:8020/writeSequenceFile";

        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(URI.create(uri), conf);
        //向HDFS上的此SequenceFile文件写数据        Path path = new Path(uri);

        //因为SequenceFile每个record是键值对的        //指定key类型        IntWritable key = new IntWritable(); //key数字 -> int -> IntWritable        //指定value类型        Text value = new Text();//value -> String -> Text        //创建向SequenceFile文件写入数据时的一些选项        //要写入的SequenceFile的路径        SequenceFile.Writer.Option pathOption       = SequenceFile.Writer.file(path);
        //record的key类型选项        SequenceFile.Writer.Option keyOption        = SequenceFile.Writer.keyClass(IntWritable.class);
        //record的value类型选项        SequenceFile.Writer.Option valueOption      = SequenceFile.Writer.valueClass(Text.class);
        //SequenceFile压缩方式:NONE | RECORD | BLOCK三选一        //方案一:RECORD、不指定压缩算法//        SequenceFile.Writer.Option compressOption   = SequenceFile.Writer.compression(SequenceFile.CompressionType.RECORD);//        SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressOption);        //方案二:BLOCK、不指定压缩算法//        SequenceFile.Writer.Option compressOption   = SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK);//        SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressOption);        //方案三:使用BLOCK、压缩算法BZip2Codec;压缩耗时间        //再加压缩算法        BZip2Codec codec = new BZip2Codec();
        codec.setConf(conf);
        SequenceFile.Writer.Option compressAlgorithm = SequenceFile.Writer.compression(SequenceFile.CompressionType.RECORD, codec);
        //创建写数据的Writer实例        SequenceFile.Writer writer = SequenceFile.createWriter(conf, pathOption, keyOption, valueOption, compressAlgorithm);

        for (int i = 0; i < 100000; i++) {
            //分别设置key、value值            key.set(100000 - i);
            value.set(DATA[i % DATA.length]); //%取模 3 % 3 = 0;            System.out.printf("[%s]\t%s\t%s\n", writer.getLength(), key, value);
            //在SequenceFile末尾追加内容            writer.append(key, value);
        }
        //关闭流        IOUtils.closeStream(writer);
    }
}

  • 命令查看SequenceFile内容

hadoop fs -text /writeSequenceFile

  • 读取SequenceFile文件

package com.hadoop.sequencefile;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IOUtils;import org.apache.hadoop.io.SequenceFile;import org.apache.hadoop.io.Writable;import org.apache.hadoop.util.ReflectionUtils;import java.io.IOException;public class SequenceFileReadNewVersion {

    public static void main(String[] args) throws IOException {
        //要读的SequenceFile        String uri = "hdfs://node01:8020/writeSequenceFile";
        Configuration conf = new Configuration();
        Path path = new Path(uri);

        //Reader对象        SequenceFile.Reader reader = null;
        try {
            //读取SequenceFile的Reader的路径选项            SequenceFile.Reader.Option pathOption = SequenceFile.Reader.file(path);

            //实例化Reader对象            reader = new SequenceFile.Reader(conf, pathOption);

            //根据反射,求出key类型对象            Writable key = (Writable)
                    ReflectionUtils.newInstance(reader.getKeyClass(), conf);
            //根据反射,求出value类型对象            Writable value = (Writable)
                    ReflectionUtils.newInstance(reader.getValueClass(), conf);

            long position = reader.getPosition();
            System.out.println(position);

            while (reader.next(key, value)) {
                String syncSeen = reader.syncSeen() ? "*" : "";
                System.out.printf("[%s%s]\t%s\t%s\n", position, syncSeen, key, value);
                //移动到下一个record开头的位置                position = reader.getPosition(); // beginning of next record            }
        } finally {
            IOUtils.closeStream(reader);
        }
    }
}

五、HDFS的其他功能介绍

1.多个集群之间的数据拷贝

在我们实际工作当中,极有可能会遇到将测试集群的数据拷贝到生产环境集群,或者将生产环境集群的数据拷贝到测试集群,那么就需要我们在多个集群之间进行数据的远程拷贝,hadoop自带也有命令可以帮我们实现这个功能

1、本地文件拷贝scp

cd /kkb/soft
scp -r jdk-8u141-linux-x64.tar.gz hadoop@node02:/lyc/soft

2、集群之间的数据拷贝distcp

cd /lyc/install/hadoop-2.6.0-cdh5.14.2/
bin/hadoop distcp hdfs://node01:8020/jdk-8u141-linux-x64.tar.gz hdfs://cluster2:8020/

2.HDFS回收站

任何一个文件系统,基本上都会有垃圾桶机制,也就是删除的文件,不会直接彻底清掉,我们一把都是将文件放置到垃圾桶当中去,过一段时间之后,自动清空垃圾桶当中的文件,这样对于文件的安全删除比较有保证,避免我们一些误操作,导致误删除文件或者数据。

1、回收站配置两个参数

 默认值fs.trash.interval=0,0表示禁用回收站,可以设置删除文件的存活时间。
 默认值fs.trash.checkpoint.interval=0,检查回收站的间隔时间。
 要求fs.trash.checkpoint.interval <= fs.trash.interval。

2、启用回收站 修改所有服务器的core-site.xml配置文件

<!-- 开启hdfs的垃圾桶机制,删除掉的数据可以从垃圾桶中回收,单位分钟 --><property> <name>fs.trash.interval</name> <value>10080</value></property>

3、查看回收站回收站在集群的/user/hadoop/.Trash/ 这个路径下

4、通过javaAPI删除的数据,不会进入回收站,需要调用moveToTrash()才会进入回收站

Trash trash = New Trash(conf);
trash.moveToTrash(path);

5、恢复回收站数据

hdfs dfs -mv trashFileDir  hdfsdir
trashFileDir :回收站的文件路径
hdfsdir  :将文件移动到hdfs的哪个路径下

6、清空回收站

hdfs dfs -expunge

总结

至此HDFS的学习笔记已经整理完毕,更多关于HDFS的知识这里附上官网地址:http://hadoop.apache.org/,下一篇将开始整理MapReduce的知识。