Scala 修改了spark中的countByKey
我有一个数据框,如下所示:Scala 修改了spark中的countByKey,scala,apache-spark,apache-spark-sql,Scala,Apache Spark,Apache Spark Sql,我有一个数据框,如下所示: +------+-------+ | key | label | +------+-------+ | key1 | a | | key1 | b | | key2 | a | | key2 | a | | key2 | a | +------+-------+ +------+-------+ | key | count | +------+-------+ | key1 | 0 | | key2 | 3
+------+-------+
| key | label |
+------+-------+
| key1 | a |
| key1 | b |
| key2 | a |
| key2 | a |
| key2 | a |
+------+-------+
+------+-------+
| 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
我想要spark中countByKeys的修改版本,它返回如下输出:
+------+-------+
| key | label |
+------+-------+
| key1 | a |
| key1 | b |
| key2 | a |
| key2 | a |
| key2 | a |
+------+-------+
+------+-------+
| 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()
:连接所有标签(将标签视为字符串)以获取类型为的数据帧
mapValues()
:按顺序分析每个字符串,以检查它们是否都相同。如果它们返回的标签数相同,则返回0我是spark的新手,我觉得应该有一些有效的方法来完成这项工作。有没有更好的方法来完成这项任务?非常简单:按键同时获取计数和不同计数,那么这只是一个简单的情况。。。然后
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|
+----+-----+
添加到上一个解决方案。如果您的数据非常大,并且您关心并行性,那么使用reduceByKey会更有效 如果您的数据很大,并且希望减少洗牌效果,因为
groupBy
可能会导致洗牌,下面是另一个使用rddapi
和reduceByKey
的解决方案,它将在分区级别内运行:
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)))`
现在,最终结果具有以下格式(键、设置(标签)、计数)
:
现在,在驱动程序中收集结果后,只需接受仅包含一个标签的集合中的计数:
// 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)分析(数据帧API)分组解决方案
我不是一个真正的火花专家,但我会把我的5美分扔在这里:)
是的,DataFrame
和SQL查询
都会通过,这可能会优化groupBy
groupBy
通过运行df.explain(true)
请注意,作业已分为三个阶段,并有两个交换阶段。值得一提的是,第二个hashpartitioning exchange
使用了一组不同的键(key,label),在这种情况下,IMO将导致混乱,因为使用(key,val)散列的分区不必与仅使用(key)散列的分区共存
以下是Spark UI可视化的计划:
2)分析RDD API解决方案
通过运行reducedd.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 []
scala>reducedd.toDebugString
res81:字符串=
(8) Shuffledd[44]在reduceByKey在:30[]
+-(8) MapPartitionsRDD[43]在mapValues在:29[]
|ParallelCollectionRDD[42]在:30[]时并行化
以下是Spark UI可视化的计划:
您可以清楚地看到,RDD方法生成的阶段和任务数量较少,并且在我们处理数据集并从驱动程序端收集数据集之前,不会导致任何混乱。这本身就告诉我们,这种方法消耗的资源和时间更少
结论归根结底,您希望应用多少优化实际上取决于您的业务需求以及您正在处理的数据的大小。如果你没有大数据,那么采用groupBy方法将是一个直截了当的选择;否则,考虑(并行性、速度、洗牌、,
&内存管理)将非常重要,大多数情况下,您可以通过分析查询计划和通过Spark UI检查作业来实现这一点。谢谢您的回答!在spark的指南[这里]()中,据说groupByKey比reduceByKey慢。这不会导致一些性能问题吗?这是RDD的问题。甚至连reduceByKey都没有。我也是一个比较新的人,所以我不能肯定,但是基于我的猜测,这应该是可以的。没有办法使用
reduceByKey
进行聚合。这似乎是真的。在dataframe api中找不到任何reduceByKey。谢谢你的回答。但是在DataFrameAPI中没有reduceByKey
,只有groupByKey
。在这种情况下,spark不会自动选择大小为groupByKey
的查询,以最大限度地减少混乱吗?这是一个非常好的问题,让我一直在思考Catalyst优化器会有多聪明?最终让我做了一个性能评估实验,我把结果附在了我的回复中。请检查我编辑的回复。考虑到RDD是一个抽象层,当涉及到系统编程时,它是一个汇编程序或机器代码。(演化周期:RDD=>Dataframe=>Datasets)。RDD表示如何做某事,而不是如何实现,因此在RDD级别没有优化器的空间
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|
+----+-----+