大数据之Hadoop

Hadoop的简介

在这里插入图片描述

Apache Hadoop是一款支持数据密集型分布式应用程序并以Apache 2.0许可协议发布的开源软件框架,有助于使用许多电脑组成的网络来解决数据、计算密集型的问题。基于MapReduce计算模型,它为大数据的分布式存储与处理提供了一个软件框架。所有的Hadoop模块都有一个基本假设,即硬件故障是常见情况,应该由框架自动处理。

Apache Hadoop的核心模块分为存储和计算模块,前者被称为Hadoop分布式文件系统(HDFS),后者即MapReduce计算模型。Hadoop框架先将文件分成数据块并分布式地存储在集群的计算节点中,接着将负责计算任务的代码传送给各节点,让其能够并行地处理数据。这种方法有效利用了数据局部性,令各节点分别处理其能够访问的数据。与传统的超级电脑架构相比,这使得数据集的处理速度更快、效率更高。

Hadoop组件

HDFS

HDFS分布式存储框架,适合海量数据的存储

HDFS的原理:是一种允许文件通过网络在多台主机上分享的文件系统,可让多机器上的多用户分享文件和存储空间

通透性;让实际上是通过网络来访问文件的动作,由程序与用户看来,就像是访问本地的磁盘一般。

容错;即使系统中有某些节点宕机,整体来说系统仍然可以持续运作而不会有数据损失【通过副本机制实现】。

分布式文件管理系统很多,hdfs只是其中一种,不合适小文件。

HDFS的节点
一、管理节点NameNode

NameNode是整个文件系统的管理节点,它维护着整个文件系统的目录树,文件目录的元信息和每个文件对应的数据块列表,接收用户的操作请求。

①维护每个DataNode的心跳,用于查看数据节点是否存活

②用于管理HDFS中所有的文件信息,包括文件的路径、文件的block块信息、文件的大小及其权限

③响应客户端的请求,根据上传或下载返回请求的结果

二、数据节点DataNode
DataNode节点提供真实文件数据的存储服务

①用于存储客户端上传的具体文件,文件上传过程会进行数据的切分,根据block块大小会将文件切分成多块,并且block块会存储在多个DataNode中

②响应客户端的上传请求和下载请求

③检测和管理存储在本节点上的block块信息

Block块为HDFS中基本的存储单位,但并不是最小的存储单位

一个文件的长度大小是size,那么从文件的偏移开始,按照固定的大小,顺序对文件进行划分并编号,划分好的每一个块称为一个block。

BLock块大小:

​ 1.在Hadoop1.X中大小默认为64M

​ 2.在Hadoop2.X中大小默认为128M

为什么BLock块大小为128M?

​ 问题:

		1. 如果一个文件不进行做拆分,对应一个DataNode中存储一个完整的文件,对导致DataNode存取压力过大,由于计算本地化,所以计算压力也会很大,由此需要拆分
		2. 如果拆分过大? 会导致1问题,同时下载速度也会很慢
		3. 如果拆分过小? 对应一个拆分过的文件会特别多,那么对应的元数据信息也会很多,会导致NameNode压力过大,检索数据信息时间会很长
基于以上问题如何设计?

规定:一个数据的检索时间最长不超过10ms,规定检索时间为BLock数据下载时间的1%为1秒

数据下载时间由网络及磁盘决定,由于磁盘和网络传输时间在100M/s,基于这个理论值得到 block块大小是在128M

设置BLock块大小

hdfs-site.xml中dfs.blocksize属性

副本数

Replication。多副本。默认是三个

设置hdfs-site.xml的dfs.replication属性

副本机制与机架?

如果当前集群设置副本数为3 那么对应存放位置为在同一个机架中会存放两个份,其他机架中保存一份,可以保证数据的安全。

为什么block块不每个机架存放一份?

由于后续做数据计算时,会将计算结果进行汇总,如果数据过于分散,那么后续网络传输压力会很大。

三、Secondary NameNode

Secondary NameNode是NameNode的备份节点

①由于NameNode需要保存元数据信息,并且元数据信息是放在内存中的

②数据存放在内存中会有安全性问题,需要持久化

③由于NameNode对外请求压力大,并不适合做持久化工作,所以交由Secondary NameNode

HDFS的读写
一、HDFS的读流程

在这里插入图片描述
1.首先调用FileSystem对象的open方法,其实是一个DistributedFileSystem的实例

2.DistributedFileSystem通过rpc获得文件的第一个block的locations,同一block按照副本数会返回多个locations,这些locations按照hadoop拓扑结构排序,距离客户端近的排在前面.

3.前两步会返回一个FSDataInputStream对象,该对象会被封装成DFSInputStream对象,DFSInputStream可以方便的管理datanode和namenode数据流。客户端调用read方法,DFSInputStream最会找出离客户端最近的datanode并连接。

4.数据从datanode源源不断的流向客户端。

5.如果第一块的数据读完了,就会关闭指向第一块的datanode连接,接着读取下一块。这些操作对客户端来说是透明的,客户端的角度看来只是读一个持续不断的流。

6.如果第一批block都读完了,DFSInputStream就会去namenode拿下一批blocks的location,然后继续读,如果所有的块都读完,这时就会关闭掉所有的流

如果在读数据的时候,DFSInputStream和datanode的通讯发生异常,就会尝试正在读的block的排第二近的datanode,并且会记录哪个datanode发生错误,剩余的blocks读的时候就会直接跳过该datanode。DFSInputStream也会检查block数据校验和,如果发现一个坏的block,就会先报告到namenode节点,然后DFSInputStream在其他的datanode上读该block的镜像

该设计的方向就是客户端直接连接datanode来检索数据并且namenode来负责为每一个block提供最优的datanode,namenode仅仅处理block location的请求,这些信息都加载在namenode的内存中,hdfs通过datanode集群可以承受大量客户端的并发访问。

二、HDFS的写流程

在这里插入图片描述
1.客户端通过调用DistributedFileSystem的create方法创建新文件,并向NameNode发送请求,namenode接收到请求后会做各种校验,比如文件是否存在,客户端有无权限去创建等。如果校验通过,namenode就会记录下新文件,否则就会抛出IO异常

2.NameNode在客户端中创建DFSOutputStream对象,DFSOutputStream可以协调namenode和datanode并会把数据切成一个个小packet,每个Packet大小为64K,packet会由多个chunks组成,每个chunk大小为512字节,并伴随一个4字节的校验码,然后将packet存放至dataQuene中

3.当dataQuene中存在数据时,会向NameNode请求上传第一个BLock块

4.NameNode接收到请求后,返回BLock块存储位置列表

5.客户端接收到列表后,根据机架感知(拓扑结构),以及负载均衡,寻找最优的一个DataNode节点进行连接

6.客户端会与最近的DataNode创建通道(pipeline),之后该DataNode会与其他的DataNode创建通道

7.开始上传第一个packet数据,dataQuene会将packet数据发送到本地的ackQueue

8.本地的ackQueue会将packet上传至DataNode的ackQueue

9.当第一个DataNode接收到packet,会将packet从ackQueue中分发至其他DataNode的ackQueue

10.当pipeline中的所有datanode都表示已经收到的时候,这时本地akc queue才会把对应的packet包移除掉。

11.当第一个BLock块中所有的packet数据上传完成,会重新向NameNode申请上传下一个BLock块所在的Locations位置,按之前的顺序再次提交,直到所有的BLock上传完成后通知namenode把文件标示为已完成

问题:如果上传过程中出现问题?

如果在写的过程中某个datanode发生错误,会采取以下几步:

​ 1) pipeline被关闭掉;

​ 2)为了防止丢包ack queue里的packet会同步到data queue里;

​ 3)把产生错误的datanode上当前在写但未完成的block删掉;

​ 4)block剩下的部分被写到剩下的两个正常的datanode中;

​ 5)namenode找到另外的datanode去创建这个块的复制

MapReduce

MapReduce作用

将用户的业务逻辑和Hadoop中的组件结合起来形成一个分布式程序

JAVA类型与Hadoop类型对应

Java中的数据类型通过序列化以后,会产生大量的描述数据信息,在网络传输过程中,会占用大量资源,所以Hadoop实现了自己的一套数据结构,并且能够和Java中的数据类型进行对应

Hadoop text 对应 JAVA中的 String

Java类型 Writable
字符串(String) Text
布尔型(boolean) BooleanWritable
字节型(byte) ByteWritable
整型(int) IntWritable
VIntWritable IntWritable
浮点型(float) FloatWritable
长整型(long) LongWritable
VLongWritable LongWritable
双精度浮点型(double) DoubleWritable
空值NULL NullWritable
MapReduce代码解析

1.Map端

​ ① 输入数据为KV数据对,其中K为其数据读取的偏移量,Value为当前输入源的一行数据

​ ②输出为KV数据对

​ ③所有的业务逻辑都是在Map方法中实现的

​ ④执行过程中每一行数据都会调用一次map方法

​ ⑤一个切片数产生一个MapTask任务

2.Reduce端

​ ①输入数据为Map端输出的数据

​ ②所有的业务逻辑都在Reduce方法中实现

​ ③如果ReduceTask数量为n个,那么对应输出文件也为n个

​ ④Reduce中会将Map端输出的相同的Key放置一起,对应其Value是一个Iterable迭代器

3.Driver端

​ Driver的作用是将当前的程序打包提交给yarn集群(如果是在集群中运行),并通过Yarn中的ResourceManager创建当前程序的ApplicationMaster

具体代码步骤:

​ ① 创建job,并设置Job中的配置,包括类,当前Job名称

​ ②通过job设置Mapper和 Reducer(Reducer并不是必须的)

​ ③设置Mapper输出的KV类以及最终输出的KV类

​ ④设置输入输出的数据路径

​ ⑤提交job

切片

在这里插入图片描述

切片源码

1、

public List<InputSplit> getSplits(JobContext job) throws IOException {
   
     // 设置一个时间管理器,用于统计切片所花费的时间
    StopWatch sw = new StopWatch().start();
    // getFormatMinSplitSize=1L getMinSplitSize =1L => minSize = 1
    long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
    // maxSize=9223372036854775807L 
    long maxSize = getMaxSplitSize(job);

    // generate splits
    // 创建List用来存储split
    List<InputSplit> splits = new ArrayList<InputSplit>();
    // 通过listStatus获取输入路径下所有的文件
    List<FileStatus> files = listStatus(job);
    for (FileStatus file: files) {
   
       // 获取文件所在路径
      Path path = file.getPath();
       // 获取文件长度
      long length = file.getLen();
      if (length != 0) {
   
        BlockLocation[] blkLocations;
        // 判断file对象类型
        if (file instanceof LocatedFileStatus) {
   
            // 本地文件系统
          blkLocations = ((LocatedFileStatus) file).getBlockLocations();
        } else {
   
           // HDFS上的文件系统
          FileSystem fs = path.getFileSystem(job.getConfiguration());
          blkLocations = fs.getFileBlockLocations(file, 0, length);
        }
          // 判断文件是否可以切分,有的文件会是压缩格式
        if (isSplitable(job, path)) {
   
          // blockSize值为当前文件大小,如果当文件大小超过128M时,大小为128M
          long blockSize = file.getBlockSize();
          // 经过对比后 blockSize=splitSize
          long splitSize = computeSplitSize(blockSize, minSize, maxSize);
		  // 当前文件长度赋予 bytesRemaining表示剩余长度
          long bytesRemaining = length;
          // SPLIT_SLOP=1.1
          while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
   
              // 当前文件大小超过切片大小的1.1倍,开始切分数据
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            // 创建切片 length-bytesRemaining表示开始位置  splitSize表示切片长度
             splits.add(makeSplit(path, length-bytesRemaining,   splitSize,
                        blkLocations[blkIndex].getHosts(),
                        blkLocations[blkIndex].getCachedHosts()));
            // 将剩余长度减去所切去的切片长度
              bytesRemaining -= splitSize;
          }

          if (bytesRemaining != 0) {
   
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            // 如果剩余字节数小于 128M * 1.1 之后作为一个切片保存
              splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
                       blkLocations[blkIndex].getHosts(),
                       blkLocations[blkIndex].getCachedHosts()));
          }
        } else {
    // not splitable
           // 如果不能切片,那么会创建一个切片用于处理数据
          splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
                      blkLocations[0].getCachedHosts()));
        }
      } else {
    
        // 如果文件为空,那么也会创建一个切片
        //Create empty hosts array for zero length files
        splits.add(makeSplit(path, 0, length, new String[0]));
      }
    }
    // Save the number of input files for metrics/loadgen
    job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
    sw.stop();
    if (LOG.isDebugEnabled()) {
   
      LOG.debug("Total # of splits generated by getSplits: " + splits.size()
          + ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
    }
    return splits;
  }

总结:

​ ① 切片大小并不总是为128M

​ ②一个文件小于128M也会产生一个切片

​ ③ 一个切片对应一个MapTask任务

2、

public List<InputSplit> getSplits(JobContext job) throws IOException {
   
    StopWatch sw = new StopWatch().start();
    long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
    long maxSize = getMaxSplitSize(job);

    // generate splits
    List<InputSplit> splits = new ArrayList<InputSplit>();
    List<FileStatus> files = listStatus(job);
    for (FileStatus file: files) {
   
      Path path = file.getPath();
      long length = file.getLen();
      if (length != 0) {
   
        BlockLocation[] blkLocations;
        if (file instanceof LocatedFileStatus) {
   
          blkLocations = ((LocatedFileStatus) file).getBlockLocations();
        } else {
   
          FileSystem fs = path.getFileSystem(job.getConfiguration());
          blkLocations = fs.getFileBlockLocations(file, 0, length);
        }
        if (isSplitable(job, path)) {
   
          long blockSize = file.getBlockSize(
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蘑菇星辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值