一、自定义输入格式
1.自定义类,继承FileInputFormat
2.提供RecordReader
(1)initliaze(InputSplit split, TaskXXXContext context):会在读取切片数据之前提前被框架调用。 InputSplit split:当前读取的切片
TaskXXXContext context:当前Job的上下文,可以通过context获取Job的配置对象。
(2)boolean nextKeyValue():负责从切片中读取一对key-value,读到之后,返回true,否则返回false
被Mapper的run()
setUp();
while(rr.nextKeyValue()){
map(rr.currentKey(),rr.currentValue(),context);
}
(3)设置自定义的输入格式:Job.setInputFormatClass();
二、SequenFile
1.SequenceFile是Hadoop提供的一种文件格式,相比纯文本格式,格式紧凑!保存的是key-value键值对!
2.在Job中将Reducer输出的key-value保存到SequenceFile中 ,可以使用SequenceFileOutPutFormat
3.在Job中读取的数据存放在SequenceFile中,可以使用SequenceFileInputFormat
三、MapTask中的shuffle过程
1.阶段定义:
MapTask:map---------sort
map:Mapper.map()中将输出的key-value写出之前
sort:Mapper.map()中将输出的key-value写出之后
2.sort
(1)当在map()将输出的key-value写出后,记录是会被Partitionner计算一个分区号
(2)计算后,记录会被收到一个缓冲区(MapOutPutBuffer)
(3)收集线程负责向缓冲区收集数据,缓冲区初始值为100M,当使用到80%的阈值,唤醒溢写线程,溢写线程会将缓冲区已经收集的数据溢写到磁盘
(4)在溢写前,会对缓冲区中的数据进行排序(快速排序),在排序时,只通过比较key进行排序
(5)排序后,按照分区,依次将数据写入到磁盘的临时文件的若干分区中
(6)每次溢写都会生成一个临时文件,当所有数据都溢写完成后,会将所有文件片段合并为一个总的最终文件
(7)在合并时,将所有临时文件的相同分区数据,进行合并,合并后再对所有的数据进行排序(归并排序)
(8)最终生成一个结果文件,这个文件分为若干分区,每个分区的数据已经按照key进行了排序,等待ReduceTask的shuffle线程来拷贝数据!
例子
orderid pid acount
10000001 Pdt_01 222.8
10000002 Pdt_06 722.4
10000001 Pdt_02 222.8
10000001 Pdt_05 25.8
10000003 Pdt_01 232.8
10000003 Pdt_01 33.8
10000002 Pdt_04 122.4
10000002 Pdt_03 522.8
统计同一笔订单中,金额最大的商品记录输出
分析得出: 在同一笔订单中,对每条记录的金额进行降序排序,最大的排前边
①orderid和acount属性都必须作为key
②针对key,提供compareTo(),先按照orderid排序(升降序都可以),再按照acount(降序)排序
Mapper
keyin-valuein
map()
keyout-valueout
shuffle之后的数据:
10000001 Pdt_02 222.8
10000001 Pdt_01 222.8
10000001 Pdt_05 25.8
10000002 Pdt_06 722.4
10000002 Pdt_03 522.8
10000002 Pdt_04 122.4
10000003 Pdt_01 232.8
10000003 Pdt_01 33.8
进入Reduce
获取分组比较器,如果没设置默认使用MapTask排序时key的比较器!
默认的比较器比较策略不符合要求,它会将orderId一样且acount一样的记录才认为是一组的!
自定义分组比较器,只按照orderId进行对比,只要OrderId一样,认为key相等,这样可以将orderId相同的分到一个组!
在组内去第一个最大的即可!
Reducer
keyin-valuein
reduce()
keyout-valueout
四、分区
1.分区是在MapTask中通过Parttitioner来计算分区号
2.Partitioner的初始化
(1)计算总的分数partitions,取决于用户设置的ReduceTask的数量
(2)partitions>1 ,默认获取用户设置的Paritionner,如果用户没有定义,那么会使用HashPartitioner. HashPartitioner根据key的hashcode进行计算,相同的key以及hash值相同的key会分到一个区.
(3)patitions<=1,默认初始化为一个Patitionner,这个Patitioner计算的所有的区号都为0.
3.注意
通常在Job的设置中,希望将数据分为几个区,就设置reduceTask的数量为对应的数量!patitions=设置的ReduceTask的数量,0<=分区器计算的区号<patitions
五、排序
1.排序是MR框架在shuffle阶段自动进行
2.在MapTask端发生两次排序,在排序时,用户唯一可以控制的是提供一个key比较器
3.设置key比较器
(1)用户可以自定义key比较器,自定义比较器必须是一个RawComparator类,重点是实现compare()方法
(2)用户可以通过key,让key实现WritableCompare接口,系统自动提供一个比较器,重点是实现compare()方法
4.排序的分类
全排序:对所有的数据进行排序,指生成一个结果文件,这个结果文件整体有序
部分排序:最终生成N个结果文件,每个文件内部整体有序
二次排序:在对key进行比较时,比较的条件为多个
辅助排序:在进行reduce阶段时,通常比较key是否相同,将相同的key分为1组
六、分组
1.分组通过分组比较器,对进入reduce的key进行对比,key相同的分为一组,一次性进入Reducer,被调用reduce()方法
2.分组比较器的设置:
(1)用户可以自定义key的分组比较器,自定义的分组比较器必须是一个RawComparator的类,重点是实现compareTo()方法
(2)如果没有设置key的分组比较器,默认采取在Map阶段排序时,key的比较器
3. Reduce的细节:在进入reduce(),Reducer会自动实例化一个key,value,这个key-value在Redcuer工作期间,一直是一个不变的对象,每次迭代,reducer会把读到的新的key-value的属性值赋值给key-value!
七、Combiner
1.Combiner的本质是一个Reducer,对key-value进行合并
2.Combiner 和 Reducer的区别:Combiner在shuffle阶段运行 ,Reducer在reduce阶段运行
3.Combiner适用于 +,-操作,不适合 *,/操作
4. Combiner的运行时机
在MapTask端: ①每次从缓冲区将数据溢写到磁盘之前,如果设置了Combiner,数据会被Combine之后,再溢写到磁盘!
②在MapTask最后的merge阶段,如果溢写的片段数据>=3,,如果设置了Combiner,在生成
最终的数据时,也会先执行Combine之后再溢写到磁盘
在ReduceTask端: ③shuffle线程从多个MapTask读取同一个分区的数据,之后进行合并,在合并时,如果shuffle所使用的内存不够,也会将部分数据临时溢写到磁盘,此时如果设置了Combiner,数据会被Combine之后,再溢写到磁盘
5. Combiner的本质目的是为了减少MR在运行期间的磁盘IO和网络IO
七、Zookeeper的安装
1.简介
(1)Zookeeper是java编写的一个开源的分布式的存储中间件
(2)Zookeeper可以用来存储分布式系统中各个进程都关心的核心数据!
(3)Zookeeper采用观察者模式 设计,可以运行客户端在读取数据时,设置一个观察者,一旦这个观察的节点触发了指定的事件,服务端会通知客户端线程,客户端可以执行回调方法,执行对应的操作!
(4)Zookeeper=文件系统+通知机制
2.数据结构
在Zookeeper中每个存储数据的基本单位称为znode,每个znode都有一个路径标识,还可以保存byte[]类型的,这个数据默认为1M.
所有的znode都挂载在 / 节点上
3.安装
(1)必须保证环境变量有JAVA_HOME
(2)解压,后配置conf/zoo_cfg文件,配置dataDir=非 /tmp目录即可
(3)集群模式,需要将集群中所有的zk实例进行配置
(4)如果是集群模式,需要在dataDir中,配置myid文件,myid中需要编写zk的serverid
4.使用
启动:bin/zkServer.sh start
bin/zkCli.sh -server host:port
停止:bin/zkServer.sh stop bin/zkCli.sh -server host:port
5.常用的命令
增:create【-s】 【-e】path data
-s :创建一个带序号的znode
-e:创建一个临时znode,临时的znode被所创建的session拥有,一旦session关闭,临时节点会被删除、
删:delete path
rmr path:递归删除
改:set path data
查:get path
stat path
ls path
ls2 path
6.支持四字命令
7.设置观察者
get path watch :监听指定节点数据的变化
ls path watch :监听当前路径子阶段数据的变化,一旦新增或删除了子节点,会触发事件
注意:观察者在设置后,只有当次有效
8.ZK集群的注意事项
(1)ZK在设计时,采用了paxos协议设计,这个协议要求,集群半数以上服务实例存储,集群才能正常提供服务
(2)ZK集群中,server有leader和follower两种角色。leader只有一个,在集群启动时,自动选举产生!
(3)在选举leader时,只有数据和leader保持同步的follower才有权参与竞选leader,在竞选时,serverID大的server有优势!
(4)集群模式ZK的写流程
①客户端可以任意连接zk实例,向server发送请求写命令
②如果当前连接的server不是leader,server会将写命令发送给leader
③leader将写命令广播到集群中的其它节点,所有节点都执行写操作命令!
④一旦集群中半数以上的节点写数据成功,leader会响应当前server,让当前server响应客户端,写操作完成!
八、HadoopHA
1.HA
High Avilable :高可用,意味着必须有容错机制,不能因为集群故障导致不可用!
HDFS:满足高可用
NN:一个集群只有一个,负责接收客户端的请求
DN:一个集群可以启动N个
YARN:满足高可用
RM:一个集群只有一个,负责接收客户端请求
NM:一个集群可以启动N个
实现hadoop的HA,必须保证NN和RM故障,采取容错机制,可以让集群继续使用!
2.防止故障
核心:避免NN和RM单点故障
以HDFS的HA为例:
①NN启动多个进程,一旦当前正在提供服务的NN故障了,让其他的备用的NN继续顶上
②NN负责接受客户端的请求
在接收客户端的写请求时,NN还负责记录用户上传文件的元数据
保证: 正在提供服务的NN,必须和备用的NN之中的元数据必须是一致的!
元数据的同步: ①在active的nn格式化后,将空白的fsimage文件拷贝到所有的nn的机器上
②active的nn在启动后,将edits文件中的内容发送给Journalnode进程
standby状态的nn主动从Journalnode进程拷贝数据,保证元数据的同步
注意: ①Journalnode在设计时,采用paxos协议, Journalnode适合在奇数台机器上启动!
在hadoop中,要求至少需要3个Journalnode进程
②如果开启了hdfs的ha,不能再启动2nn
③当启动了多个NN时,是否允许多个NN同时提供服务?
不允许多个NN同时对外提供服务,因为如果多个NN同时对外提供服务,那么
在同步元数据时,非常消耗性能,而且容易出错!
在同一时刻,最多只能有一个NN作为主节点,对外提供服务!
其余的NN,作为备用节点!
使用active状态来标记主节点,使用standby状态标记备用节点!
3. HDFS HA的搭建步骤
配置:fs.defaltFS=hdfs://slave01:9000
在整个集群中需要启动N个NN,配置N个NN运行的主机和开放的端口!
配置Journalnode
启动:先启动Journalnode
格式化NN,格式化后的fsimage文件同步到其它的NN
启动所有的NN,需要将其中之一转化为active状态
九、压缩
1.压缩的目的:
压缩的目的是MR运行期间,提高MR运行的效率
压缩可以减少MR运行期间的磁盘IO和网络IO
压缩的原则:
IO密集型,多用压缩
计算密集型,CPU负载过重,少用压缩!
3.hadoop支持的压缩格式
默认:deflate,bzip2,gzip
额外安装的:lzo,snappy
特点:bzip2压缩比最高,压缩速度最慢
snappy压缩速度最快,压缩比凑合
deflate,gzip折中
使用便利性:LZO压缩格式最麻烦!①额外安装LZO压缩格式
②如果JOB输入目录中的文件为LZO压缩格式,需要为每个文件创建索引
如果不创建索引,那么输入的文件无法切片,整个文件作为1片
还需要使用LZO特定的输入格式,使用LZOInputFormat!
其他的压缩格式,和纯文本文件使用一致的,不需要额外设置!
可切片的角度:
如果Job的输入采用了以下压缩格式,只有以下格式支持切片!
只有zip2和lzo可以切片!
使用场景:
bzip:对速度没有要求,常用reduce输出结果的压缩格式!
Lzo:作为Job输入文件的压缩格式!
Snappy:作为shuffle阶段的压缩格式!
Mapper运算结束后,需要向磁盘500M的数据,没有用压缩之前,写的速度100M/s
采用了Snappy压缩,需要向磁盘溢写500M的数据,采用了snappy压缩,写的速度100M/s,500M—>300M
Reduce拷贝300M的数据----> 解压缩(速度很快,解压缩消耗的时间可以忽略不计)------>
压缩的考虑:
①Mapper的输入: 主要考虑每个文件的大小,如果文件过大,需要使用可以切片的压缩格式!
②Reducer的输出: reducer的输出主要考虑,输出之后,是否需要下一个Job继续处理!
单个reducer输出的结果的大小!
如果需要被下个Job继续处理,且单个文件过大,也要使用可以切片的压缩格式!
③shuffle阶段: 速度快即可
压缩的参数:
io.compression.codecs : 代表整个Job运行期间,可以使用哪些压缩格式!
配置这个参数后,配置的压缩格式会被自动初始化!
默认值: deflate,gzip,bzip2
mapreduce.map.output.compress: map阶段输出的key-value是否采用压缩
默认值: false
mapreduce.map.output.compress.codec: map阶段输出的key-value采用何种压缩
默认值: deflate
mapreduce.output.fileoutputformat.compress: job在reduce阶段最终的输出是否采用压缩
默认值: false
mapreduce.output.fileoutputformat.compress.codec: job在reduce阶段最终的输出采用何种压缩
默认值: deflate
mapreduce.output.fileoutputformat.compress.type: 如果Job输出的文件以SequenceFile格式!
SequenceFile中的数据,要以何种形式进行压缩!
NONE: 是否压缩及如何压缩取决于操作系统
RECORD(默认): 每个key-value对作为一个单位,压缩一次
BLOCK: SequenceFile中的block,SequenceFile中的block默认为64K,
每个block压缩一次!
二、调度器
-
FIFO调度器
FIFO调度器的特点就是单队列,所有的Job按照客户端提交的先后顺序,先到先服务!弊端: 如果当前队列中有一个大的Job,非常消耗资源,那么这个Job之后的其他Job都需要付额外的等待时间! 造成集群的资源利用率不足! 解决: 采取多队列的配置
-
容量调度器
容量调度器的本质是多个FIFO的队列组成!Hadoop默认使用就是容量调度器! 特点: 容量 ①每个队列可以配置一定的容量,空闲的资源可以匀给其他队列临时使用 ②可以配置每个job使用的容量的限制,防止一个大的job独占所有资源 ③可以配置每个用户可以使用的容量限制,防止当个用户占用所有资源 优点: ①配置灵活,及时刷新即可、 ②资源利用率高 ③安全,可以配置每个队列的访问用户限制
-
公平调度器
公平调度器的设置和容量调度器大致相同,也是多条队列,每天队列都可以设置一定的容量!
每个Job,用户可以设置容量!区别: 公平调度器在调度策略上,采用最大最小公平算法,来调度Job,这个算法会保证 同一个队列中,所有已经提交,未运行结束的Job,获取到队列中的资源是平等的! 导致在一个队列中,小的Job运行有优势,大的Job可能不能及时获取到必须的所有资源,但是不至于饿死! 当前队列A : 目前有20个CPU,20G 内存...资源 每个Job理论上应该分配 5个CPU, 5G内存,在实际分配资源时,只考虑内存! 队列A中已经提交,未运行的Job: job1 : 2 个MapTask 2 CPU,2G 内存 2 个ReduceTask 1 CPU,1G 内存 job2 : 2 个MapTask 4 CPU,2G 内存 2 个ReduceTask 2 CPU,2G 内存 job3 : 1 个MapTask 1 CPU,1G 内存 1 个ReduceTask 1 CPU,1G 内存 job4 : 4 个MapTask 4 CPU,2G 内存 4 个ReduceTask 2 CPU,2G 内存