处理大输入时,Spark的性能非常慢

时间:2016-10-23 18:08:34

标签: performance scala hadoop apache-spark

我正在使用Spark(在Scala中)读取包含他们共享的用户和页面列表的文件,并且我希望通过他们共享的页面找到与给定用户隔开一定距离的所有用户。

该程序执行得非常糟糕,我经常会遇到GC overhead limit exceeded错误。

我在Mac OSX上本地运行Spark,内存为8 GB。使用spark-submit使用参数--driver-memory 5g提交程序,并通过设置spark.cores.max分配8个核心。输入集是1.15 GB的文件。

是否有人有迹象表明哪种操作效率非常低,以及是否有更好的替代方案?

提前致谢。

此处简要介绍了代码。

每个用户条目包含他/她在选项卡后共享的页面,每个条目由两个换行符分隔,如下所示:

John Doe    <tab>    Page 1
            <tab>    Page 2
            <tab>    Page 3

User 2      <tab>    ...

首先,我使用newAPIHadoopFile读取输入文件。

val hdpConf = new Configuration(sc.hadoopConfiguration)
hdpConf.set("textinputformat.record.delimiter", "\n\n")
val hadoopFile = sc.newAPIHadoopFile("user_pages.list", classOf[TextInputFormat], classOf[LongWritable], classOf[Text], hdpConf)

现在我将其转为成对(user, Array(pagesShared)),如此

val pagesPerUser = hadoopFile.map {
    line =>
        val line_splitted = line._2.toString.split("\t");
        (line_splitted(0), line_splitted.drop(1).mkString.split("\n"))
}

然后,我为每个用户和页面组合(k,v)创建一个包含单个(page, user)对的RDD。

val pageAndUser = pagesPerUser.flatMap(line => line._2.map(page => (line._1, page)))
    .map(...)
    .filter(...)

map使用replaceAll过滤网页标题,filter使用matches()删除包含某些包含引号和正则表达式标题的条目,以检查是否标题符合更多标准。

然后我创建直接链接到另一个用户(user, user)的每个用户的对,然后将其转换为(user, Array(user))形式的RDD(包含所有共享的相同页面的所有直接连接的用户)。

val pageAndUsers = pageAndUser.groupByKey.mapValues(_.toArray)
    .map(line => line._2)
val commonUsers = pageAndUsers.flatMap(users => users.map(user => (user, users)))
    .reduceByKey(_ ++ _).cache()
    .map(users => (users._1, users._2.distinct))

然后可以使用此RDD更进一步确定用户之间的距离,但我认为性能缓慢主要来自其中一个部分。

Spark UI显示,在确定reduceByKey时,程序似乎在mapcommonUsers步骤中执行缓慢。通过与其他程序员的解决方案进行比较,我确定它的表现方式是缓慢的。此外,我经常遇到GC溢出/堆空间超出错误,这表明我的代码中发生了一些内存泄漏。

修改: 经过一番调查后,我很确定问题出在reduceByKey(_++_)步骤。我尝试使用groupByKey代替,但程序似乎在我身上失败并且每次都在特定点上崩溃。

1 个答案:

答案 0 :(得分:1)

执行reduceByKey并使用它来组合可能增长到不确定大小的数据是危险的。例如,它看起来好像是在链接在某种意义上与页面共享的用户。但是,如果您的一个用户链接到所有其他用户,该怎么办?然后,您尝试在reduceByKey中构建的数组将变得非常大。这是您的记忆和GC问题的来源。

我希望如果您在此阶段运行时查看Spark UI,您会看到一些任务挂起。这些将是您有一个用户链接到许多用户的那些。 (也许所有用户都会链接到您的所有用户,所有用户都会挂起)。

我在您的reduceByKey(&#34; pageAndUsers&#34; RDD)之前保存您的数据,然后查询该数据以查看正在进行的操作。

也许如果你有一个小小的&#39;总共可以使用一组而不是一个数组的用户数量,因为这将自动“不同”#39;您的用户在该对的价值中随着它的变化而变得太大(取决于您的数据)。

但是,您需要查看数据以了解问题。要使用我刚才提到的设置逻辑,有一些例子(不是很快)代码:

val pageAndUsers = pageAndUser.groupByKey.mapValues(_.toSet)
    .map(line => line._2)
val commonUsers = pageAndUsers.flatMap(users => users.map(user => (user, users)))
    .reduceByKey(_ ++ _).cache()