4.Action

action操作,是行动算子,是触发spark作业执行的动因。

4.1 foreach

遍历,同scala

4.2collect

是将集群中的数据(rdd对应partition的数据)拉取到dirver(本地),所以我们需要非常慎重的使用这个算子,避免出现OOM的异常,所以一般要拉去的话我们要么在拉取之前进行filter;要么使用下面的take算子来获取对应的数据。

4.3 count

计数算子,略

4.4saveAsTextFile/saveAsHadoopFile/ saveAsHadoopAPIFile

见名知意 ,saveAs ->存储算子。分别是存为text文件,hadoop文件和hadoopapi文件,hadoop和hadoopapi文件区别在于hadoop存储的outputFormat属于hadoop.mapred,hadoopapi属于hadoop.mapreduce.OutputFormat,hadoopapi更新

4.5 take(n)/frist/takeOrdered

take(n)为获取集合中的前n个值i,take(1)=first,如果集合有序,take(n)->top(n),takerOrdered可以重写compare方法实现排序

println("-------take(2)-------------")
retRDD.take(2).foreach(println)
println("-------first-------------")
println(retRDD.first())
println("--------takeOrdered----------------------")
implicit val order = new Ordering[(String, Int)](){
    override def compare(x: (String, Int), y: (String, Int)) = {
        var ret = y._2.compareTo(x._2)
        if(ret == 0) {
            ret = y._1.compareTo(x._1)
        }
        ret
    }
}
retRDD.takeOrdered(5).foreach(println)

4.6 countByKey

countByKey/
        //通过每个key出现的次数
        val k2counts = pairsRDD.countByKey()
        for((k, v) <- k2counts) {
            println(s"${k}---$v")
        }
        //使用groupByKey、reduceByKey、countByKey   --->wordcount
        println("-----------------------------------")
        val gbks = pairsRDD.groupByKey()
//        gbks.foreach(println)
        gbks.map{case (key, values) => (key, values.size)}.foreach(println)

4.7 reduce

reduceByKey是一个transformation,reduce是一个action.作用与reducebykey相同

val sum = pairsRDD.values.reduce(_+_)
println(sum)

5.rdd持久化操作

5.1为什么要有rdd的持久化的操作?

要原因在于,如果我们相对一个RDD进行复用操作的时候,基于RDD的特性,当以rdd通过transformation转化为另外一个rdd的时候,前面的rdd就会被自动释放,此时还想在原来的rdd身上进行其它操作,需要从源头进行数据计算,这样效率自然会降低。为了能够在rdd重用的时候,直接从内存中加载相关数据,所以我们需要缓存算子(persist/cache)将rdd数据持久化到内存等等其它地方。

5.2怎么去实现持久化操作

spark中对RDD的持久化操作,需要通过rdd.persist(StorageLevel)/cache方法来进行实现,使用完毕之后我们可以通过调用rdd.unPersist(blocking:Boolean) (阻塞)

5.3持久化级别(StorageLevel)

持久化级别

解释

MEMORY_ONLY

RDD中所有的数据都会以未经序列化的java对象的格式优先存储在内存中,如果内存不够,剩下的数据不会进行持久化。很容易出现OOM=OutOfMemoryException异常。java的gc频率和对象个数成正比。gc的时候会stop-the-world。

MEMORY_ONLY_SER

和MEMORY_ONLY的操作几乎一致,唯一的区别是在内存中存储的不在是未经序列化的java对象,是序列化之后的数据,rdd经过序列化之后,每一个partition就只有一个字节数组,也就是说一个partition就是一个java对象。

MEMORY_AND_DISK

和MEMORY_ONLY的唯一区别在于,MEMORY_ONLY不会持久化哪些在内存中持久化的数据,MEMORY_AND_DISK会将哪些在内存中保存不下的数据保存到磁盘中。

MEMORY_AND_DISK_SER

就比MEMORY_AND_DISK多了一点,存储的是序列化的java对象

DISK_ONLY

不用

DISK_ONLY_SER

不用

XXXXX_2(MEMORY_ONLY_2等等)

是在上述所有操作的基础之上进行了一个备份。从安全、高可用的角度上考虑,如果备份所消耗的时间,比数据丢失之后从源头重新计算一遍的代价小,我们才考虑使用Xxxx_2。

OFF_HEAP

非堆。上述所有的操作都会使用Spark自身的内存资源,所以为了给计算提供足够的资源,可以将持久化的数据保存到非executor中。常见的OFF_HEAP:Tachyon/Alluxio

5.4如何选择合适的持久化策略

尽量要让所有的操作都在内存中完成,优先尝试MEMORY_ONLY,慎防OOM,如果满足不了;退而求其次,使用MEMORY_ONLY_SER,这个策略比MEMORY_ONLY多了序列化和反序列化的操作,这是主要的性能开销,但是进行反序列化之后的数据都是基于纯内存的计算,所以序列还是蛮高;退而求再次,MEMORY_AND_DISK,此时不要使用这个,既然要写入磁盘,那数据量指定很大,所以首先应该保证在内存存储尽可能多的数据,然后再考虑磁盘,如果保证内存有尽可能多的数据,不就是序列化吗?所以说MEMORY_ONLY_SER搞不定,直接MEMEORY_AND_DISK_SER;DISK_ONLY不用.XXXX_2慎用,主要考虑到的安全和高可用,只有在数据备份花费的时间小于重新计算的时间的情况下我们可以考虑使用这个持久化策略。