Spark-Core-RDD

Spark中最基本的数据抽象是RDD,RDD是弹性分布式数据集(Resilient Distributed DataSet)。
RDD的五大基本特性

  • A list of partitions:一系列的分片
  • A function for computing each split:在每个分片上都有一个函数去迭代/执行/计算它
  • A list of dependencies on other RDDs:RDD之间存在依赖关系
  • Optionally,a Partitioner for key-value RDDs:对于key-value的RDD可指定一个partitioner,告诉它如何分片,常用的有hash,range
  • Optionally,a list of preferred location(s) to compute each split on:要运行的计算/执行最好在哪几个机器上运行

RDD的结构
每个RDD里都会包括分区信息、依赖关系等等的信息,如图所示:
在这里插入图片描述
a,Partitions
Partitions是上面所说的,代表RDD中数据的逻辑结构,每个Partition会映射到某个节点内存或硬盘的一个数据块。

b,SparkContext
SparkContext是所有Spark功能的入口,代表与Spark节点的连接,可以用来创建RDD对象以及在节点中的广播变量等等。一个线程只有一个SparkContext。

c,SparkConf
SparkConf是一些配置信息

d,Partitioner
Partitioner决定了RDD的分区方式,目前两种主流的分区方式:Hash Partitioner和Range Partitioner。Hash就是对Key进行散列分布,Range是按照Key进行排序的分区。

e,Dependencies,
Dependencies记录了RDD的计算过程,即RDD的转换关系。
每个RDD的分区计算后生成的新的RDD的分区对应关系,可以分为窄依赖和宽依赖。

窄依赖是父RDD的分区可以一一对应子RDD的分区,宽依赖是父RDD的每个分区可以被多个子RDD分区使用。

f,CheckPoint
检查点机制,在计算过程种有一些比较耗时的RDD,我们可以将它缓存到磁盘或者HDFS中,标记这个RDD有被检查点处理过,并且清空它们的所有依赖关系。同时,给它新建一个依赖于CheckPoint的依赖关系,CheckPointRDD可以用来从硬盘种读取RDD和生成新的分区信息。
这样做之后,当某个RDD需要错误恢复后,回溯到该RDD,发现它被检查点记录过,就可以直接去硬盘读取该RDD,无需重新计算。

g,Preferred Location
针对每一个分片,都会选择一个最优的位置来计算,数据不动,代码动

h,Storage Level
用来记录RDD持久化存储的级别,常用的有:

  1. MEMORY_ONLY:只存在缓存中,如果内存不够,则不缓存剩余的部分。这是RDD默认的存储级
  2. MEMORY_AND_DISK:缓存在内存中,不够则缓存到磁盘
  3. DISK_ONLY
  4. MEMORY_ONLY_2:与上面功能相同,只不过建立副本

RDD算子

transform算子

1,map算子:RDD.map(func),对RDD中的每一个元素都作用过一次的func函数,生成新的元素会返回一个新的RDD

def map[U: ClassTag](f: T=>U):RDD[U]=withScope{
	val cleanF = sc.clean(f)
	new 
}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Demo_Map {
  def main(args: Array[String]): Unit = {
      //1,SparkContext
    val sc = new SparkContext(new SparkConf().setAppName("Demo_Map").setMaster("local[*]"))
    import scala.collection.mutable._
    val list = ListBuffer[Int](1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 0)
    val lisRDD: RDD[Int] = sc.parallelize(list, 1)
    val mapRDD: RDD[Int] = lisRDD.map(s => s*10)
    mapRDD.foreach(println)
  }
}

2,flatMap:RDD.flatMap(func),对RDD中的每一个元素都作用一次func函数,返回的是0到多个元素的RDD

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object FlatMapRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
      //RDD.flatMap(func)对RDD中的每一个元素都作用一次func函数,返回的是0到多个元素的新的RDD
    //SparkContent
    val context: SparkContext = SparkUtils.getSparkContext("FlatMapRDD")
    //准备数据
    val list:List[String] = List(
      "i have a very big big duck",
      "you are a bag girl"
    )
    //加载
    val listRDD: RDD[String] = context.parallelize(list, 1)  //设置分区数为1
    //flatmap函数
    val flatMapRDD: RDD[String] = listRDD.flatMap(
      line => {
        line.split("\\s+")
      }
    )
    flatMapRDD.foreach(println)
    SparkUtils.close(context)
  }
}

3,filter:RDD.filter(func),对RDD中的每一个元素都会作用一次func的函数,func的函数返回的是boolean,返回为true的留下一个新的RDD
4, union:RDD.union(RDD2):联合RDD1和RDD2中的数据生成一个新的RDD

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object UNIONADD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("UNIONADD")
    //加载数据
    val listRDD1 = context.parallelize(1 to 10, 1)
    val listRDD2 = context.parallelize(11 to 20, 1)
    val unionRDD: RDD[Int] = listRDD1.union(listRDD2)
    unionRDD.foreach(println)
    SparkUtils.close(context)
  }
}

5,distinct:用于RDD去重
笛卡尔乘积是指在数学中,两个集合X和Y的笛卡尔积,表示为X x Y,第一个对象是X的成员,第二个对象是Y的所有可能有序对的其中一个成员。A x B = {(x, y) | x属于A且y属于B}
假设集合A={a, b},B={0,1,2},则两个集合的笛卡尔积为{(a,0), (a,1), (a,2), (b,0), (b,1), (b,2)}
6,join:
Mysql中用于表与表之间的连接,在Spark中用于RDD与RDD之间的连接

  • 交叉连接:A a across join B b;这种操作叫做交叉连接,但是因为会产生笛卡尔积,所以一般不会使用
  • 内连接:A a [inner] join b [on a.id = b.id]:内连接,避免了笛卡尔积。内连接是从结果表中删除与其他被连接表中没有匹配行的所有行。
  • 左外连接:A a left [outer] join B b [on a.id = b.id];左外连接避免了笛卡尔积,左表数据都会查询出来,即使是不满足where条件
  • 右外连接:A a right [outer] join B b [on a.id = b.id];右外连接,避免了笛卡尔积,右表数据都会查询出来,即使不满足where条件
  • 全外连接:A a full [outer] join B b [on a.id = b.id] 避免了笛卡尔积,左右表的数据都会查询出来,即使不满足where条件

Spark中的join

  • 内连接 val rdd3 = rdd1.join(rdd2)
  • 左外连接 val rdd3 = rdd1.leftOuterjoin(rdd2)
  • 右外连接 val rdd3 = rdd1.rightOuterjoin(rdd2)
  • 全外连接 val rdd3 = rdd1.fullOuterjoin(rdd2)
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object JOINRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    //1, SparkContext
    val context: SparkContext = SparkUtils.getSparkContext("JOINRDD")
    //数据
    val boyList = List(
      "1 语文",
      "2 数学",
      "3 英语"
    )
    val girlList = List(
      "1 80",
      "2 90",
      "3 33"
    )
    //加载数据
    val boyRDD: RDD[String] = context.parallelize(boyList)
    val girlRDD: RDD[String] = context.parallelize(girlList)
    //处理boyRDD和grilRDD
    val boysRDD: RDD[(String, String)] = boyRDD.map(
      line => {
        val lines: Array[String] = line.split("\\s+")
        (lines(0), lines(1))
      }
    )
    val girlsRDD: RDD[(String, String)] = girlRDD.map(
      line => {
        val lines: Array[String] = line.split("\\s+")
        (lines(0), lines(1))
      }
    )
    val innerJoinRDD: RDD[(String, (String, String))] = boysRDD.join(girlsRDD)
    innerJoinRDD.foreach(println)
  }
}

7,foreach元组的三种方式

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object JOINRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    //1, SparkContext
    val context: SparkContext = SparkUtils.getSparkContext("JOINRDD")
    //数据
    val boyList = List(
      "1 语文",
      "2 数学",
      "3 英语"
    )
    val girlList = List(
      "1 80",
      "2 90",
      "3 33"
    )
    //加载数据
    val boyRDD: RDD[String] = context.parallelize(boyList)
    val girlRDD: RDD[String] = context.parallelize(girlList)
    //处理boyRDD和grilRDD
    val boysRDD: RDD[(String, String)] = boyRDD.map(
      line => {
        val lines: Array[String] = line.split("\\s+")
        (lines(0), lines(1))
      }
    )
    val girlsRDD: RDD[(String, String)] = girlRDD.map(
      line => {
        val lines: Array[String] = line.split("\\s+")
        (lines(0), lines(1))
      }
    )
    val innerJoinRDD: RDD[(String, (String, String))] = boysRDD.join(girlsRDD)
    //foeach 元组的三种方式

    //1,内部匿名函数
    innerJoinRDD.foreach(
      kv =>{
        println(s"${kv._2._1}=${kv._2._2}")
      }
    )
    //2,使用匹配模式
    innerJoinRDD.foreach(
      kv=>{
        kv match {
          case (id, (name, source)) => println(s"${name}=${source}")
        }
      }
    )
    innerJoinRDD.foreach{
      case (id, (name, source)) => println(s"${name}=${source}")
    }
  }
}

8,groupByKey:RDD.groupByKey(),按照key进行分组,获取到[K,Iterable[V]],这是一个shuffle dependies,但是这个不建议在实际生产中使用,因为它不会局部聚合。

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object GROUPBUKEYRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("GROUPBUKEYRDD")
    //2. 数据:id,name,age,class
    val stuList = List(
      "1 令狐冲 22 华山派",
      "2 岳不群 38 华山派",
      "3 虚竹 33 逍遥派",
      "4 乔峰 40 丐帮",
      "5 黄蓉 33 桃花岛",
      "6 杨过 11 古墓派",
      "7 小龙女 12 古墓派",
      "8 郭靖 23 丐帮" )
    val stuRDD: RDD[String] = context.parallelize(stuList)
    val stusRDD: RDD[(String, (String, String, String))] = stuRDD.map(
      line => {
        val lines: Array[String] = line.split("\\s+")
        (lines(3), (lines(0), lines(1), lines(2)))
      }
    )
    val gbkRDD: RDD[(String, Iterable[(String, String, String)])] = stusRDD.groupByKey()
    gbkRDD.foreach(println)
    //释放资源
    SparkUtils.close(context)
  }
}

9,reduceByKey:def reduceByKey(func:(v1, v2)=>v):RDD[(K,V)],V1:表示之前元素的集合,V2当前元素

import org.apache.spark.{SparkConf, SparkContext}

object WordCount extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    //1,获取到编程入口SparkContext
    val sc = new SparkContext(
      new SparkConf()
        .setAppName("WordCount")
        .setMaster("local[*]")
    )
    sc.textFile("D:\\tmp\\test.txt").flatMap(_.split("\\s+")).map((_, 1)).reduceByKey(_+_)
      .foreach(println)
    //释放资源
    sc.stop()
  }
}

11,mapPartitions(func):map算子批处理,map是每个元素就调用一次func,而mapPartitions是一个分区调用一次func。性能比map要高,注意OOM,因为它一次性要加载一个分区中所有的数据。

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object mapPartitionsRDD extends LoggerTrait {
   def main(args: Array[String]): Unit = {
     //SparkContext
     val context: SparkContext = SparkUtils.getSparkContext("mapPartitionsRDD")
     //数据
     val list = 1 to 10
     //加载
     val listRDD: RDD[Int] = context.parallelize(list, 1)
     //map
     val mapRDD: RDD[Int] = listRDD.mapPartitions(_.map(_*10))
     mapRDD.foreach(println)
     SparkUtils.close(context)
   }
}

12,coalesce(numPartitions:Int, shuffle:Boolean):RDD[T],repartition(numPartitions:Int):两个都是重新分区,repartition是coalesce接口中shuffle为true时的简易实现。coalesce中,如果Shuffle为false时,即时传入的参数大于现有的分区数目,RDD的分区不变。也就是说,不经过shufle

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object PartitionsRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    //SparkContext
    val context: SparkContext = SparkUtils.getSparkContext("PartitionsRDD")
    //数据
    val list = 1 to 10
    //加载
    val listRDD: RDD[Int] = context.parallelize(list, 1) //设置分区数为1
    println("分区数目0:" + listRDD.getNumPartitions)
    val rdd1: RDD[Int] = listRDD.repartition(10)
    println("分区数目1:" + rdd1.getNumPartitions)
    val rdd2: RDD[Int] = listRDD.coalesce(1)
    println("分区数目2:" + rdd2.getNumPartitions)
    SparkUtils.close(context)
  }
}

排序
使用case class继承Ordered特质重写compare方法,实现排序。class class的好处:
1,自动序列化
2,自动重写了toString
3,case class创建对象,不需要New

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable.ListBuffer

object Demo_FlatMap {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setAppName("Demo_FlatMap").setMaster("local[*]"))
    val rdd: RDD[String] = sc.parallelize(List("iphone5 1000 20", "iphone6 2000 50",
      "iphone7 2000 100", "iphone11 5000 50"))
    rdd.map(x=>{
      //按照空格拆分
      val strings: Array[String] = x.split("\\s+")
      val name = strings(0)
      val price = strings(1).toDouble
      val acount = strings(2).toInt
      (name, price, acount)
    }).sortBy(x=>ProduceInfoV2(x._1.toString, x._2.toDouble,   x._3)).collect().foreach(println)   //sortBy与sortByKey一样,都是分区内排序,必须使用collect
    sc.stop()
  }
}

case class ProduceInfoV2(val name:String, val price: Double, val amount:Int) extends Ordered[ProduceInfoV2]{
  override def compare(that: ProduceInfoV2) = {
    this.amount - that.amount
  }
}

Action算子
所有的action算子都是作用在RDD上的,并且在RDD的Partition上执行,主要作用是驱动我们的RDD,Transform算子是懒加载的,如果没有Action算子驱动,RDD是不执行的。

1,count:统计RDD中的元素的个数

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object CountRDD {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("CountRDD")
    val listRDD: RDD[Int] = context.parallelize(1 to 100)
    //context.textFile("") 从文件中加载,一行一行加载到一个RDD中
    //context.parallelize() 从序列中加载,一行一行加载到一个RDD中
    println(listRDD.count())
  }
}

2,take(n):返回RDD中的前n个元素,可以用来实现topn分析

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object TakeRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("TakeRDD")
    val listRDD: RDD[Int] = context.parallelize(1 to 100)
    val top10: Array[Int] = listRDD.take(10)
    println(top10.mkString(","))
    SparkUtils.close(context)
  }
}

3,first:返回第一个元素

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object FirstRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("FirstRDD")
    val listRDD: RDD[Int] = context.parallelize(1 to 100)
    val first: Int = listRDD.first()
    println(first)
    SparkUtils.close(context)
  }
}

4,reduce:reduce是一个action算子,它的作用也是聚合,返回的是一个数值。

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object ReduceRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("ReduceRDD")
    val list = List( ("name", "李熙"), ("age", "34"), ("sex", "male"), ("salary", "1"), ("name", "rocklee") )
    val listRDD: RDD[(String, String)] = context.parallelize(list)
    val res: (String, String) = listRDD.reduce {
      case ((k1, v1), (k2, v2)) => {
        (k1 + "_" + k2, v1 + v2)
      }
    }
    println(res)
    SparkUtils.close(context)
  }
}

5,countByKey:统计key的个数

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object countByKeyRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("countByKeyRDD")
    val list = List( ("name", "李熙"), ("age", "34"), ("sex", "male"), ("salary", "1"), ("name", "rocklee") )
    val listRDD: RDD[(String, String)] = context.parallelize(list)
    val map: collection.Map[String, Long] = listRDD.countByKey()
    println(map.mkString(","))
    SparkUtils.close(context)
  }
}

6,saveAsTextFile/saveAsObjectFile/saveAsSequence/saveAsSequence/saveAsHadoopFile

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

import scala.reflect.ClassTag

object SaveAsRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val sc = SparkUtils.getSparkContext("Demo4_Count")
    val list = List( Person(1, "李熙", 34), Person(2, "李东", 38), Person(3, "李楠", 18), Person(4, "a", 23), Person(5, "b", 23) )
    val lineRDD: RDD[Person] = sc.parallelize(list, 1)
    val sortRDD: RDD[Person] = lineRDD.sortBy(person => person)(
      new Ordering[Person]() {
        override def compare(x: Person, y: Person): Int = {
          if (x.age == y.age) {
            return y.name.compareTo(x.name)
          } else {
            return y.age - x.age
          }
        }
      },
      ClassTag.AnyRef.asInstanceOf[ClassTag[Person]]
    )
    sortRDD.saveAsTextFile("D://tmp/one")

    SparkUtils.close(sc )
  }
}
case class Person(id:Int, name:String, age:Int)

持久化策略
persist:保证数据的安全性
cache:将不同分区的数据暂时缓存下来,避免额外的网络开销

持久化策略含义
MEMORY_ONLY(默认)RDD中的数据,以没有经过序列化的java对象为例,存放在经过序列化的java对象为格式,存在内存中,如果内存不足,也不会存储在磁盘中,这种效率最高,但是对内存的要求也高
MEMORY_ONLY_SER比MEMORY_ONLY多了一个序列化的功能,保存在内存中的数据是经过序列化的,数据以字节数组的方式存储
MEMORY_AND_DISK比MEMORY_ONLY多了一个功能,如果内存不足,会讲数据存储在磁盘中
MEMORY_AND_DISK_SER比MEMORY_ONLY多了序列化和存储在磁盘的功能
DISK_ONLY所有数据都保存在磁盘中,这种效率最差,一般不使用
xxx_2对应上述的策略,多了一个副本策略;如MEMORY_ONLY_2,比上述的测录多了一个副本的功能,2表示两个副本。因为需要添加备份,所以性能相对降低,但是容错性增强。
HEAP_OFF使用非spark内存来操作数据,如堆外内存,如HBase、redis
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel

object PersistRDD extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("PersistRDD")
    val linesRDD: RDD[String] = context.parallelize(List( "1 F 180", "2 M 178", "3 M 174", "4 F 165", "5 M 182", "6 F 160", "7 F 150"))
    linesRDD.persist(StorageLevel.DISK_ONLY)
    linesRDD.unpersist()
    SparkUtils.close(context)
  }
}

共享变量

broadcast 广播变量:多个任务共享同一变量,从而减少不必要的网络开销

import org.apache.spark.SparkContext
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD

object BroadCast {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("BroadCast")
    val genderMap = Map( "0" -> "小姐姐", "1" -> "小果果" )
    //广播
    val genderBC: Broadcast[Map[String, String]] = context.broadcast(genderMap)
    val stuRDD: RDD[Student] = context.parallelize(List( Student(1, "吴奇隆", "1"), Student(2, "西门大官人", "1"), Student(3, "大郎", "1"), Student(4, "金莲", "0"), Student(5, "苍老师", "0"), Student(6, "志玲姐姐", "2") ))
    val resRDD: RDD[Student] = stuRDD.map(stu => {
      val gender: String = stu.gender
      Student(stu.id, stu.name, genderBC.value.getOrElse(gender, "ladypiapia"))
    })
    resRDD.foreach(println)
  }
}
case class Student(id:Int, name:String, gender:String)

累加器:accumulator

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.util.LongAccumulator

object Accumulator extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("Accumulator")
    val list = List( "Spark powers a stack of libraries including SQL and DataFrames, MLlib for machine learning, GraphX, and Spark Streaming. You can combine these libraries seamlessly in the same application." )
    val lineRDD: RDD[String] = context.parallelize(list)
    val wordRDD: RDD[String] = lineRDD.flatMap(_.split("\\s+"))
    val accumulator: LongAccumulator = context.longAccumulator //获取累加器
    val resRDD: RDD[(String, Int)] = wordRDD.map(word => {
      if (word == "Spark") accumulator.add(1) //累加器加1
      (word, 1)
    }).reduceByKey(_ + _)
    resRDD.foreach(println)
    println("Spark :" + accumulator.value)
  }
}

自定义累加器

import org.apache.spark.util.AccumulatorV2

import scala.collection.mutable

class MyAccumlator extends AccumulatorV2[String, Map[String, Long]]{

  private var map = mutable.Map[String, Long]() //累加器存放数据的数据结构
  /**
   * 当前的累加器是否初始化值
   * @return
   */
  override def isZero = true

  /**
   * 拷贝一个累加器的副本
   * @return
   */
  override def copy(): AccumulatorV2[String, Map[String, Long]] = {
    val accu = new MyAccumlator
    accu.map = this.map
    accu
  }

  /**
   * 数据进行重置
   */
  override def reset()={
    map.clear()
  }

  /**
   * 将元素添加到累加器中
   * @param word
   */
  override def add(word: String)={
    map.put(word, map.getOrElse(word, 0L).asInstanceOf[Long]+1L)
  }

  /**
   * 将多个分区相同的key的value进行聚合
   * @param other
   */
  override def merge(other: AccumulatorV2[String, Map[String, Long]])={
    other.value.foreach{
      case (word, count) =>{
        map.put(word, map.getOrElse(word, 0L).asInstanceOf[Long]+count)
      }
    }
  }

  override def value={
    map.toMap
  }
}

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object My_Accumulator extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("My_Accumulator")
    val list = List( "Spark powers a stack of libraries including SQL and DataFrames, MLlib for machine learning, GraphX, and Spark Streaming. You can combine these libraries seamlessly in the same application." )
    val lineRDD: RDD[String] = context.parallelize(list)
    val wordRDD: RDD[String] = lineRDD.flatMap(_.split("\\s+"))
    //注册自己的累加器
    val myaccu = new MyAccumlator
    context.register(myaccu, "success")
    val resRDD: RDD[(String, Int)] = wordRDD.map(
      word => {
        if (word == "Spark")
          myaccu.add(word) //累加器加一
        (word, 1)
      }
    ).reduceByKey(_ + _)
    resRDD.foreach(println)
    println("Spark" + myaccu.value)
  }
}

高阶排序
1, 分组TopN

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

import scala.collection.mutable

object topN extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("topN")
    val list = List( "金,常,100", "玉,汪,99", "金,吴,99", "玉,王,100", "金,王,67", "玉,王,66", "金,安,89", "玉,李,88", "金,张,57", "玉,阎,44" )
    val lineRDD: RDD[String] = context.parallelize(list)
    val mapRDD: RDD[(String, (String, String))] = lineRDD.map(
      line => {
        val lines: Array[String] = line.split(",")
        (lines(0), (lines(1), lines(2)))
      }
    )
    val gbkRDD: RDD[(String, Iterable[(String, String)])] = mapRDD.groupByKey()
    val top3RDD: RDD[(String, mutable.TreeSet[(String, String)])] = gbkRDD.map {
      case (course, infos) => {
        //创建一个Treeset,用于排序,按照成绩降序排序
        val topN: mutable.TreeSet[(String, String)] = mutable.TreeSet[(String, String)]()(
          new Ordering[(String, String)] {
            override def compare(x: (String, String), y: (String, String)) = {
              x._2.toInt - y._2.toInt
            }
          }
        )
        for (info <- infos) {
          topN.add(info)
        }
        (course, topN.take(3))
      }
    }
    top3RDD.foreach(println)
    SparkUtils.close(context)
  }
}

2,topN

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

import scala.reflect.ClassTag

object TopNFirst extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("TopNFirst")
    val list = List(
        Person(1, "any", 34),
        Person(2, "zz", 34),
        Person(3, "jj", 34),
        Person(4, "FF", 100),
        Person(5, "GG", 23)
    )
    val lineRDD: RDD[Person] = context.parallelize(list)
    val sortRDD: RDD[Person] = lineRDD.sortBy(
      person => person
    )(
      new Ordering[Person]() {
        override def compare(x: Person, y: Person): Int = {
          if (x.age == y.age) {
            return y.name.compareTo(x.name)
          } else {
            return y.age - x.age
          }
        }
      },
      ClassTag.AnyRef.asInstanceOf[ClassTag[Person]]
    )
    sortRDD.foreach(println)
  }
}
case class Person(id: Int, name: String, age: Int)

分区器

1,预定义分区器
HashPartition:这是spark默认的分区器,它会按照RDD中Key的值进行hashCode(),对这个值取模分区数,这个值就是分区号。
RangePartitioner:范围分区器,对应RDD的排序算子,如:sortBy默认使用的就是RangePartitioner。它可以对RDD整个数据范围区域进行重新排序。
2,自定义分区器

import org.apache.spark.{Partitioner, SparkContext}
import org.apache.spark.rdd.RDD

object CustomerPartitioner extends LoggerTrait {
  def main(args: Array[String]): Unit = {
    val context: SparkContext = SparkUtils.getSparkContext("CustomerPartitioner")
    val lineRDD: RDD[Int] = context.parallelize(1 to 10, 5)
    //该算子的作用是将对应的每条数据和索引绑定在一起,形成一个kv
    val zipIndexRDD: RDD[(Int, Long)] = lineRDD.zipWithIndex()
    //函数打印出分区好
    val func = (index:Int, iter:Iterator[(Int, Long)])=>{
      iter.map(tx =>"[partID: " +index + ", value:" + tx + "]")
    }
    //将所有的分区的数据聚合到一个数组中,collect是一个行动算子,作用就是将不同分区中的数据聚合到一个数组中
    val arrs: Array[String] = zipIndexRDD.mapPartitionsWithIndex(func).collect()
    arrs.foreach(print)
    print("-------------------------------------------------------------------------------")
    val customeRDD: RDD[(Int, Long)] = zipIndexRDD.partitionBy(new CustomerPartitioner(5))
    val arr2: Array[String] = customeRDD.mapPartitionsWithIndex(func).collect()
    arr2.foreach(print)
    SparkUtils.close(context)
  }
}
class CustomerPartitioner(numPartition:Int) extends Partitioner{
  override def numPartitions = {
    numPartition
  }

  override def getPartition(key: Any) = {
    key.toString.hashCode % numPartition
  }
}

大数据常用的数据格式
常见的文件存储格式有行式存储,列式存储和混合式存储。在大数据系统中,列式存储和混合式存储更常见。对于HDFS这种文件存储系统,其实并不关心存储的是什么形式的存储文件,只是在解析数据时,需要知道是什么类型的文件。例如,Hive对RCFile和ORCFile的支持在于,Hive只要知道文件的存储方式,就能正确解析数据并查询。
1,行式存储
广泛使用于主流关系型数据库及HDFS中,优势在于当需要按行遍历时效率很高。缺点是当不需要所有字段时,也需要读取所有字段,浪费IO和内存,以及整行压缩效率较低。例,csv。
2,列式存储
简单的列式存储是将数据按所有列进行垂直划分,每一列数据连续存储在一起。当需要所有列时,使用指针将不同的列拼接在一起。这样解决了行式存储的问题,但是不同的块存储在不同的节点上,拼接时会导致大量的网络传输。
实际中更多的是使用column-family,即将几个经常同时使用的列分为一组并连续存储,成为一个colmun-family;
3,混合存储
column-family还是会导致一些网络传输,而混合式存储可以解决这些问题。混合式存储首先将记录表按照行进行分组,若干行划分为一组(row group)。而对于每组内的所有记录,在实际存储时将同一列内容连续存储在一起。这样一方面保证同一行的字段一定在同一台机器上,另一方面向列式存储,可以避免读取无关列的数据,也可以对不同的列应用不同的压缩算法。例,RCFile
ORCFile是对RCFile的优化,ORCFile增加了对数据行组的index和统计信息,以提高查询效率。

由上面的说明可见,对于简单的文件格式,例如csv,仅仅包含数据本身。对于复杂的文件结构,则不仅仅包含数据本身,还包含i元数据信息。

Avro和Parquet
Avro和Parquet都是Apache的项目。Parquet是非常流行的列式存储文件格式,也是二进制文件。Avro原本是一个RPC框架,而RPC框架必然涉及序列化的问题。因此Avro提供了一种紧凑的二进制文件格式。avro是行式存储文件格式,也是二进制文件。

列式存储和混合式存储的文件格式,不仅可以提高数据处理的效率,也可以提高压缩效率。二进制文件相比其他文件的网络传输速度也更快。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值