我从SQL脚本转换了以下代码。它已经运行了两个小时,它仍在运行。甚至比SQL Server慢。有什么不正确的吗?
以下是计划,
table2
推送到所有执行者table1
并将分区分发给执行者。table2/t2
中的每一行都加入(交叉连接)table1
的每个分区。 因此,可以分布式/并行地运行交叉连接结果的计算。 (我想,例如假设我有16个执行器,在所有16个执行器上保留t2的副本。然后将表1分成16个分区,每个执行器一个。然后每个执行器在表1的一个分区上进行计算和t2。)
case class Cols (Id: Int, F2: String, F3: BigDecimal, F4: Date, F5: String,
F6: String, F7: BigDecimal, F8: String, F9: String, F10: String )
case class Result (Id1: Int, ID2: Int, Point: Int)
def getDataFromDB(source: String) = {
import sqlContext.sparkSession.implicits._
sqlContext.read.format("jdbc").options(Map(
"driver" -> "com.microsoft.sqlserver.jdbc.SQLServerDriver",
"url" -> jdbcSqlConn,
"dbtable" -> s"$source"
)).load()
.select("Id", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10")
.as[Cols]
}
val sc = new SparkContext(conf)
val table1:DataSet[Cols] = getDataFromDB("table1").repartition(32).cache()
println(table1.count()) // about 300K rows
val table2:DataSet[Cols] = getDataFromDB("table2") // ~20K rows
table2.take(1)
println(table2.count())
val t2 = sc.broadcast(table2)
import org.apache.spark.sql.{functions => func}
val j = table1.joinWith(t2.value, func.lit(true))
j.map(x => {
val (l, r) = x
Result(l.Id, r.Id,
(if (l.F1!= null && r.F1!= null && l.F1== r.F1) 3 else 0)
+(if (l.F2!= null && r.F2!= null && l.F2== r.F2) 2 else 0)
+ ..... // All kind of the similiar expression
+(if (l.F8!= null && r.F8!= null && l.F8== r.F8) 1 else 0)
)
}).filter(x => x.Value >= 10)
println("Total count %d", j.count()) // This takes forever, the count will be about 100
如何用Spark惯用法重写它?
参考:https://forums.databricks.com/questions/6747/how-do-i-get-a-cartesian-product-of-a-huge-dataset.html
答案 0 :(得分:1)
(不知何故,我觉得我已经看过代码了)
代码很慢,因为您只使用一个任务就可以使用JDBC从数据库加载整个数据集,尽管cache
它没有从中受益。
首先查看Web UI中的物理计划和Executors
选项卡,以了解单个执行程序和执行此工作的单个任务。
您应该使用以下其中一项来微调要加载的任务数量:
partitionColumn
,lowerBound
,upperBound
选项predicates
选项请参阅Spark官方文档中的JDBC To Other Databases。
在您完成加载后,您应该努力改进上一个count
操作,并在以下行之后添加另一个count
操作:
val table1: DataSet[Cols] = getDataFromDB("table1").repartition(32).cache()
// trigger caching as it's lazy in Dataset API
table1.count
整个查询速度慢的原因是,只有在执行操作时才会将table1
标记为高速缓存(!)。换句话说,cache
没有任何用处更重要的是使查询性能更差。
table2.cache.count
之后,效果也会提高。
如果您想进行交叉加入,请使用crossJoin运算符。
crossJoin(右:数据集[_]):DataFrame 与其他DataFrame明确的笛卡尔连接。
请注意来自crossJoin
标记的注释(无双关语)。
笛卡尔连接非常昂贵,没有可以推下的额外滤波器。
鉴于可用的所有优化,Spark已经处理了以下要求。
因此交叉连接结果的计算可以分布式/并行运行。
这是Spark的工作(再次,没有双关语意)。
以下要求需要广播。
我想,例如假设我有16个执行器,在所有16个执行器上保留t2的副本。然后将表1分成16个分区,每个执行器一个分区。然后每个执行程序在表1和t2的一个分区上进行计算。)
使用broadcast函数提示Spark SQL的引擎在广播模式下使用table2。
broadcast [T](df:Dataset [T]):Dataset [T] 将DataFrame标记为足够小,以便在广播联接中使用。