一、再说 RDD
Spark中的RDD虽然统一叫做弹性分布式数据集,但是,RDD的创建方式却是多种多样的,且RDD的数据类型也是有区分的,且RDD的操作分为两种,一种是转换(Transformation)操作,一种是执行(Action)操作
这里的RDD的操作,我们具体点就是一个完整的Spark计算过程(Driver节点提交任务,多个Worker节点并行执行任务);
其中RDD的转换操作目的就是为了得到一个或者说是创建一个新的RDD,有可能这个RDD只经过一道转换就直接进行Action操作了,也有可能被转来转去,但是,记住,只要RDD没有执行Action操作,则RDD在整个任务执行过程中的转来转去实际上是没有意义的,也就是RDD【数据集】没有被真正的创建,即内存中是没有任何数据的;
【这就好比口头协议一样,如果协议最终不落实,你说的再漂亮也是白搭】
由于上述的特殊性:只有执行Action操作的时候,RDD才会被真正创建,那如何将创建好的RDD缓存起来避免重复转换呢?
虽然扯的有点多,但是如果不多扯点的话,太不像话了,不像我的风格,哈哈
缓存的方式有两种: (1)cache (2)persist
(1) cache 【实际上调用的是无参persist()函数,即默认缓存策略方式:MEMORY_ONLY -- 性能最高】
1、创建一个list列表
2、利用parallelize函数,将现有list转换成可以并行计算的RDD,且parallelize函数支持分区【第二个参数指定】
3、缓存RDD
(2)persist【可传入持久化存储级别,即方法有好几个重载,默认为:MEMORY_ONLY 】
二、什么是Pari RDD
RDD是一个数据结构,它表示了一组数据的集合,这个数据集是有类型的,如同Java的List和Map一样,有列表集合,也有键值对的集合,RDD也是,有List列表类型的RDD,也有Map键值对类型的RDD,后者我们称作是Pari RDD,即这种类型的RDD可以进行key值的转换操作得到一个新的RDD
(1) List RDD
启一个spark-shell
创建一个list类型的RDD,并执行Action操作,遍历打印RDD中的元素【不要忘了,缓存它,待会我们要用】
scala> var list = List("Hadoop","Spark","Java","Spark")
list: List[String] = List(Hadoop, Spark, Java, Spark)
scala> var listRDD = sc.parallelize(list,2)
listRDD: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at parallel
ize at <console>:26
scala> listRDD.persist()
res0: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at parallelize
at <console>:26
scala> listRDD.foreach(println)
Hadoop
Java
Spark
Spark
执行效果
(2) Pari RDD
利用上面缓存起来的RDD,我们再进行一次map转换操作得到新的RDD -- mapRDD
scala> var mapRDD = listRDD.map(word => (word,1))
mapRDD: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[1] at map at
<console>:25
scala> mapRDD.take(10).foreach(println)
(Hadoop,1)
(Spark,1)
(Java,1)
(Spark,1)
执行效果:
map函数不陌生吧,它是一个RDD转换函数,通过key-value的形式,得到一个新的RDD,这个RDD就是一个Pari RDD
注意,在对RDD进行遍历操作的时候【Action操作】,如果RDD是本地模式的话,println打印的就是当前机器上的RDD,如果是集群模式的话,则println的结果会显示在RDD所在的worker机器上,而不是当前driver机器上;如果想要在driver节点上打印分布在各个worker节点上的RDD的话,则需要用到collect函数将所有分区上的RDD收集过来,如下:
【mapRDD.take(10).foreach(println)】
由于我们不知道RDD数据有多少个,其中一个RDD又含有多少个元素;所以直接将收集过来的RDD进行println的话,会存在问题的;如果RDD很大,全部打印元素的话,内存开销会很大甚至可能会爆掉,为了避免这种情况的发生,我们可以有选择性的打印,比如,我只抓取前一个元素:
三、reduceByKey函数怎么用?
(1)reduce函数我们知道,其是一个Action【执行】函数,对RDD根据某种规则进行元素归并操作的
(2)reduceByKey函数我们就算不知道,也可以通过函数名进行猜测
首先,reduce表示归并元素,那么元素是谁的呢?
其次,通过ByKey我们知道,归并的元素是由key来决定的,那么什么样类型的RDD才有key呢?
然后,我们得出来,只有Pari RDD才可以进行reduceByKey函数的操作
最后,问题来了,reduceByKey是转换函数呢,还是执行函数呢?
(3)reduceByKey使用
scala> var wordSizeRDD = mapRDD.reduceByKey((a,b) => a+b)
wordSizeRDD: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[2] at reduceB
yKey at <console>:25
执行结果:
注意两个地方:
首先,Pari RDD【mapRDD】经过reduceByKey函数后【将key值对应的value值归并求和】得到了一个RDD,注意这个RDD仍然是个Pari RDD【键值对形式】
其次,这个RDD是一个ShuffledRDD,what? 这是不是透漏了什么?
什么是shuffle呢? 我在之前博文中讲解Hadoop的MapReduce的时候,说过这个,中文单词是洗牌的意思,比如,这里,我们把mapRDD比作是一副扑克
洗牌的过程其实就是按照某种规则【(a,b) => a+b】进行归并操作
OK,既然得到的是一个Pari RDD,则我们可以肯定的说,reduceByKey是一个转换函数,没问题吧
(4)将" 洗过的牌 "遍历打印一下
(5)如果还不过瘾的话,我们对上面的RDD【结果】再进行一次Action操作【得到单词词频最高的value值】
拿到map的第二个元素即value值进行max【两两比较取最大值】执行操作,拿到最终的结果【2】
scala> var maxSizeRDD = wordSizeRDD.map(word => word._2).max
maxSizeRDD: Int = 2
执行结果:
(6)反过来,拿到词频数最高的value后,我想知道哪些单词出现的频率数等于这个value,我们不妨来试试
scala> var maxWordRDD = wordSizeRDD.filter(word => word._2 == 2).collect
maxWordRDD: Array[(String, Int)] = Array((Spark,2))
执行结果:
通过两步,我们知道spark单词出现的频率最高,这意味什么呢【直接联想到 == 商业价值中】?
可能Spark比较火,感兴趣的人比较多,也有可能Spark很强大,用的人很多....etc
(7)如果还不过瘾的话,能不能一行代码【一步到位】搞定呢?
scala> var maxWordRDD = wordSizeRDD.reduce((a,b) => if(a._2 > b._2 ) a else b)
maxWordRDD: (String, Int) = (Spark,2)
执行结果:
四、reduceByKey能干嘛?
通过上面的例子,我们知道了reduceByKey函数的用法,注意,它是一个转换函数,区别于reduce函数【Action函数】,且函数的作用对象是一个Pari RDD,因为只有键值对形式的RDD才具有key和value值
最后,它能干什么? 来日方长,自己好好琢磨吧......