Spark Core学习笔记(三)

一、内存管理
1、RDD内存持久化
1》Spark非常重要的一个功能特性就是可以将RDD持久化在内存中。当对RDD执行持久化操作时, 每个节点都会将自己操作的RDD中的partition持久化到内存中, 并且在之后对该RDD的反复使用中, 直接使用内存中缓存的partition数据。这样的话, 对于针对一个RDD反复执行多个操作的场景, 就 只要对RDD 计算一次即可, 后面直接使用该RDD, 而不需要反复计算多次该RDD。
2》巧妙使用RDD持久化, 甚至在某些场景下, 可以将Spark应用程序的性能提升10倍。
对于迭代式算法和快速交互式应用来说, RDD持久化, 是非常重要的。
2、RDD持久化原理
1》要持久化一个RDD, 只要调用其cache()或者persist()方法即可。
2》在该RDD第一次被计算出来时, 就会直接缓存在每个节点中,而且Spark的持久化机制还是自
动容错的。 如果持久化的RDD中的任何partition丢失了, 那么Spark会自动通过其源RDD, 使用transformation操作重新计算该partition。
3》cache()和persist()的区别在于:
cache()是persist()的一种简化方式, cache()的底层就是调用的persist()的无参版本, 同时就是调用
persist(MEMORY_ONLY), 将数据持久化到内存中。 如果需要从内存中清除缓存, 那么可以使用
unpersist()方法。
4》Spark自己也会在shuffle操作时, 进行数据的持久化,比如写入磁盘, 主要是为了在节点失败时, 避免需要重新计算整个过程。
3、RDD持久化策略
RDD持久化是可以手动选择不同的策略的。
比如可以将RDD持久化在内存中、 持久化到磁盘上、 使用序列化的方式持久化, 多持久化的数据进
行多路复用。 只要在调用persist()时传入对应的StorageLevel即可。
1》MEMORY_ONLY
以非序列化的Java对象的方式持久化在JVM内存中。 如果内存无法完全存储RDD所有的partition, 那么那些没有持久化的partition就会在下一次需要使用它的时候, 重新被计算。
2》MEMORY_AND_DISK
同上, 但是当某些partition无法存储在内存中时, 会持久化到磁盘中。 下次需要使用这些partition时, 需要从磁盘上读取。
3》MEMORY_ONLY_SER
同MEMORY_ONLY, 但是会使用Java序列化方式, 将Java对象序列化后进行持久化。 可以减少内存开销, 但是需要进行反序列化, 因此会加大CPU开销。
4》MEMORY_AND_DISK_SER:同MEMORY_AND_DISK。 但是使用序列化方式持久化Java对象
5》DISK_ONLY:使用非序列化Java对象的方式持久化, 完全存储到磁盘上。
6》MEMORY_ONLY_2、MEMORY_AND_DISK_2 ,etc
如果是尾部加了2的持久化级别, 表示会将持久化数据复用一份, 保存到其他节点, 从而在数据丢失时, 不需要再次计算, 只需要使用备份数据即可。
4、如何选择RDD持久化策略
Spark提供的多种持久化级别, 主要是为了在CPU和内存消耗之间进行取舍。
下面是一些通用的持久化级别的选择建议:
1》优先使用MEMORY_ONLY, 如果可以缓存所有数据的话, 那么就使用这种策略。 因为纯内
存速度最快, 而且没有序列化, 不需要消耗CPU进行反序列化操作。
2》如果MEMORY_ONLY策略, 无法存储所有数据的话, 那么使用MEMORY_ONLY_SER,
将数据进行序列化存储, 此操作会消耗CPU进行反序列化。
3》如果需要进行快速的失败恢复, 那么就选择带后缀为_2的策略, 进行数据的备份, 这样在
失败时, 就不需要重新计算了。
4》能不使用DISK相关的策略, 就不用使用, 有的时候从磁盘读取数据, 还不如重新计算一次。
二、共享变量
1、总述
1》Spark一个非常重要的特性就是共享变量。
2》默认情况下, 如果在一个算子的函数中使用到了某个外部的变量, 那么这个变量的值会被拷贝到每个task中。此时每个task只能操作自己的那份变量副本。 如果多个task想要共享某个变量, 那么这种方式是做不到的。
3》Spark为此提供了两种共享变量:
一种是Broadcast Variable( 广播变量) , 另一种是Accumulator( 累加器) 。
4》Broadcast Variable会将使用到的变量, 仅仅为每个节点拷贝一份, 更大的用处是优化性能, 减少网络传输以及内存消耗。
5》Accumulator则可以让多个task共同操作一份变量, 主要可以进行累加操作。
2、 Broadcast Variable广播变量
Spark提供的Broadcast Variable,是只读的,并且在每个节点上只会有一份副本,而不会为每个task都拷贝一份副本。因此其最大作用,就是减少变量到各个节点的网络传输消耗,以及在各个节点上的内存消耗。此外,spark自己内部也使用了高效的广播算法来减少网络消耗。
通过调用SparkContext的broadcast()方法,来针对某个变量创建广播变量,然后在算子的函数内使用广播变量时,每个节点只会拷贝一份副本了。
每个节点可以使用广播变量的value()方法获取值。 记住,广播变量是只读的
案例:
val factor = 3
val factorBroadcast = sc.broadcast(factor)
val arr = Array(1, 2, 3, 4, 5)
val rdd = sc.parallelize(arr)
val multipleRdd = rdd.map(num => num * factorBroadcast.value())
multipleRdd.foreach(num => println(num))
3、 Accumulator 累加器
Spark提供的Accumulator,主要用于多个节点对一个变量进行共享性的操作。
Accumulator只提供了累加的功能,但是却给我们提供了多个task对一个变量并行操作的功能。
task只能对Accumulator进行累加操作,不能读取它的值,只有Driver程序可以读取Accumulator的值
案例:
val sumAccumulator = sc.accumulator(0)
val arr = Array(1, 2, 3, 4, 5)
val rdd = sc.parallelize(arr)
rdd.foreach(num => sumAccumulator += num)
println(sumAccumulator.value)
三、容错机制
1、分布式数据集的容错性有两种方式:数据检查点和记录数据的更新。
数据检查点(Check Point),即将某个时机的中间数据写到存储(通常是HDFS)中。
面向大规模数据分析,数据检查点操作成本很高,需要通过数据中心的网络连接在机器之间复制庞大的数据集,而网络带宽往往比内存带宽低得多,同时还需要消耗更多的存储资源。
2、Spark选择记录更新的方式
RDD是一个有向无环图(DAG),每一个RDD都会记住创建该数据集需要哪些操作,跟踪记录RDD的继承关系,这个关系在Spark中称为 LineAge(血统)
由于创建RDD的操作是相对粗粒度的变换,比如map、filter、join等,即单一的操作应用于许多数据元素,而不需要存储真正的数据,比通过网络复制数据更高效。当一个RDD的某个分区丢失时,RDD有足够的信息记录其如何通过其他RDD进行计算的,只需要通过其他RDD重新计算即可。
3、RDD 血统的依赖关系分为两种:宽依赖、窄依赖。
根据父RDD分区是对应一个还是多个子RDD分区来判断。
窄依赖,一个父RDD分区对应一个子RDD分区; 宽依赖,一个父RDD分区对应多个子RDD分区;

对于窄依赖,只需通过重新计算丢失的那一块数据来恢复,容错成本低
对于宽依赖,当容错重算分区时,因为父分区数据只有一部分是需要重算子分区的,其余数据重算就造成了冗余计算。
所以,不同的应用有时候也需要在适当的时机设置数据检查点。由于RDD的只读特性使得它比常用的共享内存更容易做检查点,具体可以使用doCheckPoint方法。
检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销。
四、大数据开发的核心理念
1、(高)并发操作 2、数据不动,计算动
五、HA高可靠 和 Faderation联盟
1、HA高可靠
通过两个NameNode,其中一个处于Active状态,对外提供服务;另一个处于StandBy状态,对外不提供服务。当处于Active状态的NN发生故障时,处于StandBy状态的NN会切换为Active状态并且对外提供服务,从而保证集群的正常运行。
2、Faderation联盟
Hadoop集群在启动时,NN需要将全部的元数据信息都加载到内存中,从而保证Hadop集群的正常启动。如果NN中的元数据信息体量太大,内存容量又太小,那么会造成元数据信息无法全部都加载到内存中,这时Hadoop集群就无法正常启动了。为了解决这个问题,提出了Faderatoin联盟,让原先的一个NN变成了多个NN,同时对外提供服务,并且每个NN保存元数据的一部分。在Hadoop集群启动时,每个NN将各自保存的那一部分元数据信息加载到内存中,保证Hadoop集群的正常启动,这样也减轻NN所在节点内存的压力。但是Faderation联盟也是有缺陷的。当其中的一个NN发生故障会造成 元数据信息的丢失, 所以Faderation无法从根本上解决单点故障。
六、Spark架构原理深度剖析(Standalone模式)
1、通过spark-submit指令将打好的Spark jar包提交到Spark集群中运行。先从Driver进程开始运行,Driver中包含了我们所编写的代码。首先执行代码中的前两句, val conf=new SparkConf().setAppName("AppOperate").setMaster("local"); val sc=new SparkContext(conf)
创建SparkConf和SparkContext对象, 在创建SparkContext对象的过程中,会去做两件很重要的事,就是创建DAGScheduler和TaskScheduler这两个对象。然后,TaskScheduler会通过一个后台进程负责与Master进行注册通信, 告诉Master有一个新的Application应用程序要运行,需要Master管理分配调度集群的资源。
2、Master接收到TaskScheduler的注册请求之后,会通过资源调度算法对 集群资源进行调度,并且与Worker进行通信,请求Worker启动相应的Executor
3、Worker接收到Master的请求之后,会在本节点中启动Executor。因为集群中有多个Worker节点,那么也意味着会启动多个Executor。一个Application对应着Worker中的一个Executor。
4、Executor启动完成之后,会向Driver中的TaskScheduler进行反注册, 反注册的目的就是让Driver知道新提交的Application应用将由哪些Executor负责执行。
5、Executor向Driver中的TaskScheduler反注册完成之后,就意味 着SparkContext的初始化过程已经完成,接下来去执行SparkContext下面的代码。
6、在SparkContext下面的代码中,创建了初始RDD,并对初始RDD进行了Transformation类型的算子操作,但是系统只是记录下了这些操作行为,这些操作行并没有真正的被执行,直到遇到Action类型的算子,触发提交job之后,Action类型的算子之前所有的Transformation类型的算子才会被执行。job会被提交给DAGScheduler,DAGScheduler根据stage划分算法将job划分为多个stage(阶段),并将其封装成TaskSet(任务集合),然后将TaskSet提交给TaskScheduler。
7、TaskScheduler根据task分配算法,将TaskSet中的每一个小task分配给Executor去执行。
8、Executor接受到task任务之后,通过taskrunner来封装一个task,并从线程池中取出相应的一个线程来执行task。task线程针对RDD partition分区中的数据进行指定的算子操作, 这些算子操作包括Transformation和Action类型的操作。 补充说明:
1)taskrunner(任务运行器),会对我们编写代码进行复制、反序列化操作,进行执行task任务。
2)task分为两大类:ShuffleMapTask和ResultTask。最后一个stage阶段中的task称为ResultTask, 在这之前所有的Task称为ShuffleMapTask。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值