鉴于上一次SparkSql引起的那场灾难后,我决定做一个小小的测试:用不同的方法统计数量
infoA:
13111111111,Tom
13222222222,Jack
13333333333,Lily
13444444444,Lucy
13555555555,Allen
13666666666,White
13777777777,Rivers
13888888888,John
13999999999,Robert
infoB:
15111111111,Emma
15222222222,Mary
13555555555,Allen
13666666666,White
15333333333,Kevin
15444444444,Rose
13888888888,John
13999999999,Robert
15555555555,Kelly
15666666666,Steve
15777777777,David
15888888888,Amy
15999999999,Ruby
统计在infoA但不在infoB中用户数量
val spark = SparkSession.builder()
.appName(this.getClass.getSimpleName)
.master(master = "local[*]")
.getOrCreate()
import spark.implicits._
import org.apache.spark.sql.functions._
val dfA = spark.read.textFile(path = "./data/infoA")
.map(_.split(","))
.map(x => (x(0), x(1)))
.toDF("tel_number", "name")
val dfB = spark.read.textFile(path = "./data/infoB")
.map(_.split(","))
.map(x => (x(0), x(1)))
.toDF("tel_number", "name")
dfA.cache()
dfB.cache()
dfB.createTempView(viewName = "viewB")
代码:
val start_time1 = System.currentTimeMillis()
dfA.filter("tel_number not in (select tel_number from viewB)")
.agg(count($"tel_number")).show()
val end_time1 = System.currentTimeMillis()
println("方法一耗时:" + (end_time1 - start_time1))
解释:
这里用的是in (子查询),这次因为数据集很少,所以并没有出现上次灾难,经过上次真实环境测试,显然这种方式,适合于子查询中查出数量较少的情况,比如in (‘A’,‘B’,‘C’),里边很少的类别
结果:
+-----------------+
|count(tel_number)|
+-----------------+
| 5|
+-----------------+
方法一耗时:770
代码:
val start_time2 = System.currentTimeMillis()
dfA.join(dfB, usingColumns = Seq("tel_number"), joinType = "left_outer")
.where(dfB("name").isNull)
.agg(count($"tel_number")).show()
val end_time2 = System.currentTimeMillis()
println("方法二耗时:" + (end_time2 - start_time2))
解释:
这里使用的是join算子左关联两个表,关联不上的右边变表的name就会以null显示,这种方法不会出现巨大灾难,不过由于join是会产生shuffle的算子,因此会很消耗性能。Spark中产生Shuffle算子请参考
结果:
+-----------------+
|count(tel_number)|
+-----------------+
| 5|
+-----------------+
方法二耗时:2755
代码:
val start_time3 = System.currentTimeMillis()
val telSet: Set[String] = HashSet() ++ dfB.select("tel_number")
.map(row => row.mkString).collect()
val telBroadcast = spark.sparkContext.broadcast(telSet)
dfA.select("tel_number").map(row => row.mkString)
.filter(x => !telBroadcast.value.contains(x))
.toDF("tel_number")
.agg(count($"tel_number")).show()
val end_time3 = System.currentTimeMillis()
println("方法三耗时:" + (end_time3 - start_time3))
解释:
使用Broadcast实现Mapper端Shuffle聚合功能的原理,(如果做mapJoin,一般数据结构使用Hashset,当然不使用也可以)
结果:
+-----------------+
|count(tel_number)|
+-----------------+
| 5|
+-----------------+
方法三耗时:713
通过三种方法的比较可以看得出来性能最好的是第三种方法,即使用广播变量的方式,性能最差的是使用Join算子,因为其产生了Shuffle。有的哥们就会问,那么以后直接使用第三种方法不就好了?使用广播变量也有一定的局限性,需要把数据collect到Driver端,这个时候,必须得确保Driver端资源的充足。广播变量的详细知识请一定参考 这篇博客,而第一种方式前面已经说了,也会有局限性,因此,在使用三种方法的时候可酌情考虑。