前言
这只是一个人随意的一些分享,
你大概可以放宽心的当休闲的东西来看,
看完你大概也许会对Spark会有一些不一样的想法。
正文----一路飙,没有标题,因为是随心写的
- cartesian算子 - 笛卡尔积
cartesian的工作机制大概如下图(该图来自https://www.jianshu.com/p/c62d5d27f4ed)
多的应该就不需要说了吧?
-
那么问题来了
cartesian 会不会发生 shuffle。
我们写如下代码进行测试验证:val conf = new SparkConf() .setMaster("local[1]") .setAppName("test") val sc = new SparkContext(conf) val rdd1 = sc.parallelize(Seq("hello", "world", "hello", "you"), 2) val rdd2 = sc.parallelize(Seq("hello", "me", "hello", "me"), 2) rdd1.cartesian(rdd2).foreach(println) while (true) {}
可以发现,最后是只有一个Stage,也就是说没有发生Shuffle。
-
WTF?按照上面的原理图难道不该是ShuffleDependency 吗?
不知道小伙伴你是怎么看待这个事情的? -
赶紧跑去看看源码
看不懂的小伙伴就别看了~~
这里本人没打算详细说...
主要源码这东西,真的不是那么容易讲明白override def getDependencies: Seq[Dependency[_]] = List( new NarrowDependency(rdd1) { def getParents(id: Int): Seq[Int] = List(id / numPartitionsInRdd2) }, new NarrowDependency(rdd2) { def getParents(id: Int): Seq[Int] = List(id % numPartitionsInRdd2) } )
看到源码我们就大概了然了,
竟然是两个NarrowDependency
,
我们初略解读下这个代码,
当我们分区0去读取数据的时候,
会读取到rdd1的0 / 2 = 0
号分区
会读取到rdd2的0 % 2 = 0
号分区当我们分区1去读取数据的时候,
会读取到rdd1的1 / 2 = 0
号分区
会读取到rdd2的1 % 2 = 1
号分区当我们分区2去读取数据的时候,
会读取到rdd1的2 / 2 = 1
号分区
会读取到rdd2的2 % 2 = 0
号分区当我们分区3去读取数据的时候,
会读取到rdd1的3 / 2 = 1
号分区
会读取到rdd2的3 % 2 = 1
号分区并且是默认的Hash分区器,
这样就保证了能组合的数据肯定会组合到一起 -
上面这些其实了解一下就好了,
但是有一点我们却可以深入的想一下...
为什么要这么做?
当然显而易见的道理:减少了一次shuffle。
那么我们再来看个算子:intersection(取交集)
是不是也可以做类似的事情,
也可以不需要shuffle就能达成正确的结果。
仔细想一想....还真是可以....
但是但是....
Spark的 intersection 是会有shuffle的...
WTF?为啥?Spark没考虑到吗? -
其实我们再深入想一下,
减少shuffle是为了减少什么?
当然是为了避免磁盘IO。
笛卡尔积 那是需要全量数据做连接的,
但是求交集不是,
只要特定的数据就可以,
所以完全可以通过hash将数据正确分组在求交集,
虽然会产生shuffle,
但是数据量却可以减少非常多,
所以权衡一下,
大多数情况下还是使用shuffle 会更快。 -
本文到这里就结束了,没有说什么很实际的东西,
其实主要还是觉的Spark对于很多算子的设计还是很用心的,
真的帮助我们考虑了很多东西,
不过同样的,帮我们做了这么多,
我们思考的东西也少了,
哎~~~总觉的.....
不管怎么样!随时保持学习的心态,
平时很多常用的Api我们真的有去深究过吗?
多想多思才能学到更多的东西,
大家一起加油!!!!
网友评论