Spark在Driver上,对Application的每一个stage的task,进行分配之前,都会计算出每个task要计算的是哪个分片数据,RDD的某个partition;Spark的task分配算法,优先,会希望每个task正好分配到它要计算的数据所在的节点,这样的话,就不用在网络间传输数据;
但是呢,通常来说,有时,事与愿违,可能task没有机会分配到它的数据所在的节点,为什么呢,可能那个节点的计算资源和计算能力都满了;所以呢,这种时候,通常来说,Spark会等待一段时间,默认情况下是3s钟(不是绝对的,还有很多种情况,对不同的本地化级别,都会去等待),到最后,实在是等待不了了,就会选择一个比较差的本地化级别,比如说,将task分配到靠它要计算的数据所在节点,比较近的一个节点,然后进行计算。
但是对于第二种情况,通常来说,肯定是要发生数据传输,task会通过其所在节点的BlockManager来获取数据,BlockManager发现自己本地没有数据,会通过一个getRemote()方法,通过TransferService(网络数据传输组件)从数据所在节点的BlockManager中,获取数据,通过网络传输回task所在节点。
在Spark中数据的本地化分为5种
本地化级别
1、PROCESS_LOCAL : 进程本地化,指task计算的数据在本进程(Executor)中
2、NODE_LOCAL:节点本地化,指task计算的数据在本节点(node)的磁盘上,当task在本进程中一直没有执行(如果Driver分发task 3s后没有执行,且重复5次后),此时Driver就把这个没有执行的task发送到本节点的其他executor中执行
3、NO_PREF:没有本地化这一说,无需本地化,如计算所需的数据在关系型数据中(MySQL或Oracle),node1节点中的MySQL,可以被node2或node3节点连接使用。
4、RACK_LOCAL:task计算的数据是在本机架的其他节点上
5、ANY:随机,任何地方都可以
优先级依次是1到5,逐渐降低
参数调整
通过 SparkConf 进行调整:
new SparkConf()
.set(“spark.locality.wait”, “10”)
默认值: spark.locality.wait,默认为3s
PROCESS_LOCAL : spark.locality.wait.process 默认为3s
NODE_LOCAL:spark.locality.wait.node
RACK_LOCAL: spark.locality.wait.rack
观察大部分task的数据本地化级别
如果大多都是PROCESS_LOCAL,那就不用调节了
如果是发现,好多的级别都是NODE_LOCAL、ANY,那么最好就去调节一下数据本地化的等待时长。
调节完,应该是要反复调节,每次调节完以后,再来运行,观察日志
看看大部分的task的本地化级别有没有提升;看看,整个spark作业的运行时间有没有缩短。
实践出真理!!!
图例:
获取本地化级别时间 对应代码:
private def getLocalityWait(level: TaskLocality.TaskLocality): Long = {
// 默认等待时间,取自参数spark.locality.wait,默认为3s
val defaultWait = conf.get(config.LOCALITY_WAIT)
// 根据不同的TaskLocality,取不同的参数,设置TaskLocality等待时间
// PROCESS_LOCAL取参数spark.locality.wait.process
// NODE_LOCAL取参数spark.locality.wait.node
// RACK_LOCAL取参数spark.locality.wait.rack
val localityWaitKey = level match {
case TaskLocality.PROCESS_LOCAL => "spark.locality.wait.process"
case TaskLocality.NODE_LOCAL => "spark.locality.wait.node"
case TaskLocality.RACK_LOCAL => "spark.locality.wait.rack"
case _ => null
}
if (localityWaitKey != null) {
conf.getTimeAsMs(localityWaitKey, defaultWait.toString)
} else {
0L
}
}