1.Hadoop是什么?
Hadoop是一个由Apache基金会所开发的分布式系统基础架构
主要解决,海量数据的存储和海量数据的分析计算问题
广义上来说,HADOOP通常是指一个更广泛的概念——HADOOP生态圈
2.Hadoop发行版本:
Apache、Cloudera、Hortonworks(需要明确自己使用的版本)
Apache版本最原始(最基础)的版本,对于入门学习最好
Cloudera在大型互联网企业中用的较多
Hortonworks文档较好
3.Hadoop优势
高可靠性:Hadoop底层维护多个数据副本,所以即使Hadoop某个计算元素或存储出现故障,也不会导致数据的丢失。
高扩展性:在集群间分配任务数据,可方便的扩展数以千计的节点。
高效性:在MapReduce的思想下,Hadoop是并行工作的,以加快任务处理速度。
高容错性:能够自动将失败的任务重新分配。
4.Hadooo组成
Hadoop1.x组成:HDFS、MapReduce
Hadoop2.x组成:HDFS、MapReduce、YARN
角色:
HDFS:一个高可靠、高吞吐量的分布式文件系统
MapReduce:一个分布式的离线并行计算框架
YARN:作业调度与集群资源管理的框架
Common:支持其他模块的工具模块
5.Hadoop运行环境搭建
单机模式
伪分布式
完全分布式
HDFS简介
HDFS产生背景
随着数据量越来越大,在一个操作系统管辖的范围内存不下了,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。
HDFS概念
HDFS,它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。
HDFS的设计适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。
HDFS优缺点
优点
第一点:高容错性
数据自动保存多个副本。它通过增加副本的形式,提高容错性。
某一个副本丢失以后,它可以自动恢复。
第二点:适合大数据处理。两个维度:数据规模和文件规模
数据规模:能够处理数据规模达到 GB、TB、甚至PB级别的数据。
文件规模:能够处理百万规模以上的文件数量,数量相当之大。
第三点:流式数据访问,它能保证数据的一致性。
第四点:可构建在廉价机器上,通过多副本机制,提高可靠性。
缺点
第一点:不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。
第二点:无法高效的对大量小文件进行存储(重点)
1.存储大量小文件的话,它会占用 NameNode大量的内存来存储文件、目录和块信息。这样是不可取的,因为NameNode的内存总是有限的。
2.小文件存储的寻道时间会超过读取时间,它违反了HDFS的设计目标。
第三点:不支持并发写入、文件随机修改
1.一个文件只能有一个写,不允许多个线程同时写。
2.仅支持数据 append(追加),不支持文件的随机修改。
HDFS组成架构
Hdfs架构
Client:客户端。
文件切分。文件上传 HDFS 的时候,Client 将文件切分成一个一个的Block,然后进行存储。
与NameNode交互,获取文件的位置信息。
与DataNode交互,读取或者写入数据。
Client提供一些命令来管理HDFS,比如启动或者关闭HDFS。
Client可以通过一些命令来访问HDFS。
NameNode:就是Master,它是一个主管、管理者。
管理HDFS的名称空间。
管理数据块(Block)映射信息
配置副本策略
处理客户端读写请求。
DataNode:就是Slave,NameNode下达命令,DataNode执行实际的操作。
存储实际的数据块。
执行数据块的读/写操作。
Secondary NameNode:并非NameNode的热备。当NameNode挂掉的时候,它并不能马上替换NameNode并提供服务。
辅助NameNode,分担其工作量。
定期合并Fsimage和Edits,并推送给NameNode。
在紧急情况下,可辅助恢复NameNode。
1.5 HDFS 文件块大小
HDFS中的文件在物理上是分块存储(block),块的大小可以通过配置参数(dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,老版本中是64M。
HDFS文件是分块存储
块的大小可以设置(dfs.blocksize)来规定
默认大小
Hadoop1.x 64MB
Hadoop2.x 128MB
HDFS Shell操作
基本语法
hdfs dfs 具体命令
命令大全
hdfs dfs
[-appendToFile <localsrc> ... <dst>]
[-cat [-ignoreCrc] <src> ...]
[-checksum <src> ...]
[-chgrp [-R] GROUP PATH...]
[-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
[-chown [-R] [OWNER][:[GROUP]] PATH...]
[-copyFromLocal [-f] [-p] <localsrc> ... <dst>]
[-copyToLocal [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-count [-q] <path> ...]
[-cp [-f] [-p] <src> ... <dst>]
[-createSnapshot <snapshotDir> [<snapshotName>]]
[-deleteSnapshot <snapshotDir> <snapshotName>]
[-df [-h] [<path> ...]]
[-du [-s] [-h] <path> ...]
[-expunge]
[-get [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-getfacl [-R] <path>]
[-getmerge [-nl] <src> <localdst>]
[-help [cmd ...]]
[-ls [-d] [-h] [-R] [<path> ...]]
[-mkdir [-p] <path> ...]
[-moveFromLocal <localsrc> ... <dst>]
[-moveToLocal <src> <localdst>]
[-mv <src> ... <dst>]
[-put [-f] [-p] <localsrc> ... <dst>]
[-renameSnapshot <snapshotDir> <oldName> <newName>]
[-rm [-f] [-r|-R] [-skipTrash] <src> ...]
[-rmdir [--ignore-fail-on-non-empty] <dir> ...]
[-setfacl [-R] [{
-b|-k} {
-m|-x <acl_spec>} <path>]|[--set <acl_spec> <path>]]
[-setrep [-R] [-w] <rep> <path> ...]
[-stat [format] <path> ...]
[-tail [-f] <file>]
[-test -[defsz] <path>]
[-text [-ignoreCrc] <src> ...]
[-touchz <path> ...]
[-usage [cmd ...]]
常用命令
-help:查看命令的使用方式
hdfs dfs -help rm
-ls: 显示目录信息
hdfs dfs -ls /
-mkdir:在hdfs上创建目录
hdfs dfs -mkdir /bigdata
hdfs dfs -mkdir -p /kgc/test
-moveFromLocal从本地剪切粘贴到HDFS文件系统
hdfs dfs -moveFromLocal ./hello.txt /kgc/test
-appendToFile :追加一个文件到已经存在的文件末尾
hdfs dfs -appendToFile test1.txt /kgc/test/hello.txt
-cat :显示文件内容
hdfs dfs -cat /kgc/test/hello.txt
-tail:显示一个文件的末尾
hdfs dfs -tail /kgc/test/hello.txt
-chgrp 、-chmod、-chown:linux文件系统中的用法一样,修改文件所属权限
hdfs dfs -chmod 666 /kgc/test/hello.txt
hdfs dfs -chown kgc:kgc /kgc/test/hello.txt
-copyFromLocal:从本地文件系统中拷贝文件到hdfs路径去
hdfs dfs -copyFromLocal README.txt /
-copyToLocal:从hdfs拷贝到本地
hdfs dfs -copyToLocal /kgc/test/hello.txt ./
-cp :从hdfs的一个路径拷贝到hdfs的另一个路径
hdfs dfs -cp /kgc/test/hello.txt /hello1.txt
-mv:在hdfs目录中移动文件
hdfs dfs -mv /hello1.txt /kgc/test/
-get:等同于copyToLocal,就是从hdfs下载文件到本地
hdfs dfs -get /kgc/test/hello.txt ./
-getmerge :合并下载多个文件,比如hdfs的目录 /aaa/下有多个文件:log.1, log.2,log.3,...
hdfs dfs -getmerge /user/kgc/test/* ./merge.txt
-put:等同于copyFromLocal
hdfs dfs -put ./merge.txt /user/kgc/test/
-rm:删除文件或文件夹
hdfs dfs -rm /user/kgc/test/merge.txt
-rmdir:删除空目录
hdfs dfs -rmdir /test
-du统计文件夹的大小信息
hdfs dfs -du -s -h /user/kgc/test
hdfs dfs -du -h /user/kgc/test
-setrep:设置hdfs中文件的副本数量
hdfs dfs -setrep 10 /kgc/test/hello.txt
HDFS Java API
HDFS的API操作
1.创建工程并添加依赖
2.获取文件系统FileSystem
3.文件操作
4.关闭资源
3.3 HDFS的I/O流操作
HDFS文件上传/下载
1 获取文件系统
2 创建输入流
3 获取输出流
4 流对拷
5 关闭资源
HDFS读写流程
HDFS写数据流程
1.客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
2.NameNode返回是否可以上传。不能上传会返回异常。
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(64KB)为单位,dn1收到一个packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
8.当一个block传输完成之后,客户端再次请求NameNode上传第二个block的服务器。(重复执行3-7步)。
HDFS读数据流程
HDFS的读数据流程
1.首先调用FileSystem.open()方法,获取到DistributedFileSystem实例
2.DistributedFileSystem 向Namenode发起RPC(远程过程调用)请求获得文件的开始部分或全部block列表,对于每个返回的块,都包含块所在的DataNode地址。这些DataNode会按照Hadoop定义的集群拓扑结构得出客户端的距离,然后再进行排序。如果客户端本身就是一个DataNode,那么他将从本地读取文件。
3.DistributedFileSystem会向客户端client返回一个支持文件定位的输入流对象FSDataInputStream,用于客户端读取数据。FSDataInputStream包含一个DFSInputStream对象,这个对象用来管理DataNode和NameNode之间的I/O
4.客户端调用read()方法,DFSInputStream就会找出离客户端最近的datanode并连接datanode
5.DFSInputStream对象中包含文件开始部分的数据块所在的DataNode地址,首先它会连接包含文件第一个块最近DataNode。随后,在数据流中重复调用read()函数,直到这个块全部读完为止。如果第一个block块的数据读完,就会关闭指向第一个block块的datanode连接,接着读取下一个block块
6.如果第一批block都读完了,DFSInputStream就会去NameNode拿下一批blocks的location,然后继续读,如果所有的block块都读完,这时就会关闭掉所有的流。
HDFS如何控制客户端读取哪个副本节点数据
HDFS满足客户端访问副本数据的最近原则。即客户端距离哪个副本数据最近,HDFS就让哪个节点把数据给客户端。
NameNode和SecondaryNameNode关系
NameNode和Secondary NameNode工作机制
第一阶段:namenode启动
1.第一次启动namenode格式化后,创建fsimage和edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存。
2.客户端对元数据进行增删改的请求
3.namenode记录操作日志,更新滚动日志。
4.namenode在内存中对数据进行增删改
第二阶段:Secondary NameNode工作
1.Secondary NameNode询问namenode是否需要checkpoint。直接带回namenode是否检查结果。
2.Secondary NameNode请求执行checkpoint。
3.namenode滚动正在写的edits日志
4.将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode
5.Secondary NameNode加载编辑日志和镜像文件到内存,并合并。
6.生成新的镜像文件fsimage.chkpoint
7.拷贝fsimage.chkpoint到namenode
8.namenode将fsimage.chkpoint重新命名成fsimage
Fsimage和Edits
1.概念
2.使用oiv和oev命令查看fsimage和edits文件
# 1.查看oiv和oev命令
hdfs
oiv apply the offline fsimage viewer to an fsimage
oev apply the offline edits viewer to an edits file
# 2.oiv查看fsimage文件
# 基本语法
hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径
# 案例实操
hdfs oiv -p XML -i fsimage_0000000000000000023 -o /opt/install/hadoop/fsimage.xml
cat opt/install/hadoop/fsimage.xml
将显示的xml文件内容拷贝到eclipse中创建的xml文件中,并格式化。
# 3.oev查看edits文件
# 基本语法
hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径
# 案例实操
hdfs oev -p XML -i
edits_0000000000000000012-0000000000000000013 -o /opt/install/hadoop/edits.xml
cat /opt/install/hadoop/edits.xml
将显示的xml文件内容拷贝到eclipse中创建的xml文件中,并格式化。
3.checkpoint时间设置
(1)通常情况下,SecondaryNameNode每隔一小时执行一次。
(2)一分钟检查一次操作次数,当操作次数达到1百万时,SecondaryNameNode执行一次。
MapReduce入门
MapReduce概念
Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于hadoop的数据分析应用”的核心框架;
Mapreduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个hadoop集群上。
MapReduce优缺点
优点
MapReduce 易于编程:它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的 PC 机器运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。 就是因为这个特点使得 MapReduce 编程变得非常流行。
良好的扩展性:当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
高容错性:MapReduce 设计的初衷就是使程序能够部署在廉价的 PC 机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上面上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由 Hadoop 内部完成的。
高吞吐量:适合 PB 级以上海量数据的离线处理。这里加红字体离线处理,说明它适合离线处理而不适合在线处理。比如像毫秒级别的返回一个结果,MapReduce 很难做到。
缺点
不支持实时计算。MapReduce 无法像 Mysql 一样,在毫秒或者秒级内返回结果。
不支持流式计算。流式计算的输入数据时动态的,而 MapReduce 的输入数据集是静态的,不能动态变化。这是因为 MapReduce 自身的设计特点决定了数据源必须是静态的。
不支持DAG(有向图)计算。多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce 并不是不能做,而是使用后,每个MapReduce 作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。
MapReduce核心思想
1.分布式的运算程序往往需要分成至少2个阶段。
2.第一个阶段的maptask并发实例,完全并行运行,互不相干。
3.第二个阶段的reduce task并发实例互不相干,但是他们的数据依赖于上一个阶段的所有maptask并发实例的输出。
4.MapReduce编程模型只能包含一个map阶段和一个reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个mapreduce程序,串行运行。
MapReduce进程
一个完整的mapreduce程序在分布式运行时有三类实例进程:
1.MrAppMaster:负责整个程序的过程调度及状态协调。
2.MapTask:负责map阶段的整个数据处理流程。
3.ReduceTask:负责reduce阶段的整个数据处理流程。
MapReduce编程规范
用户编写的程序分成三个部分:Mapper,Reducer,Driver
1.Mapper阶段
(1)用户自定义的Mapper要继承自己的父类
(2)Mapper的输入数据是KV对的形式(KV的类型可自定义)
(3)Mapper中的业务逻辑写在map()方法中
(4)Mapper的输出数据是KV对的形式(KV的类型可自定义)
(5)map()方法(maptask进程)对每一个调用一次
2.Reducer阶段
(1)用户自定义的Reducer要继承自己的父类
(2)Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
(3)Reducer的业务逻辑写在reduce()方法中
(4)Reducetask进程对每一组相同k的组调用一次reduce()方法
3.Driver阶段
整个程序需要一个Drvier(包含main函数)来进行提交,提交的是一个描述了各种必要信息的job对象,将Mapper和Reducer整合
MapReduce程序运行流程分析
1.在MapReduce程序读取文件的输入目录上存放相应的文件。
2.客户端程序在submit()方法执行前,获取待处理的数据信息,然后根据集群中参数的配置形成一个任务分配规划。
3.客户端提交job.split、jar包、job.xml等文件给yarn,yarn中的resourcemanager启动MRAppMaster。
4.MRAppMaster启动后根据本次job的描述信息,计算出需要的maptask实例数量,然后向集群申请机器启动相应数量的maptask进程。
5.maptask利用客户指定的inputformat来读取数据,形成输入KV对。
6.maptask将输入KV对传递给客户定义的map()方法,做逻辑运算
7.map()运算完毕后将KV对收集到maptask缓存。
8.maptask缓存中的KV对按照K分区排序后不断写到磁盘文件
9.MRAppMaster监控到所有maptask进程任务完成之后,会根据客户指定的参数启动相应数量的reducetask进程,并告知reducetask进程要处理的数据分区。
10.Reducetask进程启动之后,根据MRAppMaster告知的待处理数据所在位置,从若干台maptask运行所在机器上获取到若干个maptask输出结果文件,并在本地进行重新归并排序,然后按照相同key的KV为一个组,调用客户定义的reduce()方法进行逻辑运算。
11.Reducetask运算完毕后,调用客户指定的outputformat将结果数据输出到外部存储。
Hadoop序列化
什么是序列化?
序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储(持久化)和网络传输。
反序列化就是将收到字节序列(或其他数据传输协议)或者是硬盘的持久化数据,转换成内存中的对象。
常用Hadoop数据序列化类型
常用数据序列化类型.png
Java类型
boolean
byte
int
float
long
double
string
map
array
Hadoop类型
BooleanWritable
ByteWritable
IntWritable
FloatWritable
LongWritable
DoubleWritable
Text
MapWritable
ArrayWritable
自定义bean对象实现序列化接口(Writable)
(1)必须实现Writable接口
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造
(3)重写序列化方法
(4)重写反序列化方法
(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写toString(),且用”\t”分开,方便后续用
(7)如果需要将自定义的bean放在key中传输,则还需要实现comparable接口,因为mapreduce框中的shuffle过程一定会对key进行排序
MapReduce框架原理
MapReduce工作流程
流程详解
1.split阶段
首先mapreduce会根据要运行的大文件来进行split,每个输入分片(input split)针对一个map任务,输入分片(InputSplit)存储的并非数据本身,而是一个分片长度和一个记录数据位置的数组。输入分片(InputSplit)通常和HDFS的block(块)关系很密切,假如我们设定HDFS的块的大小是128MB,我们运行的大文件是128x10MB,MapReduce会分为10个MapTask,每个MapTask都尽可能运行在block(块)所在的DataNode上,体现了移动计算不移动数据的思想。
2.map阶段
map阶段就是执行自己编写的Mapper类中的map函数,Map过程开始处理,MapTask会接受输入分片,通过不断的调用map()方法对数据进行处理。处理完毕后,转换为新的键值对输出。
3.Shuffle阶段(面试重点)
shuffle阶段主要负责将map端生成的数据传递给reduce端,因此shuffle分为在map端的过程和在reduce端的执行过程。具体过程如下:
(1)MapTask收集map()方法的输出对,放到内存缓冲区(称为环形缓冲区)中,其中环形缓冲区的大小默认是100MB。
(2)环形缓冲区到达一定阈值(环形缓冲区大小的80%)时,会将缓冲区中的数据溢出本地磁盘文件,这个过程中可能会溢出多个文件。
(3)多个溢出文件会被合并成大的溢出文件。
(4)在溢出过程中,及合并的过程中,都要调用Partitioner进行分区和针对key进行排序sort。
(5)合并成大文件后,shuffle的过程也就结束了,后面进入reducetask的逻辑运算过程。
4.Reduce阶段
Reduce对Shffule阶段传来的数据进行最后的整理合并。Reduce根据自己的分区号,去各个maptask机器上取相应的结果分区数据。ReduceTask会取到同一个分区的来自不同MapTask的结果文件,ReduceTask会将这些文件再进行合并(归并排序),从文件中取出一个一个的键值对group,调用用户自定义的reduce()方法,生成最终的输出文件。
5.注意
Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。
缓冲区的大小可以通过参数调整,参数:io.sort.mb 默认100M。
InputFormat数据输入
InputFormat接口实现类
FileInputFormat
DBInputFormat
…
FileInputFormat切片机制
FileInputFormat源码解析(input.getSplits(job))
1.找到你数据存储的目录。
2.开始遍历处理(规划切片)目录下的每一个文件
3.遍历第一个文件hello.txt
(1)获取文件大小fs.sizeOf(hello.txt);
(2)计算切片大小computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M
(3)默认情况下,切片大小=blocksize
(4)开始切片,形成第1个切片:ss.txt—0:128M 第2个切片ss.txt—128:256M 第3个切片hello.txt—256M:300M(每次切片时,都要判断切完剩下的部分是否大于块的1.1倍,不大于1.1倍就划分一块切片)
(5)将切片信息写到一个切片规划文件中
(6)整个切片的核心过程在getSplit()方法中完成。
(7)数据切片只是在逻辑上对输入数据进行分片,并不会再磁盘上将其切分成分片进行存储。InputSplit只记录了分片的元数据信息,比如起始位置、长度以及所在的节点列表等。
注意:block是HDFS上物理上存储的存储的数据,切片是对数据逻辑上的划分。
4.提交切片规划文件到yarn上,yarn上的MrAppMaster就可以根据切片规划文件计算开启maptask个数。
FileInputFormat中默认的切片机制:
(1)简单地按照文件的内容长度进行切片
(2)切片大小,默认等于block大小
(3)切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
获取切片信息API
FileSplit inputSplit = (FileSplit) context.getInputSplit();
MapTask工作机制
并行度决定机制
1.问题引出
maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度。那么,mapTask并行任务是否越多越好呢?
2.MapTask并行度决定机制
一个job的map阶段MapTask并行度(个数),由客户端提交job时的切片个数决定。
MapTask工作机制
(1)Read阶段:Map Task通过RecordReader,从输入InputSplit中解析出一个个key/value。
(2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。
(3)Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中。
(4)Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。
溢写阶段详情:
步骤1:利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号partition进行排序,然后按照key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序。
步骤2:按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示当前溢写次数)中。如果用户设置了Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。
步骤3:将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过1MB,则将内存索引写到文件output/spillN.out.index中。
(5)Combine阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。当所有数据处理完后,MapTask会将所有临时文件合并成一个大文件,并保存到文件output/file.out中,同时生成相应的索引文件output/file.out.index。在进行文件合并过程中,MapTask以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合并io.sort.factor(默认100)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。让每个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。
Shuffle机制
Shuffle机制
Mapreduce确保每个reducer的输入都是按键排序的。系统执行排序的过程(即将map输出作为输入传给reducer)称为Shuffle。
Shuffle结构图
Partition分区
1.默认partition分区
public class HashPartitioner<K, V> extends Partitioner<K, V> {
/** Use {@link Object#hashCode()} to partition. */
public int getPartition(K key, V value, int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
}
默认分区是根据key的hashCode对reduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区。
2.自定义Partitioner步骤
(1)自定义类继承Partitioner,重写getPartition()方法
public class ProvincePartitioner extends Partitioner<Text, FlowBean> {
@Override
public int getPartition(Text key, FlowBean value, int numPartitions) {
// 1 获取电话号码的前三位
String preNum = key.toString().substring(0, 3);
int partition = 4;
// 2 判断是哪个省
if ("136".equals(preNum)) {
partition = 0;
}else if ("137".equals(preNum)) {
partition = 1;
}else if ("138".equals(preNum)) {
partition = 2;
}else if ("139".equals(preNum)) {
partition = 3;
}
return partition;
}
}
(2)在job驱动中,设置自定义partitioner:
job.setPartitionerClass(CustomPartitioner.class);
(3)自定义partition后,要根据自定义partitioner的逻辑设置相应数量的reduce task
job.setNumReduceTasks(5);
3.注意:
如果reduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx;
如果1
例如:假设自定义分区数为5,则
(1)job.setNumReduceTasks(1);会正常运行,只不过会产生一个输出文件
(2)job.setNumReduceTasks(2);会报错
(3)job.setNumReduceTasks(6);大于5,程序会正常运行,会产生空文件
WritableComparable排序
排序是MapReduce框架中最重要的操作之一。Map Task和Reduce Task均会对数据(按照key)进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。
对于Map Task,它会将处理的结果暂时放到一个缓冲区中,当缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次排序,并将这些有序数据写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行一次合并,以将这些文件合并成一个大的有序文件。
对于Reduce Task,它从每个Map Task上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则放到磁盘上,否则放到内存中。如果磁盘上文件数目达到一定阈值,则进行一次合并以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据写到磁盘上。当所有数据拷贝完毕后,Reduce Task统一对内存和磁盘上的所有数据进行一次合并。
GroupingComparator分组(辅助排序)
对reduce阶段的数据根据某一个或几个字段进行分组。
Combiner合并
1.combiner是MR程序中Mapper和Reducer之外的一种组件
2.combiner组件的父类就是Reducer
3.combiner和reducer的区别在于运行的位置:
Combiner是在每一个maptask所在的节点运行
Reducer是接收全局所有Mapper的输出结果;
4.combiner的意义就是对每一个maptask的输出进行局部汇总,以减小网络传输量
5.combiner能够应用的前提是不能影响最终的业务逻辑,而且,combiner的输出kv应该跟reducer的输入kv类型要对应起来
Mapper
3 5 7 ->(3+5+7)/3=5
2 6 ->(2+6)/2=4
Reducer
(3+5+7+2+6)/5=23/5 不等于 (5+4)/2=9/2
6.自定义Combiner实现步骤:
(1)自定义一个combiner继承Reducer,重写reduce方法
public class WordcountCombiner extends Reducer<Text, IntWritable, Text, IntWritable>{
@Override
protected void reduce(Text key, Iterable<IntWritable> values,
Context context) throws IOException, InterruptedException {
int count = 0;
for(IntWritable v :values){
count = v.get();
}
context.write(key, new IntWritable(count));
}
}
(2)在job驱动类中设置:
job.setCombinerClass(WordcountCombiner.class);
ReduceTask工作机制
1.设置ReduceTask
reducetask的并行度同样影响整个job的执行并发度和执行效率,但与maptask的并发数由切片数决定不同,Reducetask数量的决定是可以直接手动设置:
//默认值是1,手动设置为4
job.setNumReduceTasks(4);
2.注意
(1)reducetask=0,表示没有reduce阶段,输出文件个数和map个数一致。
(2)reducetask默认值就是1,所以输出文件个数为一个。
(3)如果数据分布不均匀,就有可能在reduce阶段产生数据倾斜
(4)reducetask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个reducetask。
(5)具体多少个reducetask,需要根据集群性能而定。
(6)如果分区数不是1,但是reducetask为1,是否执行分区过程。答案是:不执行分区过程。因为在maptask的源码中,执行分区的前提是先判断reduceNum个数是否大于1。不大于1肯定不执行。
3.ReduceTask工作机制
(1)Copy阶段:ReduceTask从各个MapTask上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
(2)Merge阶段:在远程拷贝数据的同时,ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
(3)Sort阶段:按照MapReduce语义,用户编写reduce()函数输入数据是按key进行聚集的一组数据。为了将key相同的数据聚在一起,Hadoop采用了基于排序的策略。由于各个MapTask已经实现对自己的处理结果进行了局部排序,因此,ReduceTask只需对所有数据进行一次归并排序即可。
(4)Reduce阶段:reduce()函数将计算结果写到HDFS上。
OutputFormat数据输出
OutputFormat接口实现类
OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了 OutputFormat接口。
文本输出TextOutputFormat
默认的输出格式是TextOutputFormat,它把每条记录写为文本行。它的键和值可以是任意类型,因为TextOutputFormat调用toString()方法把它们转换为字符串。
Join多种应用
Reduce join
原理:
Map端的主要工作:为来自不同表(文件)的key/value对打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出。
reduce端的主要工作:在reduce端以连接字段作为key的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在map阶段已经打标志)分开,最后进行合并就ok了
该方法的缺点
这里主要分析一下reduce join的一些不足。之所以会存在reduce join这种方式,是因为整体数据被分割了,每个map task只处理一部分数据而不能够获取到所有需要的join字段,因此我们可以充分利用mapreduce框架的特性,让他按照join key进行分区,将所有join key相同的记录集中起来进行处理,所以reduce join这种方式就出现了。
这种方式的缺点很明显就是会造成map和reduce端也就是shuffle阶段出现大量的数据传输,效率很低。
Map join
使用场景:一张表十分小、一张表很大。
使用方法:
在提交作业的时候先将小表文件放到该作业的DistributedCache中,然后从DistributeCache中取出该小表进行join (比如放到Hash Map等等容器中)。然后扫描大表,看大表中的每条记录的join key/value值是否能够在内存中找到相同join key的记录,如果有则直接输出结果。
Distributedcache分布式缓存
数据倾斜原因
如果是多张表的操作都是在reduce阶段完成,reduce端的处理压力太大,map节点的运算负载则很低,资源利用率不高,且在reduce阶段极易产生数据倾斜。
解决方案
在map端缓存多张表,提前处理业务逻辑,这样增加map端业务,减少reduce端数据的压力,尽可能的减少数据倾斜。
具体办法:采用distributedcache
(1)在mapper的setup阶段,将文件读取到缓存集合中
(2)在驱动函数中加载缓存。
job.addCacheFile(new URI(“file:/d:/mapjoincache/hello.txt”));// 缓存普通文件到task运行节点
MapReduce开发总结
在编写mapreduce程序时,需要考虑的几个方面:
1.输入数据接口:InputFormat
默认使用的实现类是:TextInputFormat
TextInputFormat的功能逻辑是:一次读一行文本,然后将该行的起始偏移量作为key,行内容作为value返回
CombineTextInputFormat可以把多个小文件合并成一个切片处理,提高处理效率。
用户还可以自定义InputFormat。
2.逻辑处理接口:Mapper
用户根据业务需求实现其中三个方法:map() setup() cleanup ()
3.Partitioner分区
有默认实现 HashPartitioner,逻辑是根据key的哈希值和numReduces来返回一个分区号; key.hashCode()&Integer.MAXVALUE % numReduces
如果业务上有特别的需求,可以自定义分区。
4.Comparable排序
当我们用自定义的对象作为key来输出时,就必须要实现WritableComparable接口,重写其中的compareTo()方法。
部分排序:对最终输出的没一个文件进行内部排序。
全排序:对所有数据进行排序,通常只有一个Reduce。
二次排序:排序的条件有两个。
5.Combiner合并
Combiner合并可以提高程序执行效率,减少io传输。但是使用时必须不能影响原有的业务处理结果。
6.reduce端分组:Groupingcomparator
reduceTask拿到输入数据(一个partition的所有数据)后,首先需要对数据进行分组,其分组的默认原则是key相同,然后对每一组kv数据调用一次reduce()方法,并且将这一组kv中的第一个kv的key作为参数传给reduce的key,将这一组数据的value的迭代器传给reduce()的values参数。
YARN架构
角色:
ResourceManager:组成Resource Scheduler和Application Manager:处理客户端请求、监控NodeManager、启动和监控ApplicationMaste,进行必要的重启、整个系统的资源分配和调度
NodeManager:本节点上的资源管理和任务管理、定时向ResourceManager汇报本节点上的资源使用情况和各个Container的运行情况、接收和处理来自ResourceManager的Container启动和停止的各种命令、处理来自ApplicationMaster的指令,比如启动MapTask和ReduceTask指令
ApplicationMaster:每个应用程序对应一个ApplicationMaster,负责单个应用程序的管理、负责数据切分、为应用程序向ResourceManager申请资源(Container),并分配内部任务(MapTask和ReduceTask)、与NodeManager通信来启动/停止任务,Task都是运行在Container中的、负责任务的监控和容错,当某些Task运行出错,进行容错处理
Container:Container是YARN中的资源抽象,封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等、Container类似于一个虚拟机,可以在上面执行任务
YARN执行流程
1.作业提交
(1)client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。
(2)client向ResourceManager申请一个作业Id。
(3)ResourceManager给Client返回该job资源的提交路径(HDFS路径)和作业Id,每一个作业都有一个唯一的Id。
(4)Client发送jar包、切片信息和配置文件到指定的资源提交路径。
(5)Client提交完资源后,向ResourceManager申请运行MrAppMaster(针对该job的ApplicationMaster)。
2.作业初始化
(6)当ResourceManager收到Client的请求后,将该job添加到容量调度器(Resouce Scheduler)中。
(7)某一个空闲的NodeManager领取到该job。
(8)该NodeManager创建Container,并产生MrAppMaster。
(9)下载Client提交的资源到本地,根据分片信息生成MapTask和ReduceTask。
3.任务分配
(10)MrAppMaster向ResouceManager申请运行多个MapTask任务资源。
(11)ResourceManager将运行MapTask任务分配给空闲的多个NodeManager,NodeManager分别领取任务并创建容器(Container)。
4.任务运行
(12)MrAppMaster向两个接收到任务的NodeManager发送程序启动脚本,每个接收到任务的NodeManager启动MapTask,MapTask对数据进行处理,并分区排序。
(13)MrAppMaster等待所有MapTask运行完毕后,向ResourceManager申请容器(Container),运行ReduceTask。
(14)程序运行完毕后,MrAppMaster会向ResourceManager申请注销自己。
(15)进度和状态更新
YARN中的任务将其进度和状态(包括counter)返回给应用管理器, 客户端每秒(通过mapreduce.client.progressmonitor.pollinterval设置)向应用管理器请求进度更新, 展示给用户。可以使用YARN WebUI查看任务执行状态。
5.作业完成
除了向应用管理器请求作业进度外, 客户端每5分钟都会通过调用waitForCompletion()来检查作业是否完成。时间间隔可以通过mapreduce.client.completion.pollinterval来设置。作业完成之后, 应用管理器和container会清理工作状态。作业的信息会被作业历史服务器存储以备之后用户核查。
YARN资源调度器
FIFO:先进先出调度器
Capacity Scheduler:容量调度器
Fair Scheduler:公平调度器
YARN常用命令
# 提交任务
hadoop jar
# 查看正在运行的任务
yarn application -list
# 杀掉正在运行的任务
yarn application -kill 任务id
# 查看节点列表
yarn node -list
# 查看节点状态
yarn node -status 节点ID
namenode被格式化之后,将在/opt/install/hadoop/data/tmp/dfs/name/current目录中产生如下文件
fsimage_0000000000000000000
fsimage_0000000000000000000.md5
seen_txid
VERSION
(1)Fsimage文件:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件idnode的序列化信息。
(2)Edits文件:存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到edits文件中。
(3)seen_txid文件保存的是一个数字,就是最后一个edits_的数字
(4)每次Namenode启动的时候都会将fsimage文件读入内存,并从00001开始到seen_txid中记录的数字依次执行每个edits里面的更新操作,保证内存中的元数据信息是最新的、同步的,可以看成Namenode启动的时候就将fsimage和edits文件进行了合并