Spark性能问题(可能由"基本"错误引起)

时间:2016-09-13 10:50:40

标签: python sql apache-spark pyspark apache-spark-sql

我对Apache Spark(版本1.6)相对较新,我觉得我遇到了障碍:我查看了SE上与Spark相关的大部分问题,但到目前为止我找不到任何帮助我的东西。我相信我在基础层面上做了一些根本性的错误,但是我不能指出它到底是什么,特别是因为我编写的其他代码片段运行得很好。

我会尝试尽可能具体地解释我的情况,尽管我会简化我的任务以便更好地理解。请记住,正如我仍在学习的那样,我使用Spark的本地模式运行此代码;另外值得注意的是,我一直在使用DataFrames(而不是RDD)。最后,请注意以下代码是使用Pyspark用Python编写的,但我确实欢迎使用Scala或Java的可能解决方案,因为我认为这个问题非常基础。

我有一个通用的JSON文件,其结构类似于以下内容:

{"events":[ 
    {"Person":"Alex","Shop":"Burger King","Timestamp":"100"},
    {"Person":"Alex","Shop":"McDonalds","Timestamp":"101"},
    {"Person":"Alex","Shop":"McDonalds","Timestamp":"104"},
    {"Person":"Nathan","Shop":"KFC","Timestamp":"100"},
    {"Person":"Nathan","Shop":"KFC","Timestamp":"120"},
    {"Person":"Nathan","Shop":"Burger King","Timestamp":"170"}]}

我需要做的是计算同一个人两次访问同一商店之间的时间。输出应该是至少有一个客户至少每5秒访问一次的商店列表,以及满足此要求的客户数量。在上面的例子中,输出应该如下所示:

{"Shop":"McDonalds","PeopleCount":1}

我的想法是为每个(Person,Shop)分配相同的标识符,然后继续验证该对是否符合要求。可以使用窗口函数 ROW_NUMBER()来分配标识符,这需要在Spark中使用 hiveContext 。这是上面的文件在分配标识符后的样子:

{"events":[ 
    {"Person":"Alex","Shop":"Burger King","Timestamp":"100","ID":1},
    {"Person":"Alex","Shop":"McDonalds","Timestamp":"101", "ID":2},
    {"Person":"Alex","Shop":"McDonalds","Timestamp":"104", "ID":2},
    {"Person":"Nathan","Shop":"KFC","Timestamp":"100","ID":3},
    {"Person":"Nathan","Shop":"KFC","Timestamp":"120","ID":3},
    {"Person":"Nathan","Shop":"Burger King","Timestamp":"170","ID":4}]}

由于我需要在得出结论之前为每一对执行几个步骤(其中一些需要使用 self join ),所以我使用了临时表。

我写的代码是这样的(当然,我只包括相关部分 - " df"代表"数据框"):

t1_df = hiveContext.read.json(inputFileName)
t1_df.registerTempTable("events")
t2_df = hiveContext.sql("SELECT Person, Shop, ROW_NUMBER() OVER (order by Person asc, Shop asc) as ID FROM events group by Person, Shop HAVING count(*)>1") #if there are less than 2 entries for the same pair, then we can discard this pair
t2_df.write.mode("overwrite").saveAsTable("orderedIDs")
n_pairs = t2_df.count() #used to determine how many pairs I need to inspect
i=1
while i<=n_pairs:
    #now I perform several operations, each one displaying this structure
    #first operation...
    query="SELECT ... FROM orderedIDs WHERE ID=%d" %i
    t3_df = hiveContext.sql(query)
    t3_df.write.mode("overwrite").saveAsTable("table1")
    #...second operation...
    query2="SELECT ... FROM table1 WHERE ..."
    t4_df = hiveContext.sql(query2)
    temp3_df.write.mode("overwrite").saveAsTable("table2")
    #...and so on. Let us skip to the last operation in this loop, which consists of the "saving" of the shop if it met the requirements:
    t8_df = hiveContext.sql("SELECT Shop from table7")
    t8_df.write.mode("append").saveAsTable("goodShops")
    i=i+1

#then we only need to write the table to a proper file
output_df = hiveContext.sql("SELECT Shop, count(*) as PeopleCount from goodShops group by Shop")
output_df.write.json('output')

现在,问题出现了:输出是正确的。我已尝试过多次输入,在这方面,该程序运行良好。然而,它非常缓慢:分析每对需要大约15-20秒,无论每对都有条目。因此,例如,如果有10对需要大约3分钟,如果有100对需要30分钟,依此类推。我在具有相对不错的硬件的几台机器上运行此代码,但没有任何改变。 我也试过缓存我使用的一些(甚至全部)表,但问题仍然存在(在某些情况下所需的时间甚至增加)。更具体地说,循环的最后一个操作(使用&#34;追加&#34;的那个)需要几秒钟才能完成(从5到10),而前6个只需要1-2秒(仍然是很多,考虑到任务的范围,但绝对更易于管理)。

我认为问题可能在于以下一个(或多个):

  1. 使用循环,可能导致并行问题;
  2. 使用&#34; saveAsTable&#34;方法,强制写入I / O
  3. 使用缓存的不良或不良
  4. 这三个是我想到的唯一的东西,因为我使用Spark写的其他软件(我没有遇到任何性能问题)没有使用上述技术,因为我基本上执行了简单的JOIN操作,并使用 registerTempTable 方法来使用临时表(根据我的理解,不能在循环中使用)而不是 saveAsTable 方法。

    我试图尽可能清楚,但如果您确实需要更多详细信息,我将提供更多信息。

    编辑:感谢zero323的答案,我设法解决了我的问题。事实上,使用LAG功能是我真正需要做的事情。另一方面,我已经学会了使用&#34; saveAsTable&#34;应该不鼓励使用这种方法 - 特别是在循环中 - 因为每次调用时都会导致性能大幅下降。除非绝对必要,否则我将从现在开始避免使用它。

1 个答案:

答案 0 :(得分:1)

  

同一个人两次访问同一家商店的时间已经过去了多长时间。输出应该是至少有一个客户至少每5秒访问一次的商店列表,以及满足此要求的客户数量。

如何使用聚合简单lag

from pyspark.sql.window import Window
from pyspark.sql.functions import col, lag, sum

df = (sc
    .parallelize([
        ("Alex", "Burger King", "100"), ("Alex", "McDonalds", "101"),
        ("Alex", "McDonalds", "104"), ("Nathan", "KFC", "100"),
        ("Nathan", "KFC", "120"), ("Nathan", "Burger King", "170")
    ]).toDF(["Person", "Shop", "Timestamp"])
    .withColumn("Timestamp", col("timestamp").cast("long")))

w = (Window()
    .partitionBy("Person", "Shop")
    .orderBy("timestamp"))

ind = ((
    # Difference between current and previous timestamp le 5
    col("Timestamp") - lag("Timestamp", 1).over(w)) <= 5
 ).cast("long") # Cast so we can sum

(df
    .withColumn("ind", ind)
    .groupBy("Shop")
    .agg(sum("ind").alias("events"))
    .where(col("events") > 0)
    .show())

## +---------+------+
## |     Shop|events|
## +---------+------+
## |McDonalds|     1|
## +---------+------+