主要总结一下Hadoop、Hive、HBASE、Spark的要点。
首先Hadoop,说道hadoop他的核心就是MapReduce,就MapReduce的执行步骤来说主要如下:
☆☆☆MapReduce的执行步骤:
1、Map任务处理
1.1 读取HDFS中的文件。每一行解析成一个<k,v>。每一个键值对调用一次map函数。<0,hello you> <10,hello me>
1.2 覆盖map(),接收1.1产生的<k,v>,进行处理,转换为新的<k,v>输出<hello,1> <you,1> <hello,1> <me,1>
1.3 对1.2输出的<k,v>进行分区。默认分为一个区。
1.4 对不同分区中的数据进行排序(按照k)、分组。分组指的是相同key的value放到一个集合中。 排序后:<hello,1> <hello,1> <me,1> <you,1> 分组后:<hello,{1,1}><me,{1}><you,{1}>
1.5 Combiner(可选)对分组后的数据进行归约。
众所周知,Hadoop框架使用Mapper将数据处理成一个个的key/value键值对,在网络节点间对其进行整理(shuffle),然后使用Reducer处理数据并进行最终输出。这其中假如我们有10亿个数据,Mapper会生成10亿个键值对在网络间进行传输(网络带宽严重被占降低程序效率),所有数据都经过reduce处理,造成Reducer的巨大压力,从而大大降低程序的性能。
为了解决上述问题Combiner横空出世。
Combiner
每一个map都可能会产生大量的本地输出,Combiner的作用就是对map端的输出先做一次合并,以减少在map和reduce节点之间的数据传输量,以提高网络IO性能,是MapReduce的一种优化手段之一,其具体的作用如下所述。
(1)Combiner最基本是实现本地key的聚合,对map输出的key排序,value进行迭代。如下所示:
map : (K1, V1) → list(K2, V2)
combine : (K2, list(V2)) → list(K2, V2)
reduce : (K2, list(V2)) → list(K3, V3)
(2)Combiner还有本地reduce功能(其本质上就是一个reduce),例如Hadoop自带的wordcount的例子和找出value的最大值的程序,combiner和reduce完全一致,如下所示:
map : (K1, V1) → list(K2, V2)
combine : (K2, list(V2)) → list(K3, V3)
reduce : (K3, list(V3)) → list(K4, V4)
注意:
①、与mapper和reducer不同的是,combiner没有默认的实现,需要显式的设置在conf中才有作用。
②、并不是所有的job都适用combiner,只有操作满足结合律的才可设置combiner。combine操作类似于:opt(opt(1, 2, 3), opt(4, 5, 6))。如果opt为求和、求最大值的话,可以使用,但是如果是求中值的话,不适用。
③、Combiner的输出是Reducer的输入,如果Combiner是可插拔的(非必须),添加Combiner绝不能改变最终的计算结果。所以Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。
2、Reduce任务处理
2.1 多个map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点上。(shuffle)详见《shuffle过程分析》
2.2 对多个map的输出进行合并、排序。覆盖reduce函数,接收的是分组后的数据,实现自己的业务逻辑, <hello,2> <me,1> <you,1>
处理后,产生新的<k,v>输出。
2.3 对reduce输出的<k,v>写到HDFS中。
说到MapReduce,在其中有一个重要过程不得不介绍一下那就是shuffle
Shuffle 过程解析
shuffle横跨了map端和reduce端两个过程
Map端:
1、在map端首先接触的是InputSplit,在InputSplit中含有DataNode中的数据,每一个InputSplit都会分配一个Mapper任务,Mapper任务结束后产生<K2,V2>的输出,这些输出先存放在缓存中,每个map有一个环形内存缓冲区,用于存储任务的输出。默认大小100MB(io.sort.mb属性),一旦达到阀值0.8(io.sort.spil l.percent),一个后台线程就把内容写到(spill)Linux本地磁盘中的指定目录(mapred.local.dir)下的新建的一个溢出写文件。(注意:map过程的输出是写入本地磁盘而不是HDFS,但是一开始数据并不是直接写入磁盘而是缓冲在内存中,缓存的好处就是减少磁盘I/O的开销,提高合并和排序的速度。又因为默认的内存缓冲大小是100M(当然这个是可以配置的),所以在编写map函数的时候要尽量减少内存的使用,为shuffle过程预留更多的内存,因为该过程是最耗时的过程。)
2、写磁盘前,要进行partition、sort和combine等操作。通过分区,将不同类型的数据分开处理,之后对不同分区的数据进行排序,如果有Combiner,还要对排序后的数据进行combine。等最后记录写完,将全部溢出文件合并为一个分区且排序的文件。(注意:在写磁盘的时候采用压缩的方式将map的输出结果进行压缩是一个减少网络开销很有效的方法!)
3、最后将磁盘中的数据送到Reduce中,从图中可以看出Map输出有三个分区,有一个分区数据被送到图示的Reduce任务中,剩下的两个分区被送到其他Reducer任务中。而图示的Reducer任务的其他的三个输入则来自其他节点的Map输出。
Reduce端:
1、Copy阶段:Reducer通过Http方式得到输出文件的分区。
reduce端可能从n个map的结果中获取数据,而这些map的执行速度不尽相同,当其中一个map运行结束时,reduce就会从JobTracker中获取该信息。map运行结束后TaskTracker会得到消息,进而将消息汇报给 JobTracker,reduce定时从JobTracker获取该信息,reduce端默认有5个数据复制线程从map端复制数据。
2、Merge阶段:如果形成多个磁盘文件会进行合并
从map端复制来的数据首先写到reduce端的缓存中,同样缓存占用到达一定阈值后会将数据写到磁盘中,同样会进行partition、combine、排序等过程。如果形成了多个磁盘文件还会进行合并,最后一次合并的结果作为reduce的输入而不是写入到磁盘中。
3、Reducer的参数:最后将合并后的结果作为输入传入Reduce任务中。(注意:当Reducer的输入文件确定后,整个Shuffle操作才最终结束。之后就是Reducer的执行了,最后Reducer会把结果存到HDFS上。)
关于namenode和jobtracker及datanode和tasktracker
hadoop的集群是基于master/slave的主从模式,namenode和jobtracker属于master,datanode和tasktracker属于slave,master只有一个,而slave有多个。
SecondaryNameNode内存需求和NameNode在一个数量级上,所以通常secondary NameNode(运行在单独的物理机器上)和NameNode运行在不同的机器上。
JobTracker和TaskTracker
JobTracker 对应于 NameNode
TaskTracker 对应于 DataNode
DataNode和NameNode 是针对数据存放而言的
JobTracker和TaskTracker是对于MapReduce执行而言的
mapreduce中几个主要概念,mapreduce整体上可以分为这么几条执行线索:
jobclient,JobTracker与TaskTracker。
1、JobClient会在用户端通过JobClient类将应用已经配置参数打包成jar文件存储到hdfs,
并把路径提交到Jobtracker,然后由JobTracker创建每一个Task(即MapTask和ReduceTask)
并将它们分发到各个TaskTracker服务中去执行
2、JobTracker是一个master服务,软件启动之后JobTracker接收Job,负责调度Job的每一个子任务task运行于TaskTracker上,
并监控它们,如果发现有失败的task就重新运行它。一般情况应该把JobTracker部署在单独的机器上。
3、TaskTracker是运行在多个节点上的slaver服务。TaskTracker主动与JobTracker通信,接收作业,并负责直接执行每一个任务。
TaskTracker都需要运行在HDFS的DataNode上
HBASE的读写流程
接下来是HBASE,有关于HBASE呢就先介绍一下它的读写流程
写数据流程
- zookeeper中存储了meta表的region信息,从meta表获取相应region信息,然后找到meta表的数据
- 根据namespace、表名和rowkey根据meta表的数据找到写入数据对应的region信息
- 找到对应的regionserver
- 把数据分别写到HLog和MemStore上一份
- MemStore达到一个阈值后则把数据刷成一个StoreFile文件。若MemStore中的数据有丢失,则可以总HLog上恢复
- 当多个StoreFile文件达到一定的大小后,会触发Compact合并操作,合并为一个StoreFile,这里同时进行版本的合并和数据删除。
- 当Compact后,逐步形成越来越大的StoreFIle后,会触发Split操作,把当前的StoreFile分成两个,这里相当于把一个大的region分割成两个region。如下图:
读数据流程
- zookeeper中存储了meta表的region信息,所以先从zookeeper中找到meta表region的位置,然后读取meta表中的数据。meta中又存储了用户表的region信息。
- 根据namespace、表名和rowkey在meta表中找到对应的region信息
- 找到这个region对应的regionserver
- 查找对应的region
- 先从MemStore找数据,如果没有,再到StoreFile上读(为了读取的效率)。
Spark,Spark:基于内存的分布式计算框架 ==> 是一个执行引擎
RDD的两种算子1、Transformation(转换)2、Ation(执行)
本质区别:
Transformation 是一个RDD到另一个RDD,延迟执行
Action是一个RDD到一个结果,立即执行
RDD的五大特性
1.A list of partitions
RDD是一个由多个partition(某个节点里的某一片连续的数据)组成的的list;将数据加载为RDD时,一般会遵循数据的本地性(一般一个hdfs里的block会加载为一个partition)。
2.A function for computing each split
RDD的每个partition上面都会有function,也就是函数应用,其作用是实现RDD之间partition的转换。
3.A list of dependencies on other RDDs
RDD会记录它的依赖 ,为了容错(重算,cache,checkpoint),也就是说在内存中的RDD操作时出错或丢失会进行重算。
4.Optionally,a Partitioner for Key-value RDDs
可选项,如果RDD里面存的数据是key-value形式,则可以传递一个自定义的Partitioner进行重新分区,例如这里自定义的Partitioner是基于key进行分区,那则会将不同RDD里面的相同key的数据放到同一个partition里面
5.Optionally, a list of preferred locations to compute each split on
最优的位置去计算,也就是数据的本地性。
Spark Client和Cluster两种运行模式的工作流程
在Client模式下,Driver进程会在当前客户端启动,客户端进程一直存在直到应用程序运行结束
工作流程如下:
1.启动master和worker . worker负责整个集群的资源管理,worker负责监控自己的cpu,内存信息并定时向master汇报
2.在client中启动Driver进程,并向master注册
3.master通过rpc与worker进行通信,通知worker启动一个或多个executor进程
4.executor进程向Driver注册,告知Driver自身的信息,包括所在节点的host等
5.Driver对job进行划分stage,并对stage进行更进一步的划分,将一条pipeline中的所有操作封装成一个task,并发送到向自己注册的executor
进程中的task线程中执行
6.应用程序执行完成,Driver进程退出
在cluster模式下,Driver进程将会在集群中的一个worker中启动,而且客户端进程在完成自己提交任务的职责后,就可以退出,而不用等到应用程序执行完毕
工作流程如下:
1.在集群的节点中,启动master , worker进程,worker进程启动成功后,会向Master进行注册。
2.客户端提交任务后,ActorSelection(master的actor引用),然后通过ActorSelection给Master发送注册Driver请求(RequestSubmitDriver)
3.客户端提交任务后,master通知worker节点启动driver进程。(worker的选择是随意的,只要worker有足够的资源即可)
driver进程启动成功后,将向Master返回注册成功信息
4.master通知worker启动executor进程
5.启动成功后的executor进程向driver进行注册
6.Driver对job进行划分stage,并对stage进行更进一步的划分,将一条pipeline中的所有操作封装成一个task,并发送到向自己注册的executor
进程中的task线程中执行
7.所有task执行完毕后,程序结束