Spark - 如何通过密钥进行条件减少?

时间:2017-11-24 10:44:48

标签: scala apache-spark dataframe reduce

我有一个包含两列(键,值)的DataFrame,如下所示:

+------------+--------------------+
|         key|               value|
+------------+--------------------+
|[sid2, sid5]|             value1 |
|      [sid2]|             value2 |
|      [sid6]|             value3 |
+------------+--------------------+

键是一组字符串,我想应用reduceByKey转换,如果它们之间有交叉,则两个键相等,输出应如下所示:

+------------+--------------------+
|         key|               value|
+------------+--------------------+
|[sid2, sid5]|   [value1, value2] |
|      [sid6]|             value3 |
+------------+--------------------+

我尝试使用case类作为关键wapper并覆盖equals和hashCode函数,但它不起作用(SPARK-2620)。

知道怎么做吗? 提前谢谢。

更新 - DataFrame架构:

root
 |-- id1: array (nullable = true)
 |    |-- element: string (containsNull = true)
 |-- events1: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- sid: string (nullable = true)
 |    |    |-- uid: string (nullable = true)
 |    |    |-- action: string (nullable = true)
 |    |    |-- touchPoint: string (nullable = true)
 |    |    |-- result: string (nullable = true)
 |    |    |-- timestamp: long (nullable = false)
 |    |    |-- url: string (nullable = true)
 |    |    |-- onlineId: long (nullable = false)
 |    |    |-- channel: string (nullable = true)
 |    |    |-- category: string (nullable = true)
 |    |    |-- clientId: long (nullable = false)
 |    |    |-- newUser: boolean (nullable = false)
 |    |    |-- userAgent: string (nullable = true)
 |    |    |-- group: string (nullable = true)
 |    |    |-- pageType: string (nullable = true)
 |    |    |-- clientIP: string (nullable = true)

2 个答案:

答案 0 :(得分:1)

这不能用reduceByKey来解决,因为问题定义不适合byKey转换。核心要求是密钥具有明确定义的标识,但这不是这种情况。

考虑我们拥有密钥[sid2, sid4, sid5][sid2, sid3, sid5]的数据集。在这种情况下,无法将对象唯一地分配给分区。覆盖哈希码根本不会帮助你。

更糟糕的是,一般情况下的问题是分布式的。考虑一组集合,例如对于每个集合,至少有一个其他集合具有非空交集。在这种情况下,所有值都应合并为一个"集群"。

总体而言 - 对于没有相当严格限制的Spark而言,这不是一个好问题,并且根本无法通过基本byKey转换来解决。

低效的解决方案,可能部分解决您的问题是使用笛卡尔积:

rdd.cartesian(rdd)
  .filter { case ((k1, _), (k2, _)) => intersects(v1, v2) }
  .map { case ((k, _), (_, v)) => (k, v) }
  .groupByKey
  .mapValues(_.flatten.toSet)
然而,这是低效的,并没有解决歧义。

答案 1 :(得分:1)

我认为使用Spark SQL的数据集API是可行的(并且直接翻译了基于RDD的@ user9003280解决方案)。

// the dataset
val kvs = Seq(
  (Seq("sid2", "sid5"), "value1"),
  (Seq("sid2"), "value2"),
  (Seq("sid6"), "value3")).toDF("key", "value")
scala> kvs.show
+------------+------+
|         key| value|
+------------+------+
|[sid2, sid5]|value1|
|      [sid2]|value2|
|      [sid6]|value3|
+------------+------+

val intersect = udf { (ss: Seq[String], ts: Seq[String]) => ss intersect ts }
val solution = kvs.as("left")
  .join(kvs.as("right"))
  .where(size(intersect($"left.key", $"right.key")) > 0)
  .select($"left.key", $"right.value")
  .groupBy("key")
  .agg(collect_set("value") as "values")
  .dropDuplicates("values")
scala> solution.show
+------------+----------------+
|         key|          values|
+------------+----------------+
|      [sid6]|        [value3]|
|[sid2, sid5]|[value2, value1]|
+------------+----------------+