day01—————
一个完整的SQL语句
select distinct...from...[join]...[where...][group by...][having...][order by....][limit..]
[union all]
SQL子句的运行顺序:
(1) from leftTable
(2) on 条件
(3) [left|inner|right] join rightTable
(4) where 子句
(5) group by 子句
(6) having 子句
(7) select 子句
(8) distinct
(9) order by子句
(10) limit子句
(11) union[all]
一、Mapreduce的基础
1.1 为什么要学习Mapreduce
--1. 大数据集的传输问题: 网络带宽稀缺,时间长
--2. 单机的硬件受限:cpu少的可怜,内存受限,磁盘受限....
--3. 由于上述两个原因导致计算和分析时间过长
--4. 由于单机的受限问题,开发人员想到了使用集群来计算和分析,但是程序的编写是特别复杂的
由此Mapreduce计算分析框架出现了,可以让开发人员只需关注业务逻辑的编写,无需考虑怎么在集群上运行。提供了移动计算而非移动数据,并发运行的核心思想
1.2 Mapreduce的简介
1.2.1 简介
--1. 是Hadoop的核心模块之一
--2. 解决分布式文件系统上的大数据集如何快速的计算和分析的问题
--3. 是一个运行在hdfs上的一个计算和分析框架
1.2.2 优缺点
-- 优点
(1) mapreduce易于编程(只需要程序员考虑业务逻辑)
(2) 良好的扩展性(hdfs具有良好的扩展性)
(3) 高容错性(正在执行job的datanode宕机,mr可以继续寻找另外一个具有此副本的datanode继续运行)
(4) 适合PB级别的数据的离线分析(如何是特别小的文件,处理时间相对而言,是长的)
--缺点
(1) 不合适做低延迟(实时,准实时)的工作
(2) 不适合做流式数据计算(流式数据:指的是实时产生的数据)
(3) 不适合DAG(有向图)计算
1.3 Mapreduce的核心思想(重点)
--1. 提出了"计算向数据靠拢,而不是数据向计算靠拢"。程序员在开发时,只需要将编写的业务逻辑和mapreduce默认的组件够成一个完整的mr程序,移动到有要计算的数据的节点上
--2. 计算模型分两个运算阶段
(1)map阶段(并发运行,互不干扰,分别统计,统计结果临时保存到本地磁盘上)
(2)reduce阶段(并发运行,互不干扰,汇总统计)
1.4 Mapreduce的阶段介绍(重点)
Mapreduce在计算分析数据时,整体上是分两个阶段的,一个是Map阶段,一个是reduce阶段
1.4.1 Map阶段
--1. 阶段简介
整个map阶段,通常会有N个maptask,并行运算,互不干扰,各自统计各自的数据,这个数据通常是一个块文件(原始数据)。统计结果会临时保存到本地磁盘上
wordcount.jar------>qianfeng01----blk1825----(a:1 b:2....)---保存到mei01的磁盘上
wordcount.jar------>qianfeng02----blk1826----(a:8 h:2....)---保存到mei02的磁盘上
wordcount.jar------>qianfeng03----blk1827----(b:8 w:2....)---保存到mei03的磁盘上
--2. 数据扭转
原始数据
|
| 经过框架底层解析,一行对应一个kv键值对,K1是行偏移量,V1是行记录
|
<K1,V1>: K1V1的对数默认有行数决定,每一对K1V1调用一次mapreduce
|
|
|
map函数: 开发人员编写业务逻辑
|
| 根据开发人员编写的业务逻辑输出K2,V2
|
<K2,V2>
1.4.2 Reduce阶段
--1. 阶段介绍
reduce阶段,也会有N个ReduceTask,也是并发执行,互不干扰,处理的数据是要fetch Map阶段产生的临时数据,然后把这些临时数据进行汇总,汇总结果通常保存到HDFS上(也可以保存到本地磁盘)。
--2. 数据扭转
将map阶段产生的属于自己要处理的<K2,V2>fetch到自己运行的节点上,并分组,变成了
<K2,list<V2>>: 一个K2就表示是一组,一组调用一次reduce函数
|
|
|
reduce函数: 开发人员编写业务逻辑
|
| 根据开发人员编写的业务逻辑输出K3,V3
|
<K3,V3>
1.5 Mapreduce的编程模型
#1. map阶段的编程模型:
第一步: 自定义一个mapper类型,继承Mapper类
第二步: 指定K1V1和K2V2的泛型
第三步: 重写map函数
#2. reduce阶段的编程模型
第一步: 自定义一个reducer类型,继承Reducer类
第二步:指定K2V2和K3V3的泛型
第三步:reduce函数
#3. 驱动类型的编写模型(获取job对象,进行初始化job的各种信息)
-- 获取job对象
-- 设置驱动类型
-- 设置Mapper类型和Reduce类型
-- 设置K3V3的类型(如果K2和K3,V2和V3相同,那么就不需要单独设置K2,V3,如果谁不同就需要设置谁)
-- 设置reduceTask的个数(默认是1个)
-- 设置输入输出路径
-- 执行提交
1.6 Mapreduce的入门案例演示(重点,绘图)
1)大数据的入门案例是wordcount,也就是单词频率统计,其他的编程语言的入门案例通常都是打印helloworld
需求:统计N个文件中每个单词出现的所有次数,要汇总到一起。
数据如下:
a.txt
hello mei hello 1999 hello beijing hello
world hello java good
b.txt
hello hadoop hello java hello storm hello
spark zhang redis wang zookeeper
hive hello hbase hello flume
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zrhPtIC6-1615385545973)(ClassNotes.assets/wordcount%E7%9A%84mr%E6%B5%81%E7%A8%8B.jpg)]
2)程序的编写
参考代码
3)程序的测试:
--1. 使用maven里的package打包成jar文件
--2. 将jar文件上传到linux上
--3. 使用hadoop jar来运行文件中的wordcount程序(原理:移动计算而非移动数据)
hadoop jar [the path of jar file] [driver class] [inputPath] [outputPath]
1.7 IDE运行Mapreduce的几种模式(熟悉)
1.7.1 说明
集成开发工具(IDE)运行mapreduce的模式有三种,分别是在本地运行计算本地的文件,在本地运行计算HDFS上的文件,将计算程序远程提交到HDFS上计算HDFS上的文件。
1.7.2 本地运行计算本地的文件
本地指的是IDE所在的操作系统环境,计算的是本地文件系统中的文件。
原理如下:
- Configuration对象会读取四个默认的配置文件。
- fs.defaultFS的值为file:///,获取的是本地文件系统对象。
- mapreduce.framework.name的值为local
所以,在Driver中的输入路径和输出路径指定为本地的路径即可
1.7.3 本地运行计算HDFS上的文件
本地指的是IDE所在的操作系统环境,计算的是HDFS中的文件。
原理如下:
- 既然想要获取HDFS上的文件,那么fs.defaultFS的值为hdfs://mei01:8020,也就是要获取分布式文件系统对象
- 是访问HDFS,将对应的文件读取到本地内存进行计算,计算结果存储到HDFS上(因为还是使用mapreudce.framework.name的默认值local).
1.7.4 本地远程提交计算程序到HDFS上,计算HDFS上的文件
原理:
- 移动计算而非移动数据
- 使用的是HDFS集群的设置,mapreduce.framework.name的值是yarn
- IDE需要有权限访问HDFS,IDE需要设置跨平台操作
conf.set("mapreduce.app-submission.cross-platform", "true");
System.setProperty("HADOOP_USER_NAME", "root");
步骤:
1. 将计算程序打包成jar文件,放入到classpath下: add as Library
2. 将集群的mapred-site.xml和yarn-site.xml文件导入到项目的resouces目录(注意重启的问题)
3. 如果报: 无任务控制....等字样,说明没有设置跨平台属性
4. 程序中的路径要使用hdfs上的路径。
1.8 Partitioner组件的应用(重点)
1.8.1 reduceTask数量的研究
--1. 默认情况下是一个reduceTask
--2. 可以使用函数job.setNumReduceTasks(...)自定义个数,reduceTask的数量决定了文件的数量。
--3. reduceTask的数量为0,表明没有reduce阶段,只有map阶段,产生的文件名part-m-xxxxx,文件里的数据是不排序的,从而证明没有使用环形缓冲区。注意,reduce阶段产生的文件名part-r-xxxxx
1.8.2 分区器的简介与自定义
--1. 默认使用的是HashPartitioner分区器,分区器决定了分区的个数
默认逻辑:就是调用K2的hash值与reduceTask的数量做取模预算
--2. 程序员可以自定义分区器
只需要implements Partitioner<K2, V2> ,重写getPartition方法
注意:
-- 使用自定义分区器后,reduceTask的个数不能小于分区的数量,可以大于等于。但是大于毫无意义。
-- 得出结论,reduceTask的数量由分区数决定,分区数是由开发人员决定的
day02—————
一、Hadoop序列化机制
1.1 序列化的概念和应用领域
-- 序列化:
将内存中的对象转成二进制的字节序列形式
-- 反序列化:
讲二进制形式的字节序列转成内存中的对象
-- 应用领域
(1) 持久化到磁盘上保存
(2) 网络传输
1.2 Hadoop的常用类型
常用的数据类型对应的hadoop数据序列化类型
Java类型 | Hadoop Writable类型 | 释义 |
---|---|---|
boolean | BooleanWritable | 标准布尔型数值 |
byte | ByteWritable | 单字节数值 |
int | IntWritable | 整型数值 |
float | FloatWritable | 单精度数 |
long | LongWritable | 长整型数值 |
double | DoubleWritable | 双精度数 |
string | Text | 使用UTF8格式存储的文本 |
map | MapWritable | 以键值对的形式存储Writable类型的数据 |
array | ArrayWritable | 以数组的形式存储Writable类型的数据 |
null | NullWritable | 当<key,value>中的key或value为空时使用 |
NullWritable说明:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DCrFrWrz-1615385545975)(…/…/…/…/…/The%2520teaching%2520material/Teaching%2520notes/Phase2-04-Mapreduce/update2/mapreduce.assets/6-1587487481593.png)]
1.3 序列化接口和排序接口(重点理解)
1.3.1 序列化接口:Writable
--1. 是hadoop的序列化机制的最顶端的接口
--2. 提供了两个抽象方法
(1) write(DataOutput out) : 序列化方法,将对象的字段值序列化到out中
(2) readFields(DataInput in): 反序列化方法,从in中读取值赋值给这个对象
注意:如果要自定义hadoop类型,一定要直接或间接的实现此接口(mapreduce里的k1,v1,k2,v2,k3,v3都必须是hadoop类型)
1.3.2 排序接口:WritableComparable
-- 源码如下:
public interface WritableComparable<T> extends Writable, Comparable<T> {
}
-- 此接口继承了Hadoop的序列化接口,同时继承了java的比较接口
强调:在整个mr会涉及到大量的排序,排序默认使用的都是key(尤其是shuffle使用k2进行排序,算法是QuickSort,规则是字典排序),因此key都要实现WritableComparable
1.4 如何自定义Hadoop类型
--1. 如果自定义类型,不涉及到排序,那么只需要实现序列化接口Writable
--2. 自定义类型通常情况下可以直接实现Writable和Comparable接口
--3. 自定义类型也可以通过实现WritableComparable来间接的实现Writable和Comparable接口(推荐这种方式)
案例演示:定义一个hadoop类型来描述一个学生类型
参考代码中的com.qf.mr.writable.StudentWritable类型
1.5 Hadoop与java序列化机制的比较
##1. java序列化机制不适合Hadoop框架来直接使用
Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系......),这些数据不是我们需要的,也不便于在网络中高效传输.
所以Hadoop框架提供了自己的一套序列化机制:Writable
##2. Hadoop底层的需求:数据的序列化要快,体积要小,占用带宽要小。
##3. hadoop序列化机制的特点:
1)紧凑:紧凑的格式能让我们充分利用网络带宽,而带宽是数据中心最稀缺的资;
2)快速:进程通信形成了分布式系统的骨架,所以需要尽量减少序列化和反序列化的性能开销,这是基本的;
3)可扩展:协议为了满足新的需求变化,所以控制客户端和服务器过程中,需要直接引进相应的协议,这些是新协议,原序列化方式能支持新的协议报文;
4)互操作:能支持不同语言写的客户端和服务端进行交互;
1.6 案例之手机流量的统计(熟悉)
1)需求:
对于记录用户手机信息的文件,得出统计每一个用户(手机号)所耗费的总上行流量、下行流量,总流量结果
2)需求分析:
1. 实现自定义的bean来封装流量信息,使用手机号码作为Key,Bean作为value。这个Bean的传输需要实现可序列化,因此我们需要实现MapReduce的序列化接口Writable,重写相关方法。
2. 计算上行流量、下行流量、计费流量
3. <k1,v1>的分析:取一行TextInputFormat类去读取,offset做key,一行数据做value,
offset:phoneNum,upflow,downflow
4. 拆分,取出倒数第二倒数第三段,map端读取数据然后输出
3)代码:
参考代码:com.qf.mr.phoneflow.FlowBeanDriver
二、MapReduce的进阶
2.1 Mapreduce运行流程概述(熟悉)
##1. 整个mr运行过程中有三大类进程:
(1)MRAPPMaster: 负责整个作业的资源调度和状态协调。每一个mr程序在启动时都会启动一个MRAPPMaster
(2)MapTask:负责Map阶段的数据处理流程(是原始块文件的处理)
(3)ReduceTask:负责Reduce阶段的数据处理流程(是Map阶段产生的临时数据)
##2 整体流程如下:
(1)mr作业提交时,会为这个作业启动一个AppMaster进程,主类就是MRAPPMaster。
(2)启动后会根据job上的描述信息(输入规则和输入路径)计算inputSplit(输入分片),从而决定MapTask的数量
(3)AppMaster进程向resourcemanager为MapTask申请资源container(cpu,内存,磁盘)
(4)MapTask运行时:
-- 根据输入规则(FileInputFormat)和记录阅读器(RecordReader)以及分片信息读取块文件,形成K1V1
-- K1V1作为map函数的输入数据,进入map函数,经过逻辑处理,形成K2V2
-- K2V2被写入到环形缓冲区,当达到阈值时,会进行溢写(Flush)成磁盘上文件,溢写前分区和排序。
(5)AppMaster在监控MapTask运行5%时,会为ReduceTask申请资源,会根据用户指定的参数来启动相应的reduceTask进程,并告知reduceTask需要处理的数据范围
(6)ReduceTask运行时:
reduceTask会根据Appmaster告知自己的待处理的数据范围,从MapTask产生的临时数据中拉取属于自己的数据,并进行"归并排序",然后相同的key化为一组<K2,<ListV2>>,每一组调用一次reduce函数,经过逻辑处理后,形成K3V3,根据输出规则将数据写出到目的地。
2.2 运行流程之分片机制(重点)
2.2.1 输入分片的概念
#1. MapReduce在进行作业提交时,会预先对将要分析的原始数据进行划分处理,形成一个个等长的逻辑数据对象,称之为输入分片(inputSplit),简称“分片”。
#2. MapReduce为每一个分片构建一个单独的MapTask,并由该任务来运行用户自定义的map方法,从而处理分片中的每一条记录。
#3. 源码:
public class FileSplit extends InputSplit implements Writable {
private Path file; //要处理的文件名
private long start; //当前逻辑分片的偏移量
private long length; //当前逻辑分片的字节长度
private String[] hosts; //当前逻辑分片对应的块数据所在的主机名
private SplitLocationInfo[] hostInfos;
public FileSplit() {}
public FileSplit(Path file, long start, long length, String[] hosts) {
this.file = file; //创建逻辑分片对象时调用的构造器
this.start = start;
this.length = length;
this.hosts = hosts;
}
//.....
}
#4. 源码解析:
(1) InputSplit是抽象父类型,真正使用的是FileSplit类型,此类型里封装了四个属性:
--1) Path file: 是一个文件路径
--2) long start: 相对于整个文件的字节偏移量
--3) long length: 就是FileSplit这个对象的对应的原始数据的字节数量,通常被称之为分片大小
--4) String[] hosts: 封装的是要处理的原始数据块文件的三个副本存储节点主机名
#5. 从上分析:
一旦创建分片对象,也就是使用new调用FileSplit构造器,创建对象时,要给以上属性赋值。这些数据我们称之为逻辑数据,也就是描述信息。
2.2.2 分片大小如何选择
#1. 分片数量越多,优势如下:
-- 处理分片对应的原始数据所花的时间更少,也就是小于一个分片处理整个文件的时间。
-- 每一个分片对应一个MapTask,MapTask是并行运行的,效率高
-- 分片越多,负载均衡就越好。
-- 计算机硬件越好,处理速度越快,就可以腾出时间,计算其他任务。
#2. 分片太小的缺点:
如果分片太小,管理分片的总时间和构建map任务的总时间将决定作业的整个执行时间
#3. 分片太大的缺点:
如果分片跨越两个数据块,那么分片的部分数据需要通过网络传输到map任务运行的节点,占用网络带宽,效率更低
#4 得出结论:
因此最佳分片大小应该和HDFS上的块大小一致。hadoop2.x默认128M.
2.2.3 源码解析:FileInputFormat
--1. public List<InputSplit> getSplits(JobContext job)
说明:获取要统计的文件的分片数据以及分片数量
--2. long splitSize = computeSplitSize(blockSize, minSize, maxSize);
说明:计算分片大小
--3. long bytesRemaining = length;
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
}
说明:封装分片对象,存储到集合中,有一个1.1倍的判断,当hdfs上倒数第二个块与最后一个块
的大小加起来<=1.1时,会被封装成一个分片。
560M----->hdfs是五个块, 分片有五个
512+48M
560-128 432 304 176 48
start :0
start: 134217728
start: 134217728+134217728
start: 134217728+134217728+134217728
start: 134217728+134217728+134217728+134217728
520M 512+8M 136/128<1.1
start :0
start: 134217728
start: 134217728+134217728
start: 134217728+134217728+134217728 length = 136M
2.2.4 分片机制的总结
1)分片大小参数
通过分析源码,在FileInputFormat中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize)); 切片主要由这几个值来运算决定
参数 | 默认值 | 属性 |
---|---|---|
minsize | 1 | mapreduce.input.fileinputformat.split.minsize |
maxsize | Long.MAXVALUE | mapreduce.input.fileinputformat.split.maxsize |
blocksize | 块大小 | dfs.blocksize: |
可以看出,就是取minsize、maxsize、blocksize三者的中间的那个值。
--1. 将maxsize调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值.
--2. 将minsize调得比blockSize大,则会让切片变得比blocksize还大
--3. 但是,不论怎么调参数,都不能让多个小文件"划入"一个split
2)创建过程总结
1. 获取文件大小及位置
2. 判断文件是否可以分片(压缩格式有的可以进行分片,有的不可以)
3. 获取分片的大小
4. 剩余文件的大小/分片大小>1.1时,循环执行封装分片信息的方法,具体如下:
封装一个分片信息(包含文件的路径,分片的起始偏移量,要处理的大小,分片包含的块的信息,分片中包含的块存在哪儿些机器上)
5. 剩余文件的大小/分片大小<=1.1且不等于0时,封装一个分片信息(包含文件的路径,分片的起始偏移量,要处理
的大小,分片包含的块的信息,分片中包含的块存在哪儿些机器上)
分片的注意事项:1.1倍的冗余。
3)分片细节问题总结
如果有多个分片
- 第一个分片在读取数据时,通常会读取到下一个分片的第一行(因为这个分片的最后一行的数据有一部分在下一个块中)
- 既不是第一个分片也不是最后一个分片时,第一行的内容属于上一个分片的,最后一行会跨越到下一个分片里
- 最后一个分片舍弃第一行,末尾多读一行,表名没有数据可读了。
4)分片与块的区别
看完源码就知道了
1. 分片是逻辑数据,记录的是要处理的物理块的信息而已
2. 块是物理的,是真实存储在文件系统上的原始数据文件。
2.2.5 问题答疑
比如待处理数据有两个文件:
file1.txt 260M
file2.txt 10M
经过FileInputFormat的切片机制运算后,形成的切片信息如下:
file1.txt.split1-- 0~128
file1.txt.split2-- 128~260
file2.txt.split1-- 0~10M
2.3 运行流程之MapTask(重点)
2.3.1 MapTask的整体概述
1. maptask调用FileInputFormat的createRecordReader读取分片数据
2. 每行数据读取一次,返回一个(K,V)对,K是offset,V是一行数据
3. 将k-v对交给MapTask处理
4. 每对k-v调用一次map(K,V,context)方法,然后context.write(k,v)
5. 写出的数据交给收集器OutputCollector.collector()处理
6. 将数据写入环形缓冲区,并记录写入的起始偏移量,终止偏移量,环形缓冲区默认大小100M
7. 默认写到80%的时候要溢写到磁盘,溢写磁盘的过程中数据继续写入剩余20%
8. 溢写磁盘之前要先进行分区然后分区内进行排序
9. 默认的分区规则是hashpatitioner,即key的hash%reduceNum
10. 默认的排序规则是key的字典顺序,使用的是快速排序
11. 溢写会形成多个文件,在maptask读取完一个分片数据后,会将环形缓冲区数据刷写到磁盘
12. 将数据多个溢写文件进行合并,分区内排序(外部排序===》归并排序)
2.3.2 源码解析
##1. TextInputFormat类型源码解读
--1. 是MR默认使用的输入格式,父类是FileInputFormat
--2. RecordReader<LongWritable, Text> createRecordReader()
说明: 此方法的作用是用于获取记录阅读器对象,默认是LineRecordReader
--3. boolean isSplitable(JobContext context, Path file)
说明:此方法的作用是判断此文件是否支持切分。此方法是在计算分片时调用的。
##2. LineRecordReader类型源码解读
--1. 是MR的默认的记录阅读器对象,按行读取,一行封装成一对<k1,v1>
--2. initialize(InputSplit genericSplit,TaskAttemptContext context)
说明:初始化方法的作用就是给对象里的属性(start,end,key,value,in等)赋值,从而定位到要读取的真正的原始块文件。需要注意的是start的值最终会舍弃分片的第一行,从新赋值
--3. boolean nextKeyValue()
说明:此方法的作用是真正去读取一行记录,然后将偏移量封装到K1,行记录封装到V1里。
注意:最后一个分片时,会多读一行,证明没有数据可读了。
2.3.3 MapTask的注意事项
--1. MapTask的数量不是越多越好(虽然并发执行的效率高了,极端都不是最好的)
--2. MapTask的运行时间在1分钟左右比较合适,如果运行时间在30s左右,说明MapTash处理的数据少,可以相应的调整分片大小(调整minSize 往大了调整)
--3. 机器硬件如果比较好,比如24core+64G内存,那么每个机器节点适合的MapTask的数量在20~100以内
--4. 尽量重用虚拟机的内存,可以减少机器的性能开销
--5. 集群在hdfs-site.xml里有自己的块大小配置参数,不过,在当上传一个文件时,可以临时指针对这个文件设置块大小
day03—————
二、MapReduce的进阶(续)
2.4 运行流程之ReduceTask(重点)
1. reduceTask在MapTask运行结束后,开始fetch(HTTP通信协议)属于自己要处理的分区的数据,通常会跨节点,产生网络IO,数据来自多个MapTask的临时文件
2. reducetask将来自多个maptask的数据在内存里进行合并(merge)并排序,排序算法是归并排序。
3. reduceTask按照不同的key划分为不同的组(K2,list<V2>)
4. 一组<k2,list<v2>>调用一次reduce(k,iterable<v>values,context)
5. 经过reduce函数的逻辑处理后,形成K3,V3
6. reducetask会根据FileOutputFormat组件以及输出路径将K3,V3写出到目的地,通常是HDFS.
注意事项:
--1. ReduceTask的数量,由分区器决定
--2. 分区器是有开发人员写的,因此要注意数据倾斜(某一个分区的数据特别多,那么整个作业的时间取决于此分区)问题,
--3. 怎么避免数据倾斜:
(1)可以提前对数据进行采样分析
(2)也可以对K2进行散列计算。
--4. ReduceTask的数量最好是小于等于机器节点的数量。
2.5 Shuffle流程(重中之重)
不带环形缓冲区原理的叙述:
1. 从map函数输出到reduce函数接受输入数据,这个过程称之为shuffle.
2. map函数的输出,存储环形缓冲区(默认大小100M,阈值80M)
3. 当达到阈值时,准备溢写到本地磁盘(因为是中间数据,因此没有必要存储在HDFS上)。在溢写前要对数据进行分区整理,然后进行排序(quick sort,字典排序)
4. 如果有必要,可以在排序后,溢写前调用combiner函数进行运算,来达到减少数据的目的
5. 溢写文件有可能产生多个,然后对这多个溢写文件进行再次归并排序(也要进行分区和排序)。当溢写个数>=3时,可以再次调用combiner函数来减少数据。如果溢写个数<3时,默认不会调用combiner函数。
6. 合并的最终溢写文件可以使用压缩技术来达到节省磁盘空间和减少向reduce阶段传输数据的目的。(最终溢写文件是一个,存储在本地磁盘中)
7. Reduce阶段通过HTTP协议抓取属于自己的分区的所有map的输出数据(默认线程数是5,因此可以并发抓取)。
8. 抓取到的数据存在内存中,如果数据量大,当达到本地内存的阈值时会进行溢写操作,在溢写前会进行合并和排序(排序阶段),然后写到磁盘中,
9. 溢写文件可能会产生多个,因此在进入reduce之前会再次合并(合并因子是10),最后一次合并要满足10这个因子,同时输入给reduce函数,而不是产生最终一个临时合并文件(减少一次磁盘IO)。reduce函数输出数据会直接存储在HDFS上。
带上环形缓冲区的原理叙述:
1. 从map函数输出到reduce函数接受输入数据,这个过程称之为shuffle.
2. map函数的输出,存储环形缓冲区(默认大小100M,阈值80M)
环形缓冲区:其实是一个字节数组kvbuffer. 有一个sequator标记,kv原始数据从左向右填充(顺时针),
kvmeta是对kvbuffer的一个封装,封装成了int数组,用于存储kv原始数据的对应的元数据valstart,
keystart,partition,vallen信息,从右向左(逆时针)。参考(环形缓冲区的详解一张)
3. 当达到阈值时,准备溢写到本地磁盘(因为是中间数据,因此没有必要存储在HDFS上)。在溢写前要进行对元数据分区(partition)整理,然后进行排序(quick sort,通过元数据找到出key,同一分区的所有key进行排序,排序完,元数据就已经有序了,在溢写时,按照元数据的顺序寻找原始数据进行溢写)
4. 如果有必要,可以在排序后,溢写前调用combiner函数进行运算,来达到减少数据的目的
5. 溢写文件有可能产生多个,然后对这多个溢写文件进行再次合并(也要进行分区和排序)。当溢写个数>=3时,可以再次调用combiner函数来减少数据。如果溢写个数<3时,默认不会调用combiner函数。
6. 合并的最终溢写文件可以使用压缩技术来达到节省磁盘空间和减少向reduce阶段传输数据的目的。(存储在本地磁盘中)
7. Reduce阶段通过HTTP协议抓取属于自己的分区的所有map的输出数据(默认线程数是5,因此可以并发抓取)。
8. 抓取到的数据存在内存中,如果数据量大,当达到本地内存的阈值时会进行溢写操作,在溢写前会进行合并和排序(排序阶段),然后写到磁盘中,
9. 溢写文件可能会产生多个,因此在进入reduce之前会再次合并(合并因子是10),最后一次合并要满足10这个因子,同时输入给reduce函数,而不是产生最终一个临时合并文件(减少一次磁盘IO)。reduce函数输出数据会直接存储在HDFS上。
2.5.3 环形缓冲区的扩展:
参考源码:org.apache.hadoop.mapred.MapTask.MapOutputBuffer<K, V>
100M的字节数: 104857600 kvbuffer的长度 byte数组
21264400 kvmeta的长度 int数组
2.6 Combiner函数(重点)
--1. 意义:就是在shuffle阶段,尽可能的减少磁盘IO和网络IO
--2. 运行时机:排序后溢写时
(1) 在环形缓冲区排序后溢写前
(2) 溢写文件比较多,归并排序后,形成最终一个临时文件前,注意如果溢写文件个数小于<3,就不会调用
(3) reduce阶段的归并排序如果产生溢写文件,那就是在归并排序后,溢写前
--3. 本质:其实就是运行在shuffle阶段的reduce函数,父类就是Reducer。
--4. 注意事项:使用combiner函数可以,但是不能影响最终结果
(1)类似求最大值,最小值,求和等操作时可以的
(2)求平均值,不可以,因为可能会影响结果。
--5. 定义后,使用job.setCombinerClass(xxxxxxxx.class);
--6. Combiner是一种优化组件
代码案例:
参考:
com.qf.mr.temperature.TemperatureCombiner
com.qf.mr.temperature.TemperatrueDriver3
2.7 MapReduce优化参数(了解)
参考文档:
三、YARN资源管理器
3.1 MapReduce1.x版本的简介
## 1. 在Hadoop1.x时,只有HDFS和MapReduce两个模块
## 2. MapReduce在作业运行中,有两个组件JobTracker和TaskTracker. 这两个组件负责资源的调度和监听,惠东等工作,还没有yarn框架的概念
## 3. JobTracker负责:
--1. 作业提交后的资源调度
--2. 与TaskTracker通信并为mapTask和reduceTask分配资源
--3. 监听所有作业和所有任务的执行情况
## 4. TaskTracker负责:
--1. 运行相应的mapTask或者是reduceTask
--2. 向jobTracker汇报运行状态信息。
3.2 YARN的简介与设计思想(重点)
3.2.1 YARN的简介
--1. 为了解决MapReduce1.x框架功能的繁冗(又做计算分析,又做资源管理)及其不足,团队提供了一个全新的资源管理系统框架,被称之为YARN
--2. YARN是Hadoop2.x里的核心模块之一,是一个资源管理系统,负责为计算框架分配资源,相当于分布式的操作系统
--3. 由于具有通用性,所以也可以为spark,tez等计算框架分配资源
3.2.2 YARN的设计思想
--1. 整个资源管理系统中的功能如资源分配,作业调度,作业监控都设计成独立的子进程
--2. 包含一个常住的全局资源管理器:resourcemanager
--3. 每一个计算程序对应一个applicationmaster
--4. 每一个节点都有一个常住的守护进程NodeManager
--5. 资源被称为container(cpu,内存,磁盘,网络等)
与MapReduce1.x的比较
resourcemanager + applicationmaster+timelineserver 相当于1.x里的jobtracker
nodemanager 相当于1.x里的tasktracker
container 相当于slot
3.3 YARN的配置说明
参考文档
3.4 YARN的job提交流程(重中之重)
1. 调用waitForCompletion方法每秒轮询作业的进度,内部封装了submit()方法,用于创建JobCommiter实例,
并且调用其的submitJobInternal方法。提交成功后,如果有状态改变,就会把进度报告到控制台。错误也会报告到
控制台
2. JobCommiter实例会向ResourceManager申请一个新应用ID,用于MapReduce作业ID。这期间JobCommiter也会进行检查输出路径的情况,以及计算输入分片。
3. 如果成功申请到ID,就会将运行作业所需要的资源(包括作业jar文件,配置文件和计算所得的输入分片元数据文件)上传到一个用ID命名的目录下的HDFS上。此时副本个数默认是10.
4. 准备工作已经做好,再通知ResourceManager调用submitApplication方法提交作业。
5. ResourceManager调用submitApplication方法后,会通知Yarn调度器(Scheduler),调度器分配一个容器,在节点管理器的管理下在容器中启动 application master进程。
6. application master的主类是MRAppMaster,其主要作用是初始化任务,并接受来自任务的进度和完成报告。
7. 然后从HDFS上下载资源,主要是split和计算程序。然后为每一个split创建MapTask以及参数指定的ReduceTask,任务ID在此时分配
8. 然后Application Master会向资源管理器请求容器,首先为MapTask申请容器,然后再为ReduceTask申请容器。(5%)
9. 一旦ResourceManager中的调度器(Scheduler),为Task分配了一个特定节点上的容器,Application Master就会与NodeManager进行通信来启动容器。
10. 运行任务是由YarnChild来执行的,运行任务前,先将资源本地化(jar文件,配置文件,缓存文件)
11. 然后开始运行MapTask或ReduceTask。
12. 当收到最后一个任务已经完成的通知后,application master会把作业状态设置为success。然后Job轮询时,知道成功完成,就会通知客户端,并把统计信息输出到控制台
3.5 YARN的三种调度器(熟悉)
3.5.1 FIFO调度器
--1. 调度器维护一个队列,会将所有提交的作业放入这个队列中,然后队列中的任务一一执行,前面的执行完毕,后面才开始执行
--2. 优点:简单,不需要配置
--3. 缺点:每一个作业都会占有集群上的所有资源,比如有一个小作业可能执行1分钟就结束,但是前面有一个大作业要执行半小时,所以这个小作业要等待半小时,而不是小作业一提交就执行。
3.5.2 容量(capacity)调度器
--1. 调度器维护了两个队列,其中一个队列用于存储小作业,保证小作业已提交就立即执行。
--2. 此调度器会预留集群上的一些资源给小作业,即使没有小作业,这一部分资源也是空闲的,所有大作业在提交后,即使没有小作业,也不能使用集群上的全部资源,所以运行时间会较长
--3. YARN默认使用的调度器就是容量调度器
3.5.3 公平(fair)调度器
--1. 目的是为所有的作业尽可能的公平分配集群上的资源。
--2. 当集群上只有一个作业提交时,这个作业会占用所有资源
--3. 当集群上再有作业提交时,正在运行的作业会腾出一部分资源给刚刚提交的作业,保证了所有的作业都在提交后就直接执行,没有等待状态。
day04—————
一、Hadoop的压缩机制
Hadoop中的压缩体现在三个地方
--1. 要分析计算的文件是否为压缩文件
--2. MapTask产生的临时文件是否要压缩
--3. ReduceTask产生的文件是否要压缩
1.1 要分析计算的文件为压缩文件时
--1. 如果这个压缩格式,不支持切分,那么这个文件在HDFS上即使有100个块,也只有一个InputSplit,也就是说只有一个MapTask,势必会产生网络IO。
--2. 支持切分的压缩格式只有bzip2,还有一种叫LZO,但是LZO压缩文件必须提前建立索引才可以
1.2 MapTask产生的临时文件压缩时
--1. 优势是产生磁盘IO和网络IO
--2. 既然是临时文件,那么压缩算法就不应该太复杂(太复杂会带来CPU的性能开销特别大)
所以要使用cpu性能开销比较低的压缩算法,比如snappy
--3. MapTask默认是没有开启压缩的,如果想要开启,需要修改配置,默认的压缩算法为deflate
1.3 ReduceTask产生的文件压缩时
--1. 目的就是减少HDFS上磁盘空间
--2. 默认是没有开启,想要开启需要修改配置