原因
今天在优化Scala编写的Spark脚本时,发现脚本中大量使用了createOrReplaceTempView()
方法,此时恰好优化遇到了瓶颈。
运行期间发现,在while循环中每次用到临时表都需要读取一次,由于临时表的大小大概为40G,每次读取要话费3-4分钟,处理半年的数据就要花费几个小时,及其消耗性能。
寻找解决方案
进入IDEA
去查看该方法的源码,如下:
def createOrReplaceTempView(viewName: String): Unit = withPlan {
createTempViewCommand(viewName, replace = true, global = false)
}
一开始是一直往下寻找,但到最后都没有发现到底它是个行动算子还是转换算子
后面再次往上溯源时发现原来一开始就已经说明了是个什么算子
先来看看一个普通的行动算子和转换算子:map
和collect
:
def map[U : Encoder](func: T => U): Dataset[U] = withTypedPlan {
MapElements[T, U](func, logicalPlan)
}
def collect(): Array[T] = withAction("collect", queryExecution)(collectFromPlan)
这两个都是基于SparkSQL的调用,即基于DataSet的调用。
对于map
函数,其中调用的withTypedPlan
函数,对他的描述为:A convenient function to wrap a logical plan and produce a Dataset.
翻译之后为:包装逻辑计划并生成数据集的便捷功能。
对于collect
函数,调用的withAction方法,描述为:Wrap a Dataset action to track the QueryExecution and time cost, then report to the user-registered callback functions.
翻译后为:包装一个 Dataset 操作以跟踪 QueryExecution 和时间成本,然后报告给用户注册的回调函数。
可以看出后者为行动算子,前者为转换算子,因为一个是生成DataSet,一个是返回Array[T]。
同样的,对于createOrReplaceTempView
函数,调用的是withPlan
方法,其描述为:A convenient function to wrap a logical plan and produce a DataFrame.
翻译后为:包装逻辑计划并生成 DataFrame 的便捷函数。
因此可以看出createOrReplaceTempView
函数其实是一个转换算子,并不会有结果生成
也就是说,在Spark中,由于lazy机制,只有每次调用时才会产生计算,并且也一定会产生计算。
解决方案
由于每次调用都会读取,在循环当中每次都读取并写入的方式并不可取。因此我对其进行调整,改成了如下操作
spark.read.parquet("").cache().createOrReplaceTempView("")
在第一次读取时进行cache,由于需要用到180次,所以虽然内存会消耗更多,但是对于却会大大缩短运行时间。