Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Apache spark 递归算法和Spark数据帧的问题_Apache Spark_Apache Spark Sql - Fatal编程技术网

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秒来求解。