1 缓存及缓存级## 标题别
缓存的方法有两个cache和persist缓存有两个方法cache和persist, 通过源码可以看出cache调用了persist, 所以这两个方法运行的效率可以看做是一样的
在persist方法中需要传入StorageLevel这个对象
StorageLevel对象中可以指定缓存的数据存入到内存中, 磁盘中, 堆外缓存中, 是否关闭序列化, 以及副本数量
存储级别的选择
MEMORY_ONLY > MEMORY_ONLY_SER > MEMORY_AND_DISK_SER > MEMORY_AND_DISK
默认情况下是 MEMORY_ONLY 但是前提是内存足够大(默认jvm的0.6但是可以调整)才行能够装下整个rdd的数据
当内存大小小于rdd数据的大小,采用****MEMORY_ONLY_SER ,会进行一个序列化与反序列化的操作,将partition变为一个数组,虽增加了序列化的开销,但是全是在内存中进行 也是很快的
**如果序列化之后rdd大于内存大小,就会溢写到磁盘,可能导致oom溢出。选择****MEMORY_AND_DISK_SER ,**它会优先将数据缓存到内存中,内存不足才将数据加载到磁盘
和MEMORY_AND_DISK区别是****MEMORY_AND_DISK_SER 会进行序列化而****MEMORY_AND_DISK不会
后面接_2这种就是会有副本提供容错机制,方便快速还原故障
**2.**cache() persist() checkpoint()之间的区别与联系
相同的都是做rdd持久化的
由1的图片可以看出cache是persist的简化方式,就是persist传入无参的版本(persist默认的存储级别),对应的是persist(MEMORY_ONLY)。
区别是 cache和persist不会截断血缘关系而checkpoint会截断
2.1 三者的运用场景
cache:由上可知存储级别是MEMORY_ONLY,所以就是rdd大小小于内存大小
persist:可以传入参数来确定他的存储级别
checkpoint:计算链太长或者rdd依赖的rdd有多个依赖的rdd,会将rdd的数据放入hdfs上或者本地文件夹中。
思考:checkpoint的功能和persist选择DISK_ONLY不是一样的吗?
回答:rdd.persist(StorageLevel.DISK_ONLY)缓存的数据会随着该persist的进程结束而被删掉;
而checkpoint不手动remove则不会被删,下一个driver依旧可以用。
3.spark的运行模式和工作流程
3.1 运行模式
local:在一台机器,一般用于开发测试
standlaone:完全独立的spark集群,不依赖其他集群,分为Master和work。大体过程–客户端向Master注册应用,Master向work发送消息,依次启动Driver,executor,Driver负责向executors发送任务消息。
yarn:依赖于hadoop集群,yarn资源调度框架,将应用提交给yarn,在ApplactionMaster(相当于Stand alone模式中的Master)中运行driver,在集群上调度资源,开启excutor执行任务。
metsos:类似于yarn模式,运行在Mesos集群上(Mesos是Apache下的开源分布式资源管理框架,它被称为是分布式系统的内核。Mesos最初是由加州大学伯克利分校的AMPLab开发的,后在Twitter得到广泛使用)。
3.2工作流程
Cluster Manager:在standalone模式中即为Master主节点,控制整个集群,监控worker。在YARN模式中为资源管理器
Worker节点:从节点,负责控制计算节点,启动Executor或者Driver。
Driver: 运行Application 的main()函数
Executor:执行器,是为某个Application运行在worker node上的一个进程
3.3任务提交四个阶段
4.shuffle
4.1 HashShuffleManager是spark1.2版本之前的,因为不用,所以不详述。
4.2 sortShuffleManager
sortShuffleManager是spark1.2版本后默认的,分为两个模式。
普通模式
数据会先写入一个数据结构,聚合算子写入Map,一边通过Map局部聚合,一边写入内存。Join算子写入ArrayList直接写入内存中。然后需要判断是否达到阈值,如果达到就会将内存数据结构的数据写入到磁盘,清空内存数据结构。
在溢写磁盘前,先根据key进行排序,排序过后的数据,会分批写入到磁盘文件中。默认批次为10000条,数据会以每批一万条写入到磁盘文件。写入磁盘文件通过缓冲区溢写的方式,每次溢写都会产生一个磁盘文件,也就是说一个task过程会产生多个临时文件。
最后在每个task中,将所有的临时文件合并,这就是merge过程,此过程将所有临时文件读取出来,一次写入到最终文件。意味着一个task的所有数据都在这一个文件中。同时单独写一份索引文件,标识下游各个task的数据在文件中的索引,start offset和end offset。
这样算来如果第一个stage 50个task,每个Executor执行一个task,那么无论下游有几个task,就需要50个磁盘文件。
bypass流程
shuffle read task 的数量小于等于spark.shuffle.sort.bypassMergeThreshold参数的值时(default:200),就会启动bypass机制
当前stage的task会为每个下游的task都创建临时磁盘文件。将数据按照key值进行hash,然后根据hash值,将key写入对应的磁盘文件中(个人觉得这也相当于一次另类的排序,将相同的key放在一起了)。最终,同样会将所有临时文件依次合并成一个磁盘文件,建立索引。
该机制与sortshuffle的普通机制相比,在readtask不多的情况下,首先写的机制是不同,其次不会进行排序。这样就可以节约一部分性能开销。
5.优化
5.1 广播变量
A.在driver端定义而不是executor段定义
B.是将rdd的结果广播出去而不是rdd广播,因为rdd并不存数据
C.executor端用到driver变量,不用广播则executor会有多少task就有多少变量副本,用了广播每个
D.executor就只有一个变量副本。
5.2 对executor优化
A.增加executor数量和executor的cpu核数以及内存
B.设置task数量一般为application的总核数的2-3倍
5.3 对于RDD操作
A.避免一个RDD被重复计算:可以将该RDD(被多个后面rdd使用)抽取为一个公共的RDD
B.可以使用持久化:将多次使用的RDD持久化到内存或者磁盘中
C.如B所言,如果内存不够放下RDD,可以将他序列化,将RDD每个分区的数据序列化为一个字节数组,减少内存占比
这里可以参考1所言的存储级别
5.4 JVM调优
原因:spark task执行算子函数时,会参数大量对象,而这些对象放入年轻代中,如果年轻代内存太小,会导致Eden区和survivor区频繁内存溢出,导致minor gc,而minor gc就会导致后面用不到的对象存在老年代中,老年代内存不足就是full gc
A.spark堆内存用于两块,一个是给rdd进行cache或者persist操作,另一个是给算子函数进行存储对象所用。一般默认的cache操作是站60%,可以通过查看stage每个task的运行时间,gc时间来调整这个参数,或者persist采用MEMORY_AND_DISK_SER将部分数据转到磁盘。
B.executor的堆外内存:当spark处理超大数据量时,堆外内存可能不够,会报shuffle file cant find,oom等问题,可以通过参数配置脚本里:yarn.executormemoryOverhead=2048设为1g或2g,默认是300m
5.5 算子函数调优
A.Map改为MapPartition:因为map处理是一个分区里面的一条数据,MapPartition是对一个分区数据进行处理
B.filter后接colasce:filter之后,有可能每个task处理的数据量相差太大,导致数据倾斜,而且partition里数据量减少,后面处理依旧按照原来的相同数量的task处理导致资源浪费
C.尽量使用foreach而不是foreach 同A
D.能用reduceByKey就用,因为在写入数据时有一个本地combine操作,减少IO,数据量和网络传输
5.6 shuffle调优
A.针对于数据写入磁盘前先写入buffer缓冲区(shuffle write task 任务的)这种情况,参数是spark.shuffle.file.buffer,默认是32k,如果内存充足,可以调大比如64k
B.shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。参数是spark.reducer.maxSizeInFlight,可以调大这个减少拉取数据的次数以及网络传输的次数
C.从read task向write task拉取数据时,如果因为网络问题导致拉取失败,会自动重试拉取。对于比较费时的shuffle可以增加spark.shuffle.io.maxRetries参数(重试的最大次数),避免网络问题或者full gc导致任务失败。
D.executor内存中默认20%是分给read task进行聚合操作。如果内存充足,可以增加这个比例。参数为spark.shuffle.memoryFraction。
E.患有一些针对不同shuffle的参数配置此处不说了。
5.7 数据倾斜处理
A.hive预处理:如果是hive表数据不均匀,可以在hive里先进行聚合或者join操作,那么spark代码中就不需要写相关的shuffle算子。
B.过滤少量导致倾斜的key:如果少量倾斜的key对结果并不重要或者影响很小很小,那么可以用where去掉它。
C.提高shuffle的并行度:在执行shuffle算子时,可以给该算子传入一个参数,比如reduceByKey(1000),该参数时该算子在执行read task任务时的task的数量。就是同一份数据被1000个task处理,当然比以前几百个快。
D.两阶段聚合(局部聚合和全局聚合):适合与对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时。原理是–将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。
E.将reduce join转为map join:适用于在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,而且join操作中的一个RDD或表的数据量比较小(比如几百M或者一两G)。实现方案–不使用join算子进行连接操作,而使用Broadcast变量与map类算子实现join操作,进而完全规避掉shuffle类的操作,彻底避免数据倾斜的发生和出现。将较小RDD中的数据直接通过collect算子拉取到Driver端的内存中来,然后对其创建一个Broadcast变量;接着对另外一个RDD执行map类算子,在算子函数内,从Broadcast变量中获取较小RDD的全量数据,与当前RDD的每一条数据按照连接key进行比对,如果连接key相同的话,那么就将两个RDD的数据用你需要的方式连接起来。
G.采样倾斜key并分拆join操作:适用于RDD/Hive表进行join的时候,如果数据量都比较大,无法采用“解决方案五”,那么此时可以看一下两个RDD/Hive表中的key分布情况。如果出现数据倾斜,是因为其中某一个RDD/Hive表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有key都分布比较均匀。实现思路–1.对包含少数几个数据量过大的key的那个RDD,通过sample算子采样出一份样本来,然后统计一下每个key的数量,计算出来数据量最大的是哪几个key。2.然后将这几个key对应的数据从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上n以内的随机数作为前缀,而不会导致倾斜的大部分key形成另外一个RDD。3.接着将需要join的另一个RDD,也过滤出来那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按顺序附加一个0~n的前缀,不会导致倾斜的大部分key也形成另外一个RDD。4.再将附加了随机前缀的独立RDD与另一个膨胀n倍的独立RDD进行join,此时就可以将原先相同的key打散成n份,分散到多个task中去进行join了。5.而另外两个普通的RDD就照常join即可。6.最后将两次join的结果使用union算子合并起来即可,就是最终的join结果。
6.SPARK容错机制
6.1 血缘关系
血缘关系就是宽窄依赖。一对一为窄依赖,一对多是宽依赖(其实就是超生或独生关系)。
如何容错: 一台机器宕机,如果是窄依赖只需重算到宽依赖即它的父RDD重算,不用依赖其他节点,因为父RDD拥有全部子RDD的数据。在宽依赖情况下,丢失一个子RDD分区重算的每个父RDD的每个分区的所有数据并不是都给丢失的子RDD分区用的,会有一部分数据相当于对应的是未丢失的子RDD分区中需要的数据,这样就会产生冗余计算开销,这也是宽依赖开销更大的原因。
6.2 checkpoint机制
就是将内存中的变化刷新到持久存储,斩断依赖链 在存储中 checkpoint 是一个很常见的概念。
checkpoint 是把 RDD 保存在 HDFS中, 是多副本可靠存储,所以依赖链就可以丢掉了,就斩断了依赖链, 是通过复制实现的高容错。但是有一点要注意, 因为checkpoint是需要把 job 重新从头算一遍, 最好先cache一下, checkpoint就可以直接保存缓存中的 RDD 了, 就不需要重头计算一遍了, 对性能有极大的提升。
何时加checkpoint:
1.DAG中的Lineage过长,如果重算,则开销太大(如在PageRank中)。
2.在宽依赖上做Checkpoint获得的收益更大。
可以看 2
7.stage划分
从后往前遇到宽依赖就划分stage,即发生shuffle就切分stage
7.1stage计算模式
pipeline管道计算模式,pipeline只是一种计算模式而已,对于上图有几点:
1.Spark的pipeLine的计算模式,相当于执行了一个高阶函数f3(f2(f1(textFile))) !+!+!=3 也就是来一条数据然后计算一条数据,把所有的逻辑走完,然后落地,准确的说一个task处理遗传分区的数据 因为跨过了不同的逻辑的分区。而MapReduce是 1+1=2,2+1=3的模式,也就是计算完落地,然后在计算,然后再落地到磁盘或内存,最后数据是落在计算节点上,按reduce的hash分区落地。所以这也是比Mapreduce快的原因,完全基于内存计算。
2.数据何时落地:shuffle write时 对rdd进行持久化时。
3.stage的并行度是由最后一个RDD分区决定的,一般时一个partition对应一个分区,但是在最后的reduce时,可以指定task数量参考5.7。
此处来自别的博客
8.RDD
8.1 RDD种类
1.transform类型:延迟加载,有map,flatmap等
2.action算子:立即执行,立即执行,在源码中回调用runjob的方法,每调用一个action算子就会提交一个job。有foreach,take,reduce,collect等等
8.2 RDD特性
一组分片
即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目。
一个计算每个分区的函数
Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。
RDD之间的依赖关系
RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。
一个Partitioner
即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。
一个列表
存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。
8.3 发生shuffle的算子
byKey类的算子:比如reduceByKey、groupByKey、sortByKey、aggregateByKey、combineByKey
repartition类的算子:比如repartition(少量分区变成多个分区会发生shuffle)、 repartitionAndSortWithinPartitions、coalesce(需要指定是否发生shuffle)、partitionBy
join类的算子:比如join(先grou
- List item
pByKey后再join就不会发生shuffle)、cogroup
9 spark跟hadoop比较
1.Spark没有提供文件管理系统,所以,它必须和其他的分布式文件系统进行集成才能运作,它只是一个计算分析框架,专门用来对分布式存储的数据进行计算处理,它本身并不能存储数据;
2.Spark可以使用基于HDFS的HBase数据库,也可以使用HDFS的数据文件,还可以通过jdbc连接使用Mysql数据库数据;Spark可以对数据库数据进行修改删除,而HDFS只能对数据进行追加和全表删除;
3.Spark处理数据的设计模式与MR不一样,Hadoop是从HDFS读取数据,通过MR将中间结果写入HDFS;然后再重新从HDFS读取数据进行MR,再刷写到HDFS,这个过程涉及多次落盘操作,多次磁盘IO,效率并不高;而Spark的设计模式是读取集群中的数据后,在内存中存储和运算,直到全部运算完毕后,再存储到集群中
4.Spark中RDD一般存放在内存中,如果内存不够存放数据,会同时使用磁盘存储数据;通过RDD之间的血缘连接、数据存入内存中切断血缘关系等机制,可以实现灾难恢复,当数据丢失时可以恢复数据;这一点与Hadoop类似,Hadoop基于磁盘读写,天生数据具备可恢复性;
5.Hadoop中对于数据的计算,一个Job只有一个Map和Reduce阶段,对于复杂的计算,需要使用多次MR,这样涉及到落盘和磁盘IO,效率不高;而在Spark中,一个Job可以包含多个RDD的转换算子,在调度时可以生成多个Stage,实现更复杂的功能;
ark中RDD一般存放在内存中,如果内存不够存放数据,会同时使用磁盘存储数据;通过RDD之间的血缘连接、数据存入内存中切断血缘关系等机制,可以实现灾难恢复,当数据丢失时可以恢复数据;这一点与Hadoop类似,Hadoop基于磁盘读写,天生数据具备可恢复性;
5.Hadoop中对于数据的计算,一个Job只有一个Map和Reduce阶段,对于复杂的计算,需要使用多次MR,这样涉及到落盘和磁盘IO,效率不高;而在Spark中,一个Job可以包含多个RDD的转换算子,在调度时可以生成多个Stage,实现更复杂的功能;