使用hadoop镶木地板处理大数据到CSV输出

时间:2017-05-17 09:22:00

标签: scala hadoop apache-spark apache-zeppelin

我有3个数据集,我想加入并对它们进行分组,以获得包含聚合数据的CSV。

数据作为镶木地板文件存储在Hadoop中,我使用Zeppelin运行Apache Spark + Scala进行数据处理。

我的数据集如下所示:

user_actions.show(10)
user_clicks.show(10)
user_options.show(10)

+--------------------+--------------------+
|                  id|             keyword|
+--------------------+--------------------+
|00000000000000000001|               aaaa1|
|00000000000000000002|               aaaa1|
|00000000000000000003|               aaaa2|
|00000000000000000004|               aaaa2|
|00000000000000000005|               aaaa0|
|00000000000000000006|               aaaa4|
|00000000000000000007|               aaaa1|
|00000000000000000008|               aaaa2|
|00000000000000000009|               aaaa1|
|00000000000000000010|               aaaa1|
+--------------------+--------------------+
+--------------------+-------------------+
|           search_id|   selected_user_id|
+--------------------+-------------------+
|00000000000000000001|               1234|
|00000000000000000002|               1234|
|00000000000000000003|               1234|
|00000000000000000004|               1234|
+--------------------+-------------------+

+--------------------+----------+----------+
|           search_id|   user_id|  position|
+--------------------+----------+----------+
|00000000000000000001|      1230|         1|
|00000000000000000001|      1234|         3|
|00000000000000000001|      1232|         2|
|00000000000000000002|      1231|         1|
|00000000000000000002|      1232|         2|
|00000000000000000002|      1233|         3|
|00000000000000000002|      1234|         4|
|00000000000000000003|      1234|         1|
|00000000000000000004|      1230|         1|
|00000000000000000004|      1234|         2|
+--------------------+----------+----------+

我想要实现的是为每个用户id获取带有关键字的JSON,因为我需要在MySQL中导入它们并将user_id作为PK。

user_id,keywords
1234,"{\"aaaa1\":3.5,\"aaaa2\":0.5}"

如果JSON没有开箱即用,我可以使用元组或任何字符串:

user_id,keywords
1234,"(aaaa1,0.58333),(aaaa2,1.5)"

到目前为止我所做的是:

val user_actions_data = user_actions
                                .join(user_options, user_options("search_id") === user_actions("id"))

val user_actions_full_data = user_actions_data
                                    .join(
                                            user_clicks,
                                            user_clicks("search_id") === user_actions_data("search_id") && user_clicks("selected_user_id") === user_actions_data("user_id"),
                                            "left_outer"
                                        )

val user_actions_data_groupped = user_actions_full_data
                                        .groupBy("user_id", "search")
                                        .agg("search" -> "count", "selected_user_id" -> "count", "position" -> "avg")


def udfScoreForUser = ((position: Double, searches: Long) =>  ( position/searches ))

val search_log_keywords = user_actions_data_groupped.rdd.map({row => row(0) -> (row(1) -> udfScoreForUser(row.getDouble(4), row.getLong(2)))}).groupByKey()


val search_log_keywords_array = search_log_keywords.collect.map(r => (r._1.asInstanceOf[Long], r._2.mkString(", ")))

val search_log_keywords_df = sc.parallelize(search_log_keywords_array).toDF("user_id","keywords")
    .coalesce(1)
    .write.format("csv")
    .option("header", "true")
    .mode("overwrite")
    .save("hdfs:///Search_log_testing_keywords/")

虽然这可以通过小数据集按预期工作,但我的输出CSV文件是:

user_id,keywords
1234,"(aaaa1,0.58333), (aaaa2,0.5)"

在针对200 + GB数据运行时遇到问题。

我对Spark& Scala相当新,但我认为我遗漏了一些东西而且我不应该使用DF到rdd,收集映射到数组,并将其并行化回DF以将其导出为CSV。 / p>

总结一下,我想对所有关键字应用评分,并按用户ID对其进行分组,并将其保存为CSV。到目前为止我所做的工作是使用一个小数据集,但当我将它应用于200GB +数据时,apache spark失败了。

2 个答案:

答案 0 :(得分:1)

是的,在Spark中依赖var As = new List<A> { new A { Id = 1 }, new A { Id = 2 }, new A { Id = 3 }, new A { Id = 4 } }; var Bs = new List<B> { new B { Id = 1 }, new B { Id = 2 } }; var set = new HashSet<int>(Bs.Select(b => b.Id)); var filtered = from a in As where !set.Contains(a.Id) select a; // of course, only convert when it's used to get lazy evaluation benefit. As = filtered.ToList(); // If As has unique Ids var dictA = As.ToDictionary(a => a.Id, a => a); foreach (var b in Bs) { if (dictA.ContainsKey(b.Id)) dictA.Remove(b.Id); } // either use dictA or if really have to convert back to a list As = dictA.Values.ToList(); 的任何东西通常都是错误的 - 除非你正在调试一些东西。当您调用collect时,所有数据都是在阵列中的驱动程序中收集的,因此对于大多数大数据集而言,这甚至不是一个选项 - 您的驱动程序将抛出OOM并死掉。

我不明白你为什么要先收集?为什么不简单地映射分布式数据集?

collect

这样,一切都是并行进行的。

关于在search_log_keywords .map(r => (r._1.asInstanceOf[Long], r._2.mkString(", "))) .toDF("user_id","keywords") .coalesce(1) .write.format("csv") .option("header", "true") .mode("overwrite") .save("hdfs:///Search_log_testing_keywords/") dataframes之间切换,我现在不会过分担心。我知道社区主要提倡使用rdds,但根据Spark的版本和您的用例,dataframes可能是更好的选择。

答案 1 :(得分:0)

HDFS的主要目标是将文件拆分为多个块并以冗余方式存储。最好存储在HDFS中分区的数据,除非您绝对有必要拥有一个大文件。