在spark中修改了countByKey

时间:2019-04-13 14:50:21

标签: scala apache-spark apache-spark-sql

我有一个数据框,如下所示:

HttpSession

我想要在spark中修改countByKeys的版本,该版本返回如下输出:

+------+-------+
| key  | label |
+------+-------+
| key1 | a     |
| key1 | b     |
| key2 | a     |
| key2 | a     |
| key2 | a     |
+------+-------+

我解决这个问题的方法:

步骤:

  1. +------+-------+ | key | count | +------+-------+ | key1 | 0 | | key2 | 3 | +------+-------+ //explanation: if all labels under a key are same, then return count of all rows under a key else count for that key is 0 :连接所有标签(将标签视为字符串)以获取类型的数据框 reduceByKey()
  2. < key,concat_of_all_labels >:按字符解析每个字符串以检查是否都相同。如果它们的返回标签数相同,则返回0。

我是新来的火花,我觉得应该有一些有效的方法来完成这项工作。有没有更好的方法来完成此任务?

3 个答案:

答案 0 :(得分:5)

这非常简单:通过键同时获得计数和非重复计数,然后...然后...

val df = Seq(("key1", "a"), ("key1", "b"), ("key2", "a"), ("key2", "a"), ("key2", "a")).toDF("key", "label")
df.groupBy('key)
  .agg(countDistinct('label).as("cntDistinct"), count('label).as("cnt"))
  .select('key, when('cntDistinct === 1, 'cnt).otherwise(typedLit(0)).as("count"))
  .show

+----+-----+
| key|count|
+----+-----+
|key1|    0|
|key2|    3|
+----+-----+

答案 1 :(得分:1)

添加到先前的解决方案中。如果您的数据确实很大并且您在乎并行性,那么使用reduceByKey会更高效。

如果您的数据很大,并且想减少改组效果,因为groupBy可能会导致改组,这是另一种使用RDD APIreduceByKey的解决方案,它将在分区级别上运行:< / p>

val mockedRdd = sc.parallelize(Seq(("key1", "a"), ("key1", "b"), ("key2", "a"), ("key2", "a"), ("key2", "a")))

// Converting to PairRDD
val pairRDD = new PairRDDFunctions[String, String](mockedRdd)

// Map and then Reduce
val reducedRDD = pairRDD.mapValues(v => (Set(v), 1)).
     reduceByKey((v1, v2) => (v1._1 ++ v2._1, v1._2 + v1._2))

scala> val result = reducedRDD.collect()
`res0: Array[(String, (scala.collection.immutable.Set[String], Int))] = Array((key1,(Set(a, b),2)), (key2,(Set(a),4)))`

现在,最终结果的格式为(key, set(labels), count)

Array((key1,(Set(a, b),2)), (key2,(Set(a),4)))

现在,在驱动程序中收集结果之后,您只需接受仅包含一个标签的Set中的计数:

// Filter our sets with more than one label
scala> result.filter(elm => elm._2._1.size == 1)
res15: Array[(String, (scala.collection.immutable.Set[String], Int))] 
              = Array((key2,(Set(a),4)))

使用Spark 2.3.2进行分析

1)通过解决方案分析(DataFrame API)组

我并不是真正的Spark Expert,但是我会在这里丢5美分:)

是的,DataFrameSQL Query经过Catalyst Optimizer,可以优化groupBy

使用DataFrame API提出的

groupBy方法通过运行df.explain(true)

生成以下物理计划
== Physical Plan ==
*(3) HashAggregate(keys=[key#14], functions=[count(val#15), count(distinct val#15)], output=[key#14, count#94L])
+- Exchange hashpartitioning(key#14, 200)
   +- *(2) HashAggregate(keys=[key#14], functions=[merge_count(val#15), partial_count(distinct val#15)], output=[key#14, count#105L, count#108L])
      +- *(2) HashAggregate(keys=[key#14, val#15], functions=[merge_count(val#15)], output=[key#14, val#15, count#105L])
         +- Exchange hashpartitioning(key#14, val#15, 200)
            +- *(1) HashAggregate(keys=[key#14, val#15], functions=[partial_count(val#15)], output=[key#14, val#15, count#105L])
               +- *(1) Project [_1#11 AS key#14, _2#12 AS val#15]
                  +- *(1) SerializeFromObject [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, scala.Tuple2, true])._1, true, false) AS _1#11, staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, scala.Tuple2, true])._2, true, false) AS _2#12]
                     +- Scan ExternalRDDScan[obj#10]

注意,该工作已分为三个阶段,并且有两个交换阶段。值得一提的是,第二个hashpartitioning exchange使用了不同的键集(键,标签),在这种情况下,IMO将导致随机播放,因为用(key,val)散列的分区将不必与之共存。分区仅用(键)进行散列。

以下是Spark UI可视化的计划:

groupBy solution

2)分析RDD API解决方案

通过运行reducedRDD.toDebugString,我们将获得以下计划:

scala> reducedRDD.toDebugString
res81: String =
(8) ShuffledRDD[44] at reduceByKey at <console>:30 []
 +-(8) MapPartitionsRDD[43] at mapValues at <console>:29 []
    |  ParallelCollectionRDD[42] at parallelize at <console>:30 []

以下是Spark UI可视化的计划:

RDD API Approach

您可以清楚地看到,RDD方法生成的阶段和任务数量较少,并且在我们处理数据集并从驱动程序侧收集数据之前,不会造成任何混乱。仅此一点就告诉我们,这种方法消耗的资源和时间更少。

结论,最终要应用的优化量确实取决于您的业务需求以及要处理的数据的大小。如果您没有大数据,那么使用groupBy方法将是一个简单的选择;否则,请考虑(平行,速度,混洗, 和内存管理)非常重要,并且在大多数情况下,您可以通过分析查询计划并通过Spark UI检查作业来实现这一目标。

答案 2 :(得分:0)

scala> val df = sc.parallelize(Seq(("key1", "a"), ("key1", "b"), ("key2", "a"), ("key2", "a"), ("key2", "a")))

scala> val grpby = df.groupByKey()

scala> val mp = gb.map( line  => (line._1,line._2.toList.length,line._2.toSet.size))
                  .map { case(a,b,c) => (a,if (c!=1) 0 else b) }

scala> val finres  =  mp.toDF("key","label")

scala> finres.show
+----+-----+
| key|label|
+----+-----+
|key1|    0|
|key2|    3|
+----+-----+