Spark之RDD特性学习

RDD:是一种受限的共享内存模型,或者是只读的记录的集合。

底层存储原理:将分布在不同存储器的Block数据块聚集在一起,每个block是由BlockManagerSlave管理,但是Block的元数据由Driver节点的BlockManagerMaster保存,BlockManagerSlave生成Block后向BlockManagerMaster注册该Block,由BlockManagerMaster管理RDD与Block的关系,当RDD不再需要存储时,将向BlockManagerSlave发送删除相应的Block。
在这里插入图片描述

五特性:

分区列表:RDD是被分区的,每个分区否会被一个计算任务处理,分区数量决定了并行计算的数量,分区数量是可以在创建RDD时被指定数量,若是没有指定则是改程序所分配到的cup核数(一个core可承载2-4个partition):partitions = cpus,如果是从HDFS文件创建,默认为文件的Block数。

每个分区都有一个计算函数:RDD的计算函数是以分区作为基础的单位,每个RDD都会实现compute函数,对具体的分区进行计算,RDD的分片是并行的—>分布式并行处理,由于RDD的前后依赖关系,遇到宽窄关系,例如reduceBykey操作时划分为Stage,Stage内部操作是通过Pipeline进行的,在具体处理数据时会通过BloackManager来获取相关数据。

依赖于其他RDD的列表:RDD每次转换都会生成新的RDD,所以RDD会形成了类似流水线一样的前后依赖关系,每当有分区的数据丢失后,spark会通过依赖关系进行重新计算出丢失的分区数据。

key-value数据类型的RDD分区器、控制分区策略和分区数:每个RDD都有Partitioner属性,它决定了RDD如何分区。当然,Partition的个数还决定了每个Stage的Task个数,RDD的分区函数可以分区,可传入相关参数,如HashPartitioner和RangerPartitioner,它本身针对的key-value的形式,如果不是key-value的形式就不会有具体的Partitioner,Partitioner本身决定了下一步会产生多少并行的分区,同时它本身决定了当前并行(Parallelize)shuffle输出的并行数据,从而使Spark具有能够控制数据在不同节点上的特性,用户可以自定义分区策略。

每个分区都有一个优先位置列表:优先位置列表会存储每个Partition的优先位置,对于一个HDFS文件来说,就是每个Partition块的位置。通过观察Saprk任务执行会发现Spark在执行具体计算、具体任务前就知道任务需要发生在哪些节点上,就是说代码运算之前就知道那些节点的数据需要被计算,而不需要计算的节点则不会执行该任务。

TaskContext是读取或者改变执行任务的环境,用org.apache.spark.TaskContext.get()可返回当前可用的TaskContext,可以调用内部的函数访问正在执行任务的环境信息。Partitioner是一个对象,定义了如何在key-value类型的RDD的元素中用key分区,从0到numPartitions-1区间内映射每一个key到partition ID。partition是一个RDD的分区标识符。

RDD七特征:

1、自动进行内存和磁盘数据存储的切换

Spark会优先把数据存放到内存中,如果数据大于内存,则会考虑放置策略和优化算法。当应用程序不足时,Spark应用程序自动将数据存放到磁盘上,保证其高效运行。

2、基于Lineage的搞笑容错机制

每个操作仅仅依赖父操作,分区之间不存在影响,出现错误也仅仅需要恢复单个split的特定部分即可。出错方式:

1、数据检查点:通过数据中心的网络连接不同的机器,然后每次操作时都需要复制数据集,相当于每次都有一个拷贝,拷贝是要通过网络的,网络宽带就是分布式的瓶颈,对存储资源也是很大的消耗。

2、记录数据的更新:每次数据变化都会记录下来,不需要复制数据,但是比较复杂,会消耗性能。

Spark的RDD通过记录数据更新的方式很高效,原因:一是RDD是不可变的且是Lazy级别;二是RDD的写操作是粗粒度的,但是RDD的写操作既可以是粗粒度的也可以是细粒度的。

3、Task失败自动重试

默认重试次数为4次

1. private[spark] class TaskSchedulerImpl(
2. 		val sc:SparkContext,
3. 		valmaxTaskFailures:Int,
4. 		isLocal:Boolean = false)
5. 	extends TaskScheduler with Logging / / 继承任务调度器、日志 trait
6. {
7. 	def this(sc:SparkContext) = this(sc,sc. conf. getInt("spark. task. maxFailures",4))
8.  .......
9. }

TaskSchedulerImpl是底层的任务调度接口TaskScheduler的实现,这些Schedulers从每一个Stage中的DAGScheduler中获取TAskSet,运行他们。DAGScheduler是高层调度,他计算每个Job的Stage的DAG,然后提交给Stage,用gTaskSet的形式启动底层TAskScheduler调度在集群中运行。

4、Stage失败自动重试

默认重试4次,且可以直接运行计算失败的阶段,只计算失败的数据分片。Stage对象可以跟中多个StageInfo,存储SparkListeners监听到的Stage信息,将stage信息传递给Listeners或者web BI,源代码

1.  private[scheduler] abstract class Stage(
2.    val id:Int,
3.    val rdd:RDD[_],
4.    val numTasks:Int,
5.    val parents:List[Stage],
6.    val firstJobId:Int,
7.    val callSite:CallSite)
8.   extends Logging {
9.    // Partition 的个数
10.  val numPartitions = rdd. partitions. length
11.   // 属于这个工作集的 Stage
12.  val jobIds = new HashSet[Int]
13.  val pendingPartitions = new HashSet[Int]
14.   // 用于此 Stage 的下一个新 Attempt 的标识 ID
15.  private var nextAttemptId:Int =0
16.  val name:String = callSite. shortForm
17.  val details:String = callSite. longForm
18.  private var _internalAccumulators:Seq[Accumulator[Long]] = Seq. empty
19.   // Stage 内部所有任务共享的累加器
20.  def internalAccumulators:Seq[Accumulator[Long]] = _internalAccumulators
21.   /∗∗
22.   ∗ 重新初始化与该 Stage 相关联的内部累加器
23.   ∗ 当属于这个 Stage 的任务的一个子集已经完成时,称为一次提交,否则
24. ∗ 重新初始化内部累加器,这里又将覆盖部分任务
25. ∗/
26.  def resetInternalAccumulators():Unit = {
27.   _internalAccumulators = InternalAccumulator. create(rdd. sparkContext)
28.  }
29.   /∗∗
30. ∗最新的[StageInfo] object 指针。 这需要在这里被初始化,
31.   ∗ 任何 Attempts 都是被创造出来的,因为 DAGScheduler 使用 StageInfo
32.   ∗ 告诉 SparkListeners 工作开始时(即发生之前的任何阶段已经创建)
8
     33.    ∗/
34.   private var _latestInfo:StageInfo = StageInfo. fromStage(this,nextAttemptId)
35.    /∗∗
36.    ∗设置 Stage Attempt IDs,当失败时可以读取失败信息,
37.    ∗ 跟踪这些失败,为了避免无休止的重复不断失败
38.    ∗ 在同一个 Stage 中多个 Tasks 任务尝试失败时,我们使用 Hashset 集合记录每一个
39.    ∗ 尝试失败的 ID 号,这样可避免记录重复的失败情况(spark -5945)
40.    ∗/
41.  private val fetchFailedAttemptIds = new HashSet[Int]
42.   private[scheduler] def clearFailures():Unit = {
43.    fetchFailedAttemptIds. clear()
44.   }
45.    /∗∗
46.    ∗ 检查是否应该中止由于连续多次读取失败的 Stage
47.    ∗ 如果失败的次数超过允许的次数,此方法更新失败 Stage Attempts 和返回的运行集
48.    ∗/
49.  private[scheduler] def failedOnFetchAndShouldAbort(stageAttemptId:Int):Boolean = {
50.    fetchFailedAttemptIds. add(stageAttemptId)
51.    fetchFailedAttemptIds. size >= Stage. MAX_CONSECUTIVE_FETCH_FAILURES
52.   }
53.    /∗∗在 Stage 中创建一个新的 attempt ∗/
54.   def makeNewStageAttempt(
55.     numPartitionsToCompute:Int,
56.     taskLocalityPreferences:Seq[Seq[TaskLocation]] = Seq. empty):Unit = {
57.    _latestInfo = StageInfo. fromStage(
58.     this,nextAttemptId,Some(numPartitionsToCompute),taskLocalityPreferences)
59.    nextAttemptId + =1
60.   }
61.    /∗∗返回当前 Stage 中最新的 StageInfo ∗/
62.   def latestInfo:StageInfo = _latestInfo
63.   override final defhashCode():Int = id
64.   override final def equals(other:Any):Boolean = other match {
65.    case stage:Stage => stage != null && stage. id == id
66.    case _ => false
67.   }
68.    // 返回需要重新计算的分区标识的序列
69.   def findMissingPartitions():Seq[Int]
70.  }
71.  private[scheduler] object Stage {
72.    // 允许在一个 Stage 的中止的连续故障数
73.  val MAX_CONSECUTIVE_FETCH_FAILURES =4
74.  }

stage是Spark Job运行时具有相同逻辑功能并且并行计算任务的一个基本单元。Stage中所有的任务都依赖于同样的Shuffle,每个DAG任务通过DAGScheduler在Stage的边界出发生Shuffle形成Stage,然后DAGScheduler运行这些阶段的拓扑顺序。

5、Checkpoint和Persist可主动或者被动触发

persist后RDD工作时,每一个工作节点都会把计算的分区结果保存在内存或或者而磁盘上,可以重复利用。用户只可以与Driver Program交互所以只能cache Transformation算子处理后生成的RDD,而在Transformation算子中生成的RDD不能直接被客户cache,例如reduceByKey()中生成的ShufflRDD、MapPartitionRDD。

6、数据调度弹性

生成有向无环图后,可以将多个任务串联或者并行执行,无需将Stage的中间结果输出到HDFS中,发生故障时用其他节点代替运行。

7、数据分片高度弹性(coalesce)

Spark进行分片时,默认将数据存放到内存中,如果内存存放不下则会将一部分数据存放到磁盘上保存。coalesce源码

1.   /∗∗
2.   ∗ 返回一个新的 RDD,恰好有 numpartitions 分区,类似的合并定义在 RDD 序列
3.   ∗ 此操作的结果在一个窄依赖,如果是从 1000 个分区到 100 个分区,就
4.   ∗ 不会有一个 Shuffle,而不是每 100 个新的分区将要求 10 个当前分区
5.   ∗/
6.  case class Coalesce(numPartitions:Int,child:SparkPlan)extends UnaryNode {
7.   override def output:Seq[Attribute] = child. output
8.   override def outputPartitioning:Partitioning = {
9.    if(numPartitions ==1)SinglePartition
10.   else UnknownPartitioning(numPartitions)
11.  }
12.  protected override def doExecute():RDD[InternalRow] = {
13.   child. execute(). coalesce(numPartitions,shuffle = false)
14.  }
15.  override def canProcessUnsafeRows:Boolean = true
16. }

在计算过程中会产生很多数据碎片,此时如果产生的Partition非常小,但是却要消耗一个线程去处理可能会降低他的处理效率,就会考虑把需要小的Partition合并为一个较大的Partition处理。另一方面若是Partition数量较少,导致每个Partition的Block比较大就会把Partition切成更小的数据分片,这样Saprk处理更多的批次就不会出现OOM异常了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值