Apache spark 递归算法和Spark数据帧的问题
如果您想对图形做任何有趣的事情,不管是使用Apache spark 递归算法和Spark数据帧的问题,apache-spark,apache-spark-sql,Apache Spark,Apache Spark Sql,如果您想对图形做任何有趣的事情,不管是使用GraphX还是新的GraphFrames,您最终都会使用递归算法。我遇到的问题是,当使用DataFrames时,算法的每次迭代都会花费越来越长的时间,每次迭代都会启动更多的执行阶段。我更像是一个功能性的Spark用户——我可以让事情发生,但不完全掌握引擎盖下发生的事情。但我的猜测是,沿袭链一直在扩展,在不破坏沿袭链的情况下,每个步骤都会重新计算早期迭代。所以迭代1做迭代1;迭代2再次执行迭代1,然后执行迭代2;迭代3必须先做1,然后再做2,以此类推 所
GraphX
还是新的GraphFrames
,您最终都会使用递归算法。我遇到的问题是,当使用DataFrames
时,算法的每次迭代都会花费越来越长的时间,每次迭代都会启动更多的执行阶段。我更像是一个功能性的Spark用户——我可以让事情发生,但不完全掌握引擎盖下发生的事情。但我的猜测是,沿袭链一直在扩展,在不破坏沿袭链的情况下,每个步骤都会重新计算早期迭代。所以迭代1做迭代1;迭代2再次执行迭代1,然后执行迭代2;迭代3必须先做1,然后再做2,以此类推
所以我的第一个问题是:这是真的发生了什么,或多或少
为了测试它,我一直在玩RDD.checkpoint
。这似乎有帮助,但我无法证明。这是我的第二个问题——告诉我我使用检查点的方法是否有用
最后,如果能听到其他可能的解决办法,那就太好了。也许Spark甚至不是正确的答案。我什么都愿意
为了测试所有这些,我一直在使用一个简单的算法来多边形填充顶点属性——一种属性继承。给出这样一张图:
val nodes = Seq(
(1L, Option(1L), Option(1L)),
(2L, None, Option(2L)),
(3L, Option(2L), None),
(4L, None, None)
).toDF("id","inputType","recurrence")
val edges = Seq(
(1L, 2L, "parent"),
(2L, 4L, "parent"),
(1L, 3L, "parent")
).toDF("src","dst","type")
+---+---------+----------+
| id|inputType|recurrence|
+---+---------+----------+
| 1| 1| 1|
| 2| 1| 2|
| 3| 2| 1|
| 4| 1| 2|
+---+---------+----------+
在对顶点中缺少的属性进行多边形填充后,应得到如下结果:
val nodes = Seq(
(1L, Option(1L), Option(1L)),
(2L, None, Option(2L)),
(3L, Option(2L), None),
(4L, None, None)
).toDF("id","inputType","recurrence")
val edges = Seq(
(1L, 2L, "parent"),
(2L, 4L, "parent"),
(1L, 3L, "parent")
).toDF("src","dst","type")
+---+---------+----------+
| id|inputType|recurrence|
+---+---------+----------+
| 1| 1| 1|
| 2| 1| 2|
| 3| 2| 1|
| 4| 1| 2|
+---+---------+----------+
顶点1L
是父节点,其他顶点继承了父节点缺少的属性,如果需要,沿着链向上
该算法实际上并不复杂——我将使用自己拼凑的数据帧/图形算法,而不是GraphFrames
首先,我将定义一个函数来创建节点和边的边三元组:
import org.apache.spark.sql.DataFrame
def triplets(vertices: DataFrame, edges: DataFrame) : DataFrame = {
edges.toDF(edges.columns.map(c => "edge_" + c):_*)
.join(vertices.toDF(vertices.columns.map(c => "src_" + c):_*), col("edge_src") === col("src_id"))
.join(vertices.toDF(vertices.columns.map(c => "dst_" + c):_*), col("edge_dst") === col("dst_id"))
}
基于上述数据,三元组(节点、边)
显示:
+--------+--------+---------+------+-------------+--------------+------+-------------+--------------+
|edge_src|edge_dst|edge_type|src_id|src_inputType|src_recurrence|dst_id|dst_inputType|dst_recurrence|
+--------+--------+---------+------+-------------+--------------+------+-------------+--------------+
| 1| 2| parent| 1| 1| 1| 2| null| 2|
| 1| 3| parent| 1| 1| 1| 3| 2| null|
| 2| 4| parent| 2| null| 2| 4| null| null|
+--------+--------+---------+------+-------------+--------------+------+-------------+--------------+
到目前为止还不错,现在是一个递归函数,用于在层次结构下聚合填充null
值:
def fillVertices(vertices: DataFrame, edges: DataFrame) : (DataFrame, DataFrame) = {
val vertexAttributes = vertices.columns.filter(c => c != "id")
val edgeAttributes = edges.columns.filter(c => (c != "src" && c != "dst"))
val messages = triplets(vertices,edges).select(
Seq(col("edge_src"), col("edge_dst")) ++ vertexAttributes.map(attr => when(col("src_" + attr).isNotNull && col("dst_" + attr).isNull, col("src_" + attr)) as "msg_" + attr):_*
).filter(
vertexAttributes.map(attr => col("msg_" + attr).isNotNull).fold(lit(false)){ (a,b) => a || b }
).groupBy(col("edge_dst") as "msg_dst")
.agg(max(col("msg_" + vertexAttributes(0))) as ("msg_" + vertexAttributes(0)), vertexAttributes.slice(1,vertexAttributes.length).map(c => max(col("msg_" + c)) as ("msg_" + c)):_*)
if (! messages.rdd.isEmpty) {
val newVerts = vertices.join(messages, col("id") === col("msg_dst"), "left_outer").select(Seq(col("id")) ++ vertexAttributes.map(c => coalesce(col(c), col("msg_" + c)) as c):_*)
fillVertices(newVerts, edges)
}
else (vertices,edges)
}
如果执行填充顶点(节点、边)。\u 1.show
确实会显示正确的结果——所有节点的null
值都已正确填充。然而,它需要大量的计算阶段
再次注意,这与我在GraphFrames
中看到的行为非常相似——我不认为这与我正在做的事情有关,而是Spark中递归算法的一般问题
正如我所说,我已经尝试检查底层的RDD
,它似乎很有帮助。我用它来检查一个数据帧
:
sc.setCheckpointDir("/your/checkpoint/dir")
def dfCheckpoint(df: DataFrame) : DataFrame = {
df.rdd.checkpoint
if (df.rdd.count > 0) {
df.sqlContext.createDataFrame(df.rdd, df.schema)
}
else df
}
然后并排测试,这里的算法与上面的相同,只是新创建的节点DataFrame
在返回之前得到了检查点
def fillVerticesCheckpoint(vertices: DataFrame, edges: DataFrame) : (DataFrame, DataFrame) = {
val vertexAttributes = vertices.columns.filter(c => c != "id")
val edgeAttributes = edges.columns.filter(c => (c != "src" && c != "dst"))
val messages = triplets(vertices, edges).select(
Seq(col("edge_src"), col("edge_dst")) ++ vertexAttributes.map(attr => when(col("src_" + attr).isNotNull && col("dst_" + attr).isNull, col("src_" + attr)) as "msg_" + attr):_*
).filter(
vertexAttributes.map(attr => col("msg_" + attr).isNotNull).fold(lit(false)){ (a,b) => a || b }
).groupBy(col("edge_dst") as "msg_dst")
.agg(max(col("msg_" + vertexAttributes(0))) as ("msg_" + vertexAttributes(0)), vertexAttributes.slice(1,vertexAttributes.length).map(c => max(col("msg_" + c)) as ("msg_" + c)):_*)
if (! messages.rdd.isEmpty) {
val newVerts = vertices.join(messages, col("id") === col("msg_dst"), "left_outer").select(Seq(col("id")) ++ vertexAttributes.map(c => coalesce(col(c), col("msg_" + c)) as c):_*)
fillVerticesCheckpoint(dfCheckpoint(newVerts), edges)
}
else (vertices, edges)
}
现在,如果您执行fillVerticesCheckpoint(节点、边)。\u 1.show
。这似乎少了很多阶段。我不知道如何量化它,但似乎检查点版本的阶段数是非检查点版本的1/3
根据我所看到的,我猜我的第一个问题的答案是,是的,这是一个血统问题。我的第二个问题的答案似乎是肯定的,检查点让它变得更好。但如果能确认这两个问题,那就太好了
至于我的最后一点,解决同一问题的其他方法,我唯一能想到的是通过在每次迭代之间将
DataFrames
保存到Parquet
文件来创建我自己的检查点。还有其他人吗?您尝试过我们讨论的localCheckpoint
版本吗?没有帮助。即使是一个只需3次Pregel迭代的简单图,也需要25秒来求解。