Spark之Task的定义

一个供Executor执行的可执行的逻辑单元,Spark目前提供了两类Task,分别为ShuffleMapTask和ResultTask。Job会由一个或者多个Stage,一个Spark Job基于Stage构建成逻辑执行计划和物理执行计划。如: Job A={S1,S2,S3} 由三个Stage构成,那么S1、S2会由ShuffleMapTasks构成,S3作为Job的最后一个Stage由多个ResultTasks构成。Task在Driver侧构建,在Executor侧执行,执行完一个ResultTask会把Task的输出发送给Driver侧的应用程序,执行完一个ShuffleMapTask后基于Task的Partitioner把Task的输出划分多份进行存储。

如上图:一个Stage内只会存在同一种TaskTask数量与StagePartition数量保持一致(运行的Task数量可能会大于Partition数量)

构造函数:

private[spark] abstract class Task[T](

//Task对应的Stage唯一ID号

val stageId: Int,

//stageAttemptId表示Stage被执行的次数,第一次为1
val stageAttemptId: Int,

//Task对应的partitionid,一个Task对一个唯一的partitionid
val partitionId: Int,
@transient var localProperties: Properties = new Properties,
// The default value is only used in tests.
serializedTaskMetrics: Array[Byte] =
      SparkEnv.get.closureSerializer.newInstance().serialize(TaskMetrics.registered).array(),
val jobId: Option[Int] = None,
val appId: Option[String] = None,
val appAttemptId: Option[String] = None) extends Serializable {}

属性:

//在MapOutputTracker中定义的,记录Map侧失败的次数,由Driver侧维护。Executor每次执行Task的时候会检查loca epoch<epoch,会更新epoch,并清除Executor的缓存的mapStatuses。

//在Local Mode下epoch没有意义

var epoch: Long = -1

// TaskContext在run方法中初始化,即在Executor侧初始化
@transient var context: TaskContextImpl = _

TaskContextImpl记录了一个Task的上下文信息,包括Task的重要属性和执行结果。

上下文属性:

属性

说明

stageid

Stage的唯一ID号

stageAttemptNumber

Stage被执行次数,每失败一次加一,由Driver侧维护

partitionid

当前Task所对应的Partitionid,一对一的关系

attemptNumber

Task被执行的次数,每失败一次加一,由Driver侧维护

执行结果:

方法

说明

addTaskCompletionListener

添加任务完成的事件,任务失败后也会添加完成事件

addTaskFailureListener

添加任务失败事件

方法:

//由Executor执行,ShuffleMapTask、ResultTask分别实现了runTask,详情请见Task执行小节

def runTask(context: TaskContext): T 

 

综上述,Spark中的Task主要信息包括 Stage、Partitioner、TaskContext等这些Spark Core的概念,不包括RDD和用户的编程逻辑代码。Driver通过Broadcast把RDD广播给所有的Executor,通过Endpoint把Task点对点的发送到指定的Executor。

注意:RDD与Stage分开存在一个明显的性能问题,Executor每次执行Task的时候都需要返序列化一次,下图为ShuffleMapTask中反序列化的代码。

// RDD本身数据是通过BroadCast广播出去给所有Executor,这里通过BroadCast获取RDD并反序列化
val (rdd, dep) = ser.deserialize[(RDD[_], ShuffleDependency[_, _, _])](
  ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)
_executorDeserializeTime = System.currentTimeMillis() - deserializeStartTime
_executorDeserializeCpuTime = if (threadMXBean.isCurrentThreadCpuTimeSupported) {
  threadMXBean.getCurrentThreadCpuTime - deserializeStartCpuTime
} else 0L

我们都知道反序列化是一个很消耗CPU的操作,对于大数据计算框架CPU资源显得更加珍贵。在DAGScheduler.submitMissingTasks方法中Spark的开发人员有考虑过把RDD放到Stage里面以避免多次的反序列化。通过BroadCast广播给所有的Executor,可以确保每个Task都可独占一个RDD序列化后的副本,在Task之间可以提供很强的隔离性。他们担心用户在RDD的Function闭包中通过对象引用修改对象的状态,例如:Hadoop中JobConf/Configuration对象不是线程安全的。

// TODO: Maybe we can keep the taskBinary in Stage to avoid serializing it multiple times.
// Broadcasted binary for the task, used to dispatch tasks to executors. Note that we broadcast
// the serialized copy of the RDD and for each task we will deserialize it, which means each
// task gets a different copy of the RDD. This provides stronger isolation between tasks that
// might modify state of objects referenced in their closures. This is necessary in Hadoop
// where the JobConf/Configuration object is not thread-safe.
var taskBinary: Broadcast[Array[Byte]] = null

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值