Spark

Spark

spark与mr对比

在这里插入图片描述

mapreduce中多个job需要多次读写磁盘。而spark比mapreduce快,两个mapreduce之间不需要落地(shuffle是会落地到磁盘的),省去了多次读写磁盘,数据加载在内存中,每次直接读写内存就快了,但当数据量很大时(达到10T、1P),会内存溢出,这也是spark没有mr稳定的原因之一。

spark比mr快:

  1. spark可以尽量将数据放在内存中计算(cache)

  2. 多个shuffle中间结果不需要落地

  3. spark是粗粒度资源申请(在任务执行之前会将所需要的资源全部申请下来),

    而mr是细粒度资源申请,每一个task执行的时候单独申请资源。

RDD五大特性

RDD:弹性的分布式数据集。

  1. RDD是由一组分区组成,默认一个切片对应一个分区;
  2. 算子实际是作用在每一个分区上面,每一个分区由一个task处理;
  3. RDD之间有一系列的依赖关系,后面一个RDD依赖前一个RDD。依赖分为宽依赖(有shuffle)和窄依赖(没有shuffle),在宽依赖的位置切分不同的stage:宽依赖之前是一个stage,宽依赖之后是一个stage。stage相当于mr中的map端或reduce端,stage是一组并行计算的task。
  4. 分区类的算子只能作用在KV格式的RDD上;
  5. Spark为task的计算提供了最佳的计算位置,移动计算而不是移动数据,会尽量将task发送到数据所在节点执行。

注意:RDD默认不保存数据,数据只是流过每一个RDD。

在这里插入图片描述

WordCount

  • 本地运行
package com.shujia.spark

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

object Demo1WordCount {

  def main(args: Array[String]): Unit = {

    /** 创建spark运行环境
      */
    //配置文件对象
    val conf = new SparkConf()
    //会在yarn中显示的名称
    conf.setAppName("wc")
    //运行方式,local:本地运行,
    conf.setMaster("local")
    //构建spark 上下文对象。	SparkContext是spark的入口
    val sc: SparkContext = new SparkContext(conf)


    //1、读取数据
    //RDD: 弹性的分布式数据集(可以看作是scala中的一个集合来使用)
    val linesRDD: RDD[String] = sc.textFile("spark/data/words")

    //2、将每一行的多个单词拆分出来
    //在spark中数据处理的方法一般称为算子,算子处理完了之后会返回一个新的RDD
    val wordsRDD: RDD[String] = linesRDD.flatMap(line => line.split(","))

    //3、按照单词进行分组,在底层会产生shuffle
    val groupRDD: RDD[(String, Iterable[String])] = wordsRDD.groupBy(word => word)

    //4、统计单词的数量
    val countRDD: RDD[String] = groupRDD.map(kv => {
      val word: String = kv._1

      //一个组内所有单词
      val iter: Iterable[String] = kv._2	//Iterable是迭代器

      //单词的数量
      val count: Int = iter.size

      //返回
      word + "," + count
    })

    //5、保存数据(如果目录已存在会报错)
    countRDD.saveAsTextFile("spark/data/count")

  }

}
  • 将spark代码提交到服务器中运行
整合在yarn上
在公司一般不适用standalone模式,因为公司一般已经有yarn,不需要搞两个资源管理框架

1、spark	on yarn client模式:  日志在本地输出,一般用于上线前测试
spark-submit --class org.apache.spark.examples.SparkPi --master yarn-client --executor-memory 512M --num-executors 2 spark-examples_2.11-2.4.5.jar 100

2、spark on yarn cluster模式: 上线使用,不会再本地打印日志。 减少io
spark-submit --class org.apache.spark.examples.SparkPi --master yarn-cluster --executor-memory 512m --num-executors 2 --executor-cores 1 spark-examples_2.11-2.4.5.jar 100

获取yarn程序执行日志,执行成功之后才能获取到
yarn logs -applicationId application_1560967444524_0003
package com.shujia.spark

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Demo2WordCountSubmit {

  def main(args: Array[String]): Unit = {

    /**
      *
      * 将spark代码提交到服务器中运行
      * 1、删除本地   conf.setMaster("local")
      * 2、修改输入输出路径
      *
      * 将项目打包上传到服务器
      * 提交任务
      * spark-submit --class  com.shujia.spark.Demo2WordCountSubmit --master yarn-client   spark-1.0.jar
      *
      */


    //创建spark运行环境:
    //配置文件对象
    val conf = new SparkConf()
    //会在yarn中显示的名称
    conf.setAppName("wc")
    //运行方式,local:本地运行,
    //    conf.setMaster("local")
    //构建spark 上下文对象。SparkContext是spark的入口
    val sc: SparkContext = new SparkContext(conf)


    //1、读取数据
    //将读取文件的路径修改为hdfs的路径
    val linesRDD: RDD[String] = sc.textFile("/data/words")//默认最小两个分区

    //2、将每一行的多个单词拆分出来
    //在spark中数据处理的方法一般称为算子,算子处理完了之后会返回一个新的RDD
    val wordsRDD: RDD[String] = linesRDD.flatMap(line => line.split(","))

    //3、按照单词进行分组,在底层会产生shuffle
    val groupRDD: RDD[(String, Iterable[String])] = wordsRDD.groupBy(word => word)

    //4、统计单词的数量
    val countRDD: RDD[String] = groupRDD.map(kv => {
      val word: String = kv._1

      //一个组内所有单词
      val iter: Iterable[String] = kv._2

      //单词的数量
      val count: Int = iter.size

      //返回
      word + "," + count
    })

    ///删除输出路径
    //使用hadoop的api
    val configuration = new Configuration()
    val fileSystem: FileSystem = FileSystem.get(configuration)

    if (fileSystem.exists(new Path("/data/count"))) {
      fileSystem.delete(new Path("/data/count"), true)
    }

    //5、保存数据
    countRDD.saveAsTextFile("/data/count")
  }

}

算子

在spark中数据处理的方法一般称为算子,算子处理完了之后会返回一个新的RDD。

  • 转换算子:从一个rdd转换成另一个rdd,是rdd之间的转换

    ​ 转换算子是懒执行,需要action算子触发执行。

  • 操作算子:触发任务执行,每一个action算子都会触发一个job

构建RDD的方式:

  1. 读取文件
  2. 基于scala集合构建RDD

  • map
package com.shujia.spark

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

object Demo3Map {
  def main(args: Array[String]): Unit = {

    /**
      * 转换算子:从一个Rdd转换成另一个rdd,是rdd之间的转换,
      * 		转换算子是懒执行,需要action算子触发执行
      *
      * 操作算子: 触发任务执行,每一个action算子都会触发一个job
      *
      */
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)


    /**
      * 构建RDD的方式
      * 1、读取文件
      * 2、基于scala集合构建RDD
      * 
      * parallelize方法可以将集合变成一个RDD
      */
    val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8))

    println("map之前")

    //转换算子是懒执行,需要action算子触发执行,每一个action算子都会触发执行一次

    /**
      * map算子:是一个转换算子,从一个RDD转换成另一个RDD
      *
      * 处理完之后数据量行数不变
      *
      */					//将rdd1的数据一个一个传给后面的函数
    val rdd2: RDD[String] = rdd1.map((i: Int) => {// U 可以自定义
      println("map" + i)
      i.toString
    })

    println("map之后")


    //操作算子。触发job运行,每一个操作算子都会触发一个job。所以测试时打印在后面了
    rdd2.foreach(println)
    rdd2.foreach(println)

    while (true) {//为了在网页上面看job任务
    }

  }

}
  • mapPartitions
package com.shujia.spark

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

object Demo4MapPartition {
  def main(args: Array[String]): Unit = {
      
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    /**
      * mapPartitions: 一次处理一个分区的数据,需要返回一个迭代器或者集合
      *
      * mapPartitionsWithIndex: 多了一个分区编号
      *
      */      
    val rdd1: RDD[String] = sc.textFile("spark/data/words")

    val rdd2: RDD[String] = rdd1.mapPartitions((iter: Iterator[String]) => {
      println("一个分区")
      //iter  
      iter.flatMap(line => line.split(","))
    })

    //rdd2.foreach(println)

    val rdd3: RDD[String] = rdd1.mapPartitionsWithIndex((index: Int, iter: Iterator[String]) => {
      println("分区编号:" + index)
      iter
    })

    rdd3.foreach(println)
  }

}
  • filter
package com.shujia.spark

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

object DEmo5Filter {
  def main(args: Array[String]): Unit = {
      
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    /**
      * filter: 过滤数据,函数返回true保留数据,函数返回false 过滤数据
      */
    //基于集合构建RDD
    val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 0))

    val rdd2: RDD[Int] = rdd1.filter((i: Int) => i % 2 == 1)

    rdd2.foreach(println)
  }

}
  • flatMap
package com.shujia.spark

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

object Demo6FlatMap {
  def main(args: Array[String]): Unit = {
      
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    /**
      * flatMap: 将一行数据拆分成多行数据
      */      
    val rdd1: RDD[String] = sc.parallelize(List("java,spark,java", "hadoop,hive,hbase"))

    val rdd2: RDD[String] = rdd1.flatMap((line: String) => line.split(","))

    rdd2.foreach(println)
  }

}
  • sample
package com.shujia.spark

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

object Demo7Sample {
  def main(args: Array[String]): Unit = {
      
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    /**
      * sample:抽样
      */      
    val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")

    val rdd2: RDD[String] = rdd1.sample(true, 0.1)

    rdd2.foreach(println)
  }

}
  • groupBy
package com.shujia.spark

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

object Demo8GroupBy {
  def main(args: Array[String]): Unit = {
      
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    /**
      * groupBy: 指定一个列进行分组
      */      
    val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")

    val rdd2: RDD[(String, Iterable[String])] = rdd1.groupBy(line => line.split(",")(4))

    //    rdd2.foreach(println)

    //将rdd转换成kv格式,就可以使用groupByKey了
    val rdd3: RDD[(String, String)] = rdd1.map(line => {
      val clazz: String = line.split(",")(4)
      (clazz, line)
    })

    /**
      * groupByKey: 通过key进行分组,将通过一个班级的学生分到同一个组内
      * 			只能作用在kv格式的rdd上
      */					//就是通过key进行分组,所以方法里面不用穿东西了
    val rdd4: RDD[(String, Iterable[String])] = rdd3.groupByKey()

    rdd4.foreach(println)
  }
}
  • reduceByKey
package com.shujia.spark

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

object Demo9RedueByKey {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

  
    val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")

    val rdd2: RDD[(String, Int)] = rdd1.map(line => {
      val clazz: String = line.split(",")(4)
      (clazz, 1)
    })
      
    //统计班级的人数
    val rdd3: RDD[(String, Iterable[Int])] = rdd2.groupByKey()

    val rdd4: RDD[(String, Int)] = rdd3.map(kv => {
      val clazz: String = kv._1

      //班级的人数
      val count: Int = kv._2.sum

      (clazz, count)
    })

//    rdd4.foreach(println)
    /**
      * reduceByKey: 通过key对value进行聚合,一般用于加和计算,需要传入一个聚合函数
      * 效率比groupBykey高,reduceBykey可以在map端进行预聚合, (可以减少shuffle过程中传输的数据量)
      *
      * reduceByKey只能用于聚合操作,功能比groupBykeu弱,性能更高
      */
    val rdd5: RDD[(String, Int)] = rdd2.reduceByKey((x, y) => x + y)

    rdd5.foreach(println)


    //简写,函数的参数只使用了一次,可以通过下划线代替
    val rdd6: RDD[(String, Int)] = rdd2.reduceByKey(_ + _)

    while (true) {
    }

  }

}

在这里插入图片描述

  • union
package com.shujia.spark

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

object Demo10Union {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)


    val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 3, 4))
    val rdd2: RDD[Int] = sc.parallelize(List(5, 6, 7, 8, 9))

    /**
      * getNumPartitions:获取rdd分区数
      * 	不是一个算子,只是一个普通的方法
      */
    println("rdd1:" + rdd1.getNumPartitions)
    println("rdd2:" + rdd2.getNumPartitions)


    /**
      * union:将两个rdd合并成一个,在屋里层面没有合并,只是在代码层面合并
      *
      * 两个rdd的类型必须一致
      *
      * 新的rdd分区数量等于合并之前rdd分区数量之和
      */
    val unionRDD: RDD[Int] = rdd1.union(rdd2)
    println("unionRDD:" + unionRDD.getNumPartitions)

    unionRDD.foreach(println)

  }

}
  • join
package com.shujia.spark

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

object Demo11Join {
  def main(args: Array[String]): Unit = {
      
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    val nameRDD: RDD[(String, String)] = sc.parallelize(List(("001", "张三"), ("002", "李四"), ("003", "王五"), ("004", "小伟")))

    val ageRDD: RDD[(String, Int)] = sc.parallelize(List(("001", 23), ("002", 24), ("003", 25)))

    /**
      * join: 默认是inner join 。 两个rdd都必须是kv格式
      */
    val joinRDD: RDD[(String, (String, Int))] = nameRDD.join(ageRDD)

    //join之后整理数据
    val rdd3: RDD[(String, String, Int)] = joinRDD.map(kv => {
      val id: String = kv._1
      val name: String = kv._2._1
      val age: Int = kv._2._2
      (id, name, age)
    })


    //如果rdd结构比较复杂,可以通过case 简写,提高可读性
    val rdd4: RDD[(String, String, Int)] = joinRDD.map {
      //一行一行对rdd的数据进行匹配(匹配类型)。格式匹配上了就会执行
      case (id: String, (name: String, age: Int)) => {
        (id, name, age)
      }
    }
    // rdd4.foreach(println)

    val leftJoinRDD: RDD[(String, (String, Option[Int]))] = nameRDD.leftOuterJoin(ageRDD)
    val rdd5: RDD[(String, String, Int)] = leftJoinRDD.map(kv => {
      val id: String = kv._1
      val name: String = kv._2._1

      val age: Int = kv._2._2.getOrElse(0)

      (id, name, age)
    })


    val rdd6: RDD[(String, String, Int)] = leftJoinRDD.map {
      //匹配关联成功的情况
      case (id: String, (name: String, Some(age))) => {
        (id, name, age)
      }
      //匹配没有关联上的情况
      case (id: String, (name: String, None)) => {
        (id, name, 0)
      }
    }
    rdd6.foreach(println)

  }

}
  • mapValues
package com.shujia.spark

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

object Demo12MapValues {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    val ageRDD: RDD[(String, Int)] = sc.parallelize(List(("001", 23), ("002", 24), ("003", 25)))

    /**
      *
      * mapValues:处理kv格式的rdd,  处理value key 不变
      */
    val rdd1: RDD[(String, Int)] = ageRDD.mapValues(value => value + 1)
    rdd1.foreach(println)
  }

}
  • sortBy
package com.shujia.spark

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

object Demo13Sort {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)


    val ageRDD: RDD[(String, Int)] = sc.parallelize(List(("001", 231), ("002", 24), ("003", 25)))

    /**
      * sortBy: 指定一个排序的列, 默认是升序(true)
      * sortByKey : 通过key进行排序
      */
    val sortRDD: RDD[(String, Int)] = ageRDD.sortBy(kv => kv._2, ascending = false)
    sortRDD.foreach(println)
  }
}
  • 操作算子

  • 每一个action算子都会独立触发一个job任务

package com.shujia.spark

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

object Demo14Action {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    val rdd1: RDD[String] = sc.textFile("spark/data/students.txt")

    /**
      * count: 统计rdd的行数
      * 		会触发任务执行
      */
    println(rdd1.count())

    /**
      * foreach:遍历数据,一般只能用于本地测试.	将前面的数据一行一行传递后面打印
      * foreachPartition: 一次处理一个分区的数据, 一般用于将数据保存到外部数据的时候
      */
    rdd1.foreach(println)

    rdd1.foreachPartition(iter => {
      println("一个分区")
      iter.foreach(println)
    })


    /**
      * collect: 将rdd转换成集合,将rdd的数据拉取到内存中,如果rdd数据量比较大会导致内存溢出
      *
      * 会内存溢出,这也是spark没有mapreduce稳定的原因
      */
    val array: Array[String] = rdd1.collect()
    println(array)


    /**
      * reduce: 全局聚合
      *
      * select sum(age) from student
      */
    val ages: RDD[Int] = rdd1.map(line => line.split(",")(2).toInt)
    val sumAge: Int = ages.reduce((x, y) => x + y)
    println(sumAge)


    /**
      * save:将数据保存到hdfs中,如果输出目录已存在会报错
      */
    rdd1.saveAsTextFile("spark/data/out1")

    while (true) {
    }

  }
}
  • PI
package com.shujia.spark

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

import scala.collection.immutable
import scala.util.Random

object Demo15PI {
  def main(args: Array[String]): Unit = {

    //快速生成一个大集合
    val list: immutable.Seq[Int] = 0 to 10000000

	//构建spark环境
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)


    //将scala集合转换成RDD
    val dataRDD: RDD[Int] = sc.parallelize(list)


    //随机生成点
    val pointRDD: RDD[(Double, Double)] = dataRDD.map(i => {

      //随机生成点(x,y)  范围是-1,1
      val x: Double = Random.nextDouble() * 2 - 1
      val y: Double = Random.nextDouble() * 2 - 1

      (x, y)
    })

    //取出园内的点
    val filterRDD: RDD[(Double, Double)] = pointRDD.filter {
      case (x: Double, y: Double) => {
        x * x + y * y <= 1
      }
    }

    //通过公式计算pi:
    val pi: Double = filterRDD.count().toDouble / list.length * 4
    println("pi = " + pi)
  }

}

缓存

  • Cache

spark的rdd中默认不保存数据,当对rdd使用多次时可以将rdd进行缓存

MEMORY_ONLY:数据量不大,内存足够。

MEMORY_AND_DISK_SER:数据量很大,超过内存限制。

在这里插入图片描述

package com.shujia.spark

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

object Demo16Cache {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)

    val student: RDD[String] = sc.textFile("spark/data/students.txt")

    val mapStudent: RDD[String] = student.map(line => {
      println("map----")
      line
    })


    /**
      * 对多次使用的RDD进行缓存, 默认cache是将数据缓存在内存中
      *
      */
    //  mapStudent.cache()

    /**
      * 持久化级别选择
      * 1、如果rdd的数据量不大 ,没有超过内存的限制--->  MEMORY_ONLY
      * 2、如果数据操超过了内存的限制 ---> MEMORY_AND_DISK_SER (压缩之后放内存内存放不下放磁盘)
      * 不管压不压缩放内存都比放磁盘要块,所有尽量将数据压到内存中
      *
      *
      * 缓存实际上是将数据缓存在执行task所在的服务器的内存或者磁盘上
      *
      */
    mapStudent.persist(StorageLevel.MEMORY_AND_DISK_SER)


    //统计班级的人数
    val clazzKVRDD: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(4), 1))
    val clazzNumRDD: RDD[(String, Int)] = clazzKVRDD.reduceByKey(_ + _)
    clazzNumRDD.foreach(println)


    //统计性别的人数
    val genderKVRDD: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(3), 1))
    val genderNumRDD: RDD[(String, Int)] = genderKVRDD.reduceByKey(_ + _)
    genderNumRDD.foreach(println)
  }

}
  • Checkpoint:将rdd的数据保存到hdfs中

在这里插入图片描述

package com.shujia.spark

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

object Demo17Checkpoint {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)


    //设置checkpoint的路径
    sc.setCheckpointDir("spark/data/checkpoint")

    val student: RDD[String] = sc.textFile("spark/data/students.txt")


    val mapStudent: RDD[String] = student.map(line => {
      println("map----")
      line
    })

    /**
      * checkpoint: 将rdd的数据保存到hdfs中,
      * 1、当第一个job执行完成之后会从最后一个rdd向前回溯,对调用了checkpoint的rdd打上标记
      * 2、另启动一个job重新计算rdd的数据,并将rdd的数据保存到hdfs
      *
      * 在checkpoint之前进行cache 优化效率
      *
      *
      * 主要在spark streaming 中使用
      *
      */
    mapStudent.cache()

    mapStudent.checkpoint()


    //统计班级的人数
    val clazzKVRDD: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(4), 1))
    val clazzNumRDD: RDD[(String, Int)] = clazzKVRDD.reduceByKey(_ + _)
    clazzNumRDD.foreach(println)


    //统计性别的人数
    val genderKVRDD: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(3), 1))
    val genderNumRDD: RDD[(String, Int)] = genderKVRDD.reduceByKey(_ + _)
    genderNumRDD.foreach(println)


    val genderKVRDD1: RDD[(String, Int)] = mapStudent.map(line => (line.split(",")(3), 1))
    val genderNumRDD1: RDD[(String, Int)] = genderKVRDD1.reduceByKey(_ + _)
    genderNumRDD1.foreach(println)
  }
}

广播变量和累加器

  • Driver端:spark程序的主程序,Driver也是一个独立的JVM;

  • Executor:执行器,执行task,独立的JVM。

算子外面的代码运行在Driver端,算子里的代码会被封装成Task发送到Executor中执行。

广播变量:在算子内使用算子外面的一个变量时,可以将这个变量进行广播,广播后减少变量副本的数量;如果不广播,每一个task中都会有一个变量副本,广播之后Executor中只有一个变量副本,一般task>>>>executor的数量。

累加器:当在算子内修改算子外的一个普通变量时,是不会生效的。

在这里插入图片描述

  • 累加器
package com.shujia.spark

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

import scala.collection.mutable.ListBuffer

object Demo18Acc {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)


    val student: RDD[String] = sc.textFile("spark/data/students.txt")
    /**
      * 算子内的代码运行在Executor,算子外面的代码运行在Driver端
      * 在算子内修改算子外的变量不会生效
      *
      * Executor:执行器,执行task,独立的JVM进程
      * Driver端:spark程序的主程序,Driver也是一个独立的JVM
      */
    var i = 0
    student.foreach(line => {
      i += 1
      println(line)
    })
    println(i)


    /**
      * 累加器:只能用来做累加计算
      * 1、在每一个task中进行局部累加
      * 2、当job执行完成之后,在driver端进行汇总
      */
    //定义一个累加器:1、在driver端定义;  2、在算子内累加;  3、在driver端读取累加结果
    val accumulator: LongAccumulator = sc.longAccumulator


    student.foreach(line => {
      //累加器可以在算子内进行累加
      accumulator.add(1)
      println(line)
    })

    //读取累加器的结果
    println(accumulator.value)


  /*  student.foreach(line => {
//在spark的算子内不能再使用rdd
//因为算子中的代码会被封装成一个task发生到Executor中执行,rdd 本身就是一个弹性的分布式数据集,不能在网络中传输
      student.foreach(l => {
        println(l)
      })
    })*/

  }

}
package com.shujia.spark

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

object Demo19Bro {
  def main(args: Array[String]): Unit = {
	//构建spark环境
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("map")
    val sc = new SparkContext(conf)


    val student: RDD[String] = sc.textFile("spark/data/students.txt")


    /*  val ids = List("1500100009", "1500100005", "1500100007")


      val filterRDD: RDD[String] = student.filter(line => {
        val id: String = line.split(",")(0)
        ids.contains(id)
      })*/


    //filterRDD.foreach(println)


      
    /**
      * 广播变量
      * :使用广播变量副本会减少	
      */
    val ids = List("1500100009", "1500100005", "1500100007")
    //在Driver将一个变量广播
    val broad: Broadcast[List[String]] = sc.broadcast(ids)

    val filterRDD: RDD[String] = student.filter(line => {
      val id: String = line.split(",")(0)
      //在算子内获取广播变量中的数据
      val broadValue: List[String] = broad.value
      broadValue.contains(id)
    })
    filterRDD.foreach(println)

    /**
      * 使用限制
      * 1、广播变量只能在算子外面定义,只能在算子里面使用
      */
  }

}
package com.shujia.spark

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

object Demo19Partition {
  def main(args: Array[String]): Unit = {

    val conf = new SparkConf()

    //会在yarn中显示的名称
    conf.setAppName("wc")

    /**
      * spark shuffle之后rdd默认的分区数据
      *
      */
    conf.set("spark.default.parallelism", "3")

    conf.setMaster("local")

    val sc: SparkContext = new SparkContext(conf)

    /**
      * 第一个rdd的分区数据默认等于切片数,可以指定一个最小分区数,最小分区所默认是2
      *
      */
    val linesRDD: RDD[String] = sc.textFile("spark/data/words", 10)


    println("linesRDD:" + linesRDD.getNumPartitions)

    /**
      * 没有shuffle算子----->后面的rdd分区数默认等于父rdd分区数
      *
      */

    val wordRDD: RDD[String] = linesRDD.flatMap(_.split(","))

    println("wordRDD:" + wordRDD.getNumPartitions)

    val kvRDD: RDD[(String, Int)] = wordRDD.map((_, 1))


    println("kvRDD:" + kvRDD.getNumPartitions)

    /**
      *
      * countRDD 分区数决定因素
      *
      * 1、如果没有指定分区数据默认等于父rdd分区数
      * 2、shuffle类的算子可以手动指定分区数据(reduce的数量)
      *
      *
      * 分区数据决定优先级: 手动指定---> 默认分区数(spark.default.parallelism)---> 父rdd分区数
      *
      */

    val countRDD: RDD[(String, Int)] = kvRDD.reduceByKey(_ + _, 2)


    println("countRDD:" + countRDD.getNumPartitions)

    /**
      * repartition: 改变rdd分区数据,会产生shuffle,  不做任何逻辑处理
      *
      * 可以用于提供并行度
      *
      */

    val repartitionRDD: RDD[(String, Int)] = countRDD.repartition(10)


    println("repartitionRDD:" + repartitionRDD.getNumPartitions)

    repartitionRDD.foreach(println)

    while (true) {

    }
  }

}

练习

package com.shujia.spark

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

object Demo20Student {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setAppName("student")
      .setMaster("local[4]")
    val sc = new SparkContext(conf)


    /**
      *
      * 2、统计总分大于年级平均分的学生 [学号,姓名,班级,总分]
      * 1、统计总分
      * 2、统计年级平均分
      * 3、过滤总分是否打印平均分
      * 4、整理数据
      *
      */

    //1、读取数据
    val scores: RDD[String] = sc.textFile("spark/data/score.txt")

    scores.cache()

    //转换成kv格式
    val kvScore: RDD[(String, Double)] = scores.map(line => {
      val split: Array[String] = line.split(",")
      (split(0), split(2).toDouble)
    })

    /// 统计总分
    val sumScore: RDD[(String, Double)] = kvScore.reduceByKey(_ + _)

    //统计平均分
    val avgScore: Double = sumScore.map(_._2).sum() / sumScore.count()

    println(s"年级平均分:$avgScore")

    //过滤总分是否打印平均分
    val filterScore: RDD[(String, Double)] = sumScore.filter(_._2 > avgScore)


    //管理学生表整理数据
    val students: RDD[String] = sc.textFile("spark/data/students.txt")

    //将学生表转换成kv格式
    val kvStudent: RDD[(String, (String, String))] = students.map(line => {
      val split: Array[String] = line.split(",")
      val id: String = split(0)
      val name: String = split(1)
      val clazz: String = split(4)

      (id, (name, clazz))
    })

    //关联学生信息表
    val joinRDD: RDD[(String, (Double, (String, String)))] = filterScore.join(kvStudent)

    //整理数据
    val resultRDD: RDD[String] = joinRDD.map {
      case (id: String, (sumScore: Double, (name: String, clazz: String))) =>
        s"$id,$name,$clazz,$sumScore"

    }
    //保存数据
    resultRDD.saveAsTextFile("spark/data/sumScore")
  }

}
package com.shujia.spark

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

object Demo21Student {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setAppName("student")
      .setMaster("local[4]")

    val sc = new SparkContext(conf)


    /**
      *
      *
      * 3、统计每科都及格的学生 [学号,姓名,班级,科目,分数]
      * 1、将所有都及格的分数和对应的学号取出来
      * 2、统计每一个及格科目数
      * 3、过滤没有都及格的学生
      * 4、整理数据
      */
    //1、读取数据
    val scores: RDD[String] = sc.textFile("spark/data/score.txt")

    val kvScore: RDD[(String, (String, Double))] = scores.map(line => {
      val split: Array[String] = line.split(",")
      val id: String = split(0)
      val cId: String = split(1)
      val sco: Double = split(2).toDouble
      (cId, (id, sco))
    })


    //读取科目表
    val cource: RDD[String] = sc.textFile("spark/data/cource.txt")

    val kvCource: RDD[(String, Double)] = cource.map(line => {
      val split: Array[String] = line.split(",")
      val id: String = split(0)
      val sumScore: Double = split(2).toDouble

      (id, sumScore)
    })


    //关联分数表和科目表
    val joinRDD: RDD[(String, ((String, Double), Double))] = kvScore.join(kvCource)

    //过滤不及格的分数
    val filterRDD: RDD[(String, ((String, Double), Double))] = joinRDD.filter {
      //没有使用到的列可以用下划线代替
      case (_: String, ((_: String, sco: Double), sumScore: Double)) =>
        sco >= sumScore * 0.6

    }

    val idRDD: RDD[(String, Int)] = filterRDD.map {
      case (_: String, ((id: String, _: Double), _: Double)) => {
        (id, 1)
      }
    }

    //统计每个学生及格的科目数
    val value: RDD[(String, Int)] = idRDD.reduceByKey(_ + _)

    val filterStudent: RDD[(String, Int)] = value.filter(_._2 == 6)


    val ids: Array[String] = filterStudent.map(_._1).collect()

    //整理数据
    val studentScore: RDD[String] = scores.filter(line => ids.contains(line.split(",")(0)))

    studentScore.foreach(println)
  }
}
package com.shujia.spark

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

object Demo22Student {
  def main(args: Array[String]): Unit = {

    /**
      * 4、统计偏科最严重的前100名学生  [学号,姓名,班级,科目,分数
      * 偏科程度判断依据:通过方差判断偏科程度
      *
      */
    val conf: SparkConf = new SparkConf()
      .setAppName("student")
      .setMaster("local[4]")
    val sc = new SparkContext(conf)


    //读取分数表
    val scores: RDD[String] = sc.textFile("spark/data/score.txt")

    //科目表
    val cource: RDD[String] = sc.textFile("spark/data/cource.txt")

    val kvCource: RDD[(String, Double)] = cource
      .map(_.split(","))
      .map(arr => (arr(0), arr(2).toDouble))

    //key 是科目编号,value是科目总分
    val courceMap: collection.Map[String, Double] = kvCource.collectAsMap()

    //广播集合
    val bro: Broadcast[collection.Map[String, Double]] = sc.broadcast(courceMap)

    /**
      * map join  , 将小表加载到内存在map端进行表关联
      * 限制: 不能用于两大表的关联,
      *
      */
    //关联科目表
    val scoreCou: RDD[(String, Double, Double)] = scores.map(line => {
      val split: Array[String] = line.split(",")
      val id: String = split(0)
      val cId: String = split(1)
      val sco: Double = split(2).toDouble

      //通过科目编号获取科目总分
      val sumScore: Double = bro.value.getOrElse(cId, 100)
      (id, sco, sumScore)
    })

    //对分数进行归一化。将不同范围的分数拉到同一个级别上
    val oneScore: RDD[(String, Double)] = scoreCou.map {
      case (id: String, sco: Double, sumScsore: Double) =>
        (id, sco / sumScsore)

    }

    //计算学生的平均值
    val groupRDD: RDD[(String, Iterable[Double])] = oneScore.groupByKey()

    val avgSco: RDD[(String, Double)] = groupRDD.map {
      case (id: String, scos: Iterable[Double]) =>
        (id, scos.sum / 6)
    }


    val joinRDD: RDD[(String, (Double, Double))] = oneScore.join(avgSco)

    //计算分数和平均分的差值
    val chapingRDD: RDD[(String, Double)] = joinRDD.map {
      case (id: String, (sco: Double, acgSc: Double)) =>
        (id, (sco - acgSc) * (sco - acgSc))
    }

    //差值的和
    val fangRDD: RDD[(String, Double)] = chapingRDD.reduceByKey(_ + _)


    //按照方差降序排名
    val sortfangRDD: RDD[(String, Double)] = fangRDD.sortBy(_._2, ascending = false)

    //取出前100
    val top100Id: Array[String] = sortfangRDD.take(100).map(_._1)

    //取出这些学生的分数
    val filterScore: RDD[String] = scores.filter(line => top100Id.contains(line.split(",")(0)))


    filterScore.foreach(println)
  }

}

资源调度(yarn -client)

在本地启动一个Driver,本地打印日志。

优点:本地打印日志,可以查看详细运行日志,方便测试;

缺点:Driver会和集群中Executor进行大量通信,会导致本地服务器网卡流量剧增;

​ 每一个sparkDriver都会占用一个端口,端口会不够用。

在这里插入图片描述

资源调度(yarn-cluster)

用于上产环境,本地不会启动Driver,会随即在集群中的某个节点上启动Driver。本地没有详细日志。

优点:Driver在集群中随机的一个节点,不会导致网卡流量剧增。

缺点:在本地看不到日志,不方便测试。

在这里插入图片描述

blockManager

executor中不光有线程池。还都会有一个blockmanager,用于管理executor中的数据:

  1. rdd的缓存数据。
  2. 广播变量和累加器。
  3. shuffle过程中的文件。

在这里插入图片描述

spark shuffle

package com.shujia.spark

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

object Demo19Partition {
  def main(args: Array[String]): Unit = {

    val conf = new SparkConf()
    //会在yarn中显示的名称
    conf.setAppName("wc")

    //spark shuffle之后rdd默认的分区数据
    conf.set("spark.default.parallelism", "3")

    conf.setMaster("local")

    val sc: SparkContext = new SparkContext(conf)

    /**
      * 第一个rdd的分区数据默认等于切片数,可以指定一个最小分区数,最小分区所默认是2
      *
      *  没有shuffle算子----->后面的rdd分区数默认等于父rdd分区数
      */
    val linesRDD: RDD[String] = sc.textFile("spark/data/words", 10)
    println("linesRDD:" + linesRDD.getNumPartitions)//10

    val wordRDD: RDD[String] = linesRDD.flatMap(_.split(","))
    println("wordRDD:" + wordRDD.getNumPartitions)//10
      
	//变成KV格式的RDD
    val kvRDD: RDD[(String, Int)] = wordRDD.map((_, 1))
    println("kvRDD:" + kvRDD.getNumPartitions)//10

    /**
      * countRDD 分区数决定因素:
      * 1、如果没有指定分区数据默认等于父rdd分区数
      * 2、shuffle类的算子可以手动指定分区数据(reduce的数量)
      *
      * 分区数据决定优先级: 手动指定---> 默认分区数(spark.default.parallelism)---> 父rdd分区数
      */
    val countRDD: RDD[(String, Int)] = kvRDD.reduceByKey(_ + _, 2)
    println("countRDD:" + countRDD.getNumPartitions)//2

    /**
      * repartition: 重分区,改变rdd分区数据,会产生shuffle,  不做任何逻辑处理
      *
      * 可以用于提供并行度
      */
    val repartitionRDD: RDD[(String, Int)] = countRDD.repartition(10)
    println("repartitionRDD:" + repartitionRDD.getNumPartitions)//分区数变成10

    repartitionRDD.foreach(println)
  }

}

资源调度和任务调度

Application(spark程序)--->Job(一个action算子触发的任务)--->stage(一组并行计算的task)--->task(执行任务的最小单元)


大数据计算分两步
	1、资源调度  yarn-client
		1、通过spark-submit提交任务
		2、在本地启动Driver   val sc = new SparkContext(conf)
		3、Driver发请求给RM  启动AM
		4、RM分配资源启动AM
		5、AM向RM申请资源启动Excutor
		6、Excutor反响注册给Driver 
		7、开始任务调度(action算子触发)

	2、任务调度
		1、当遇到一个action算子,触发一个任务,开始任务调度
		2、构建DAG有向无环图
		3、DAGscheduler根据宽窄依赖切分stage
		4、DAGscheduler将stage以taskSet的形式发送给taskScheduler
		5、taskscheduler根据本地化算法将task发送到Execuotr中执行
		6、taskscheduler接收task执行情况

如果task失败taskscheduler负责重试,默认重试3次  如果是因为shuffle file not found  taskscheduler不再重试task,而是由DAGscheduler重试上一个stage,DAGscheduler默认重试stage4次

		DAGscheduler   负责切分stage
		taskscheduler  负责发送task到Exsecutor中执行
		
粗粒度资源调度 spark:
一次性将所需要的资源(executor数量,executor内存,executor core)全部申请下来,task执行不需要再申请资源,执行速度变快;当最后一个task执行完成之后才会释放资源
缺点:浪费资源,导致资源不能充分利用

细粒度资源调度  mr:
每一个task执行都需要单独申请资源,每次申请资源需要时间,导致task执行变慢--->job变慢---->application变慢
充分利用资源

在这里插入图片描述

Spark SQL

DataFrame : 在RDD的基础上增加了列名,相当于一张表

  • SparkSession、DataFrame
package com.shujia.sql
//(2.4.5)
import org.apache.spark.SparkContext
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}

object Demo1SparkSessio {
  def main(args: Array[String]): Unit = {

    /**
      * SparkSession : spark 2.0提供的新的入口
      *
      * 新版本统一的入口。之前spark core里面没有,这是spark sql里面的
      */
    val spark: SparkSession = SparkSession
      .builder()
      .master("local")
      //指定spark sql shuffle 之后df 分区的数量,默认是200
      //小于200就不会触发bypass机制,数据就会有序
      .config("spark.sql.shuffle.partitions", 2)
      .appName("spark")
      .getOrCreate()

    //获取sparkContet
    //val sc: SparkContext = spark.sparkContext

    /**
      * DataFrame : 在RDD的基础上增加了列名
      * 相当于一张表
      */
    //spark sql 读取数据的方式						json格式,里面有列名,有列值
    val studentDF: DataFrame = spark.read.json("spark/data/students.json")
    //查看表结构
    studentDF.printSchema()

    /**
      * show: 查看数据,相当于action算子
      */
    studentDF.show()
    studentDF.show(100)
    studentDF.show(false) //完整显示数据,不会省略


    /**
      * DSL 语句: 类sql语句, 介于sql和代码中间的api
      */
    studentDF
      .where(" age > 22")
      .select("id")
      .show()


    //统计班级的人数
    studentDF
      .groupBy("clazz")
      .count()
      .show()


    /**
      * 创建临时试图。将studentDF创建成一张视图
      */
    studentDF.createOrReplaceTempView("student")


    /**
      * 通过spark 编写sql  ,返回一个新的DataFrame
      */
    val clazzCount: DataFrame = spark.sql(
      """
        |select clazz,count(1) as c
        |from student
        |group by clazz
        |
        |
      """.stripMargin)


    /**
      * 保存数据
      */
    clazzCount
      .write
      .mode(SaveMode.Overwrite) //如果输出目录已存在自动覆盖
      .json("spark/data/clazz_count")
  }

}
  • 读取各种文件类型的数据
package com.shujia.sql

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}

object Demo2CreateDF {

  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession
      .builder()
      .master("local")
      .appName("df")
      .getOrCreate()


    /**
      * 1、 读取json文件
      * json中自带列名,不需要指定列名
      * 一般不用,json格式数据会冗余
      */
    val jsonDF: DataFrame = spark
      .read
      .format("json")
       .load("spark/data/students.json")


    /**
      * 2、读取文件格式的数据
      */
    val csvDF: DataFrame = spark
      .read
      .format("csv")
      .option("sep", ",") //默认是逗号分隔
      //按照数据的顺序指定表结构
      .schema("id STRING , name STRING , age INT , gender STRING , clazz STRING")
      .load("spark/data/students.txt")

    csvDF.printSchema()
    //csvDF.show()


    /**
      * 3、读取数据库构建df
      */
    val jdbcDF: DataFrame = spark
      .read
      .format("jdbc")
      .option("url", "jdbc:mysql://master:3306")
      .option("dbtable", "student.student")
      .option("user", "root")
      .option("password", "123456")
      .load()

    jdbcDF.show()
    /**
      * 保存一个parquet格式的文件
      * parquet是一种带表结构的压缩格式
      */
    csvDF.write.mode(SaveMode.Overwrite).parquet("spark/data/parquet")


    /**
      * 4、读取parquet 格式的文件
      */
   val parquetDF: DataFrame = spark
      .read
      .format("parquet")
      .load("spark/data/parquet")

    parquetDF.printSchema()
    parquetDF.show()


    /**
      * 保存一个orc格式的数据
      */
    csvDF.write.mode(SaveMode.Overwrite).orc("spark/data/orc")

    /**
      * 5、读取orc格式的数据
      * 是一种带表结构的压缩格式
      */
    val orcDF: DataFrame = spark
      .read
      .format("orc")
      .load("spark/data/orc")
    orcDF.printSchema()
    orcDF.show()


    /**
      * 6、基于RDD创建DF(需要导入隐士转换才能用toDF)
      */
    val sc: SparkContext = spark.sparkContext
    val rdd: RDD[String] = sc.textFile("spark/data/students.txt")
    val studentRDD: RDD[(String, String, Int, String, String)] = rdd.map(line => {
      val split: Array[String] = line.split(",")

      (split(0), split(1), split(2).toInt, split(3), split(4))
    })

    //导入隐式转换
    import spark.implicits._

    //将rdd转换成DF ,指定列名
    val studentDF: DataFrame = studentRDD.toDF("id", "name", "age", "gender", "clazz")
    studentDF.printSchema()
    studentDF.show()*/


    while (true){
    }

  }

}
  • DFApi
package com.shujia.sql

import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

object Demo3DFApi {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local")
      .appName("api")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()

    //导入隐式转换
    import spark.implicits._

    val student: DataFrame = spark
      .read
      .format("csv")
      .option("sep", ",") //默认是逗号分隔
      //按照数据的顺序指定表结构
      .schema("id STRING , name STRING , age INT , gender STrING , clazz STRING")
      .load("spark/data/students.txt")


    /**
      * show :查看数据
      */
    student.show()

    /**
      * where : 过滤数据
      */
    //字符串表达式
    student.where("age > 22").show()
    //使用 列表达式的方式
    student.where($"age" >= 22).show()


    /**
      * Dataset[Row]  和 DataFrame同一个东西
      */
    val filterDS: Dataset[Row] = student.filter((row: Row) => {
      val age: Int = row.getAs[Int]("age")
      age >= 22
    })
    filterDS.show()

    /**
      * select : 选择数据
      */
    student.select("name", "age").show()
    //选择的时候也可以处理数据
    student.select($"name", $"age" + 1 as "age").show()

    /**
      * group By: 分组
      * 分组之后必须接一个聚合函数
      */
    student.groupBy("clazz").max("age").show()


    //导入sql中素有的函数
    import org.apache.spark.sql.functions._
    //计算每个班级最大的年龄
    student
      .groupBy("clazz")
      .agg(max($"age") as "maxAge")
      .show()

    //计算每个班级平均年龄
    student.groupBy("clazz")
      .agg(avg("age") as "avgAge")
      .show()


    //统计班级性别的人数
    student.groupBy($"clazz", $"gender")
      .agg(countDistinct($"id") as "c")
      .show()


    /**
      * join : 表关联
      * 如果关联的列名一样直接指定关联列名
      */
    val score: DataFrame = spark
      .read
      .format("csv")
      .option("sep", ",") //默认是逗号分隔
      //按照数据的顺序指定表结构
      .schema("sid STRING , cId STRING , sco DOUBLE")
      .load("spark/data/score.txt")

    val joinDF: DataFrame = student.join(score, $"id" === $"sid", "inner")
    //如果关联的列名一样直接指定关联列名
    val joinDF: DataFrame = student.join(score, "id")

    joinDF
      .groupBy("name")
      .agg(sum($"sco") as "sumSco")
      .show()
  }
}
package com.shujia.sql
//统计每一个城市人流量最多的前两个区县
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.{DataFrame, SparkSession}

object Demo4Dianxin {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local")
      .appName("dianxin")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()

    //导入隐式转换
    import spark.implicits._
    //导入sql 所有的函数
    import org.apache.spark.sql.functions._

    //读取数据
    val dianxin: DataFrame = spark
      .read
      .format("csv")
      .option("sep", ",")
      .schema("mdn STRING,grid STRING, cityId STRING , countyId STriNG , t INT , startTIme STRING, endTIme STRING ,day STRING")
      .load("spark/data/dianxin_data")
      .where($"cityId" =!= "\\N") //过滤脏数据
      
    // DSL
    dianxin
      .groupBy($"cityId", $"countyId") //按照城市和曲线分组
      .agg(countDistinct($"mdn") as "num") //统计人数
      .select($"cityId", $"countyId", $"num", row_number().over(Window.partitionBy($"cityId").orderBy($"num".desc)) as "rank")
      .where($"rank" <= 2) //取前2		
      .select($"cityId", $"countyId", $"num", $"rank") //整理数据
      .show(1000)   
      //这种方式代码就是从前到后执行,不是按照sql的执行顺序
      
      
    //sql语句  
    dianxin.createOrReplaceTempView("dianxin")
    spark.sql(
      """
        |
        |
        |select * from
        |(
        |select
        |cityId,countyId,num , row_number() over(partition by cityId  order by num desc ) as rank
        |from
        |(select
        | cityId,countyId,count(distinct mdn) as num
        |from
        |dianxin
        |group by cityId,countyId) as a
        |) as b
        |where rank <=2
        |
        |
      """.stripMargin)
    .show()

  }
}
  • explode,一行变多行,行专列
package com.shujia.sql

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.{Column, DataFrame, SparkSession}

object Demo5Burk {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local")
      .appName("dianxin")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()

    //导入隐式转换
    import spark.implicits._
    //导入sql 所有的函数
    import org.apache.spark.sql.functions._


    /**
      * explode: 相当于flatmap,一行变多行
      */
    spark.sql(
      """
        |select explode(array(1,2,3,4,5,6))
        |
      """.stripMargin)
    // .show()

    spark.sql(
      """
        |select explode(map('01','张三','02','李四','03','王五'))
        |
      """.stripMargin)
    //.show()

    /**
      * 1、统计每个公司每年按月累计收入
      *
      * 输出结果
      * 公司代码,年度,月份,当月收入,累计收入
      */
    val burk: DataFrame = spark.read
      .format("csv")
      .option("sep", ",")
      .schema("burk STRING,year STRING,tsl01 DOUBLE,tsl02 DOUBLE,tsl03 DOUBLE,tsl04 DOUBLE,tsl05 DOUBLE,tsl06 DOUBLE,tsl07 DOUBLE,tsl08 DOUBLE,tsl09 DOUBLE,tsl10 DOUBLE,tsl11 DOUBLE,tsl12 DOUBLE")
      .load("spark/data/burks.txt")

    burk.createOrReplaceTempView("burk")


    spark.sql(
      """
        |
        |select
        |burk,year,month,pic
        |from
        |burk
        |LATERAL VIEW  explode(map( 1, tsl01,2 ,tsl02, 3 , tsl03,4,tsl04,5, tsl05,6 ,tsl06, 7 , tsl07,8,tsl08,9, tsl09,10 ,tsl10, 11 , tsl11,12,tsl12)) t as month,pic
        |
      """.stripMargin)

    spark.sql(
      """
        |												//按月累加
        |select burk,year,month ,pic ,sum(pic) over (partition by burk,year order by month) as sumPic
        |from
        |(select
        |burk,year,explode(map( 1, tsl01,2 ,tsl02, 3 , tsl03,4,tsl04,5, tsl05,6 ,tsl06, 7 , tsl07,8,tsl08,9, tsl09,10 ,tsl10, 11 , tsl11,12,tsl12)) as (month,pic)
        |from
        |burk) as a
        |
      """.stripMargin)
    //.show(1000)


    /**
      * DSL
      */
    val monthMap: Column = map(
      expr("1"), $"tsl01",
      expr("2"), $"tsl02",
      expr("3"), $"tsl03",
      expr("4"), $"tsl04",
      expr("5"), $"tsl05",
      expr("6"), $"tsl06",
      expr("7"), $"tsl07",
      expr("8"), $"tsl08",
      expr("9"), $"tsl09",
      expr("10"), $"tsl10",
      expr("11"), $"tsl11",
      expr("12"), $"tsl12"
    )


    burk
      .select($"burk", $"year", explode(monthMap) as Array("month", "pic"))
      .select($"burk", $"year", $"month", $"pic", sum($"pic") over Window.partitionBy($"burk", $"year").orderBy($"month".asc) as "sumPic")
      .show(1000)


    /**
      * 2、统计每个公司当月比上年同期增长率。lag函数 
      * 公司代码,年度,月度,增长率(当月收入/上年当月收入 - 1)
      */

    /**
      * coalesce : 如第一个为null 再取第二个
      */
    burk
      .select($"burk", $"year", explode(monthMap) as Array("month", "pic"))
      .select($"burk", $"year", $"month", $"pic", lag($"pic", 1, 0.0) over Window.partitionBy($"burk", $"month").orderBy($"year") as "lastPic")
      .select($"burk", $"year", $"month", $"pic", round(coalesce($"pic" / $"lastPic" - 1, expr("1.0")), 5) as "bi")
      .show()

  }

}
  • spark sql 运行方式:
  1. 在ideal里面写sql,打包发送到集群上面运行。读写地址写hdfs上面的路径。spark-submit提交。用于生产

  2. spark shell 。在命令行的里写一行代码,写一行运行一行。用于测试,简单的任务使用。

  3. spark-sql。spark-sql --master yarn-client。进入sql命令行。可以整合hive,使用hive元数据。

    这个需要在hive的配置文件中添加一些spark的配置。整合后在spark-sql 里面就可以使用hive的表了。

package com.shujia.sql

import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}

object Demo6Submit {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession
      .builder()
      .appName("submit")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()

    import spark.implicits._
    import org.apache.spark.sql.functions._


    val student: DataFrame = spark.read
      .format("csv")
      .option("sep", ",")
      .schema("id STRING , name STRING ,age INT ,gender STRING ,clazz STRING")
      .load("/data/student") //读取hdfs文件

    val clazznuNum: DataFrame = student
      .groupBy($"clazz")
      .agg(count($"clazz") as "num")


    //保存数据
    clazznuNum.write
      .format("csv")
      .option("sep", "\t")
      .mode(SaveMode.Overwrite)
      .save("/data/clazz_num")

  }
}
  • spark 整合hive
package com.shujia.sql

import org.apache.spark.sql.{DataFrame, SparkSession}

object Demo7OnHIve {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession
      .builder()
      .appName("submit")
      .config("spark.sql.shuffle.partitions", 2)
      .enableHiveSupport() //开启读取hive的元数据
      .getOrCreate()

    /**
      * 可以直接使用hive中表
      */
    spark.sql(
      """
        |show tables
        |
      """.stripMargin).show()


    spark.sql(
      """
        |
        |select clazz,count(1) from student group by clazz
        |
        |
      """.stripMargin).show()

    //可以读取hive中的表构建df
    val score: DataFrame = spark.table("score")
    score.show()
  }

}
//不能直接运行,因为本地模式没有整合hive。需要打包放到集群上运行。

Spark优化

  1. 代码层面优化:
  • 避免创建重复的RDD
  • 尽可能复用同一个RDD
  • 对多次使用的RDD进行持久化
  • 尽量避免使用shuffle类算子
  • 使用map-side预聚合的shuffle操作
  • 使用高性能的算子
  • 广播大变量
  • 使用Kryo优化序列化性能
  • 优化数据结构
  • 使用高性能的库fastutil
  1. 参数层面优化:
  • 数据本地性
  • JVM调优
  • shuffle调优
  • 调节Executor堆外内存
  1. 数据倾斜七种解决方案:
  • 使用Hive ETL预处理数据
  • 过滤少数导致倾斜的key
  • 提高shuffle操作的并行度
  • 双重聚合
  • 将reduce join转为map join
  • 采样倾斜key并分拆join操作
  • 使用随机前缀和扩容RDD进行join

  • 代码优化

在这里插入图片描述

  • 对多次使用的RDD进行持久化
如何选择一种最合适的持久化策略 
1、默认情况下,性能最高的当然是MEMORY_ONLY,但前提是你的内存必须足够足够大, 可以绰绰有余地存放下整个RDD的所有数据。因为不进行序列化与反序列化操作,就避 免了这部分的性能开销;对这个RDD的后续算子操作,都是基于纯内存中的数据的操作 ,不需要从磁盘文件中读取数据,性能也很高;而且不需要复制一份数据副本,并远程传 送到其他节点上。但是这里必须要注意的是,在实际的生产环境中,恐怕能够直接用这种 策略的场景还是有限的,如果RDD中数据比较多时(比如几十亿),直接用这种持久化 级别,会导致JVM的OOM内存溢出异常。 

2、如果使用MEMORY_ONLY级别时发生了内存溢出,那么建议尝试使用 MEMORY_ONLY_SER级别。该级别会将RDD数据序列化后再保存在内存中,此时每个 partition仅仅是一个字节数组而已,大大减少了对象数量,并降低了内存占用。这种级别 比MEMORY_ONLY多出来的性能开销,主要就是序列化与反序列化的开销。但是后续算 子可以基于纯内存进行操作,因此性能总体还是比较高的。此外,可能发生的问题同上, 如果RDD中的数据量过多的话,还是可能会导致OOM内存溢出的异常。

3、如果纯内存的级别都无法使用,那么建议使用MEMORY_AND_DISK_SER策略,而不是 MEMORY_AND_DISK策略。因为既然到了这一步,就说明RDD的数据量很大,内存无 法完全放下。序列化后的数据比较少,可以节省内存和磁盘的空间开销。同时该策略会优 先尽量尝试将数据缓存在内存中,内存缓存不下才会写入磁盘。 

4、通常不建议使用DISK_ONLY和后缀为_2的级别:因为完全基于磁盘文件进行数据的读写 ,会导致性能急剧降低,有时还不如重新计算一次所有RDD。后缀为_2的级别,必须将 所有数据都复制一份副本,并发送到其他节点上,数据复制以及网络传输会导致较大的性 能开销,除非是要求作业的高可用性,否则不建议使用。 
  • 使用高性能的算子

使用reduceByKey/aggregateByKey替代groupByKey

使用mapPartitions替代普通map Transformation算子

使用foreachPartitions替代foreach Action算子

使用filter之后进行coalesce操作

使用repartitionAndSortWithinPartitions替代repartition与sort类操 作 代码

repartition:coalesce(numPartitions,true) 增多分区使用这个

coalesce(numPartitions,false) 减少分区 没有shuffle只是合并 partition

  • 如果需要将数据保存到外部数据库,使用foreachPartition 代替foreach。因为rdd没有save方法,而df有。
package com.shujia.optimize

import java.sql.{Connection, DriverManager, PreparedStatement}
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession

object Demo0ForeachPartition {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local")
      .appName("foreach")
      .getOrCreate()

    val rdd1: RDD[String] = spark
      .sparkContext
      .textFile("spark/data/students.txt", 4)

    println(rdd1.getNumPartitions)

    /**
      * 将rdd的数据保存到mysql中
      */


/*

    rdd1.foreach(line => {

      /**
        * 建立jdbc连接
        *
        * 网络连接的对象不能序列化,也不能在网络中传输
        *
        */
      Class.forName("com.mysql.jdbc.Driver")
      val con: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student?useUnicode=true&characterEncoding=utf-8", "root", "123456")


      val stat: PreparedStatement = con.prepareStatement("insert into student(id,name,age,gender,clazz) values(?,?,?,?,?)")


      val split: Array[String] = line.split(",")

      stat.setString(1, split(0))
      stat.setString(2, split(1))
      stat.setInt(3, split(2).toInt)
      stat.setString(4, split(3))
      stat.setString(5, split(4))

      //插入数据
      stat.executeUpdate()
    })
*/


    /**
      * foreachPartition  : 遍历一个分区的数据
      *
      * iter : 一个分区的数据
      *
      *
      * 如果需要将数据保存到外部数据库,使用foreachPartition 代替foreach
      *
      * foreachPartition 每一个分区只会创建一个连接
      */


    rdd1.foreachPartition(iter => {

      //这里的代码每一个分区指挥执行一次

      //每一个分区只会建立一个连接
      Class.forName("com.mysql.jdbc.Driver")
      val con: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student?useUnicode=true&characterEncoding=utf-8", "root", "123456")

      println("连接建立成功")

      //遍历一个分区的数据
      iter.foreach(line => {
        val stat: PreparedStatement = con.prepareStatement("insert into student(id,name,age,gender,clazz) values(?,?,?,?,?)")


        val split: Array[String] = line.split(",")

        stat.setString(1, split(0))
        stat.setString(2, split(1))
        stat.setInt(3, split(2).toInt)
        stat.setString(4, split(3))
        stat.setString(5, split(4))
        //插入数据
        stat.executeUpdate()
      })
      //关闭连接
      con.close()

    })


    /**
      * mapPartitions : 遍历一个分区,返回一i个迭代器
      *
      * mapPartitionsWithIndex ; 在mapPartitions的基础上多了分区编号
      */
    rdd1.mapPartitions(iter => {
      //在这里写的代码每一个分区只会执行一次
      iter.map(line => line)
    })


    rdd1.mapPartitionsWithIndex {
      case (index, iter) => {
        println(index)
        iter
      }
    }.foreach(line => line)


  }

}
package com.shujia.optimize

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

object Demo1Coalesce {
  def main(args: Array[String]): Unit = {


    /**
      * coalesce   重分区
      *
      * 改变rdd的并行度,  如果资源充足,可以提高并行度提高任务执行速度
      *
      * 保证每一个task处理的数据再50m到1G不等
      */

    val conf: SparkConf = new SparkConf()
      .setAppName("app")
      .setMaster("local[1]")
    val sc: SparkContext = new SparkContext(conf)

    //    产生小文件
    /*    sc
          .textFile("spark/data/students.txt")
          //重分区   会产生shuffle
          .repartition(20)
          .saveAsTextFile("spark/data/repartition")*/

    //数据里面由很多小文件,导致rdd分区很多,每隔分区数据量很小
    val rdd: RDD[String] = sc.textFile("spark/data/repartition")
    println("rdd:" + rdd.getNumPartitions)


    /**
      * repartition  会产生shuffle
      */
    val rdd2: RDD[String] = rdd.repartition(3)
    println("rdd2:" + rdd2.getNumPartitions)


    /**
      * 增加分区必须产生shuffle
      * 减少分区的时候可以不产生shuflle
      */

    /**
      * 合并小文件
      *
      * 保证每一个task处理的数据量在100M - 1G 左右
      */
    //减少分区的时候可以不产生shuflle
    val cRDD: RDD[String] = rdd2.coalesce(2, shuffle = false)
    println("cRDD:" + cRDD.getNumPartitions)
    cRDD.foreach(println)

    //增加分区必须有shuffle
    val rdd3: RDD[String] = rdd.coalesce(200, shuffle = true)
    println(rdd3.getNumPartitions)
  }
}
  • 广播大变量
开发过程中,会遇到需要在算子函数中使用外部变量的场景(尤其是大变量,比如 100M以上的大集合),那么此时就应该使用Spark的广播(Broadcast)功能来提 升性能 

函数中使用到外部变量时,默认情况下,Spark会将该变量复制多个副本,通过网络 传输到task中,此时每个task都有一个变量副本。如果变量本身比较大的话(比如 100M,甚至1G),那么大量的变量副本在网络中传输的性能开销,以及在各个节 点的Executor中占用过多内存导致的频繁GC(垃圾回收),都会极大地影响性能 

如果使用的外部变量比较大,建议使用Spark的广播功能,对该变量进行广播。广播 后的变量,会保证每个Executor的内存中,只驻留一份变量副本,而Executor中的 task执行时共享该Executor中的那份变量副本。这样的话,可以大大减少变量副本 的数量,从而减少网络传输的性能开销,并减少对Executor内存的占用开销,降低 GC的频率 

广播大变量发送方式:Executor一开始并没有广播变量,而是task运行需要用到广 播变量,会找executor的blockManager要,bloackManager找Driver里面的 blockManagerMaster要。
package com.shujia.optimize

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

object MapJoin {
  /**
    * map join
    *
    * 将小表广播,大表使用map算子
    *
    * 1、小表不能太大, 不能超过2G
    * 2、如果driver内存不足,需要手动设置  如果广播变量大小超过了driver内存大小,会出现oom
    */
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("app")
    val sc: SparkContext = new SparkContext(conf)

    //RDD 不能广播。集合才能广播
    val studentRDD: RDD[String] = sc.textFile("spark/data/students.txt")
    //将数据拉去到driver端,变成一个map集合
    val stuMap: Map[String, String] = studentRDD
      .collect() //将rdd的数据拉取Driver端变成一个数组
      .map(s => (s.split(",")(0), s))
      .toMap

    //广播map集合
    val broStu: Broadcast[Map[String, String]] = sc.broadcast(stuMap)

    val scoreRDD: RDD[String] = sc.textFile("spark/data/score.txt")
    //循环大表,通过key获取小表信息
    scoreRDD.map(s => {
      val sId: String = s.split(",")(0)
      //从广播变量里面获取数据
      val stuInfo: String = broStu.value.getOrElse(sId, "")
      stuInfo + "," + s
    }).foreach(println)

    while (true) {
    }

  }
}
package com.shujia.optimize

import org.apache.spark.sql.{DataFrame, SparkSession}

object SparkSqlMapJoin {

  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder()
      .master("local[4]")
      .appName("join")
      .config("spark.sql.shuffle.partitions", "2")
      .getOrCreate()


    import spark.implicits._

    val student: DataFrame = spark.read
      .format("csv")
      .schema("id STRING , name STRING ,age INT ,gender STRING ,clazz STRING")
      .load("spark/data/students.txt")

    val score: DataFrame = spark.read
      .format("csv")
      .schema("sid STRING ,cId STRING , sco INT")
      .load("spark/data/score.txt")
    /**
      * .hint("broadcast") : 广播表实现m ap join
      *
      */   //方式1:
    student.join(score.hint("broadcast"), $"sId" === $"id").show(10000000)

	//方式2:
    student.createOrReplaceTempView("student")
    score.createOrReplaceTempView("score")
    spark.sql(
      """
        |
        |select /*+broadcast(a)  */ * from score as a join student as b on a.sId=b.id
        |
      """.stripMargin).show(1000000)

    while (true) {
    }

  }
}
  • 使用Kryo优化序列化性能

在Spark中,主要有三个地方涉及到了序列化:

  1. 在算子函数中使用到外部变量时,该变量会被序列化后进行网络传输;
  2. 将自定义的类型作为RDD的泛型类型时(比如JavaRDD,SXT是自定义类型),所有自 定义类型对象,都会进行序列化。因此这种情况下,也要求自定义的类必须实现 Serializable接口。
  3. 使用可序列化的持久化策略时(比如MEMORY_ONLY_SER),Spark会将RDD中的每个 partition都序列化成一个大的字节数组。
Kryo序列化器介绍: 

Spark支持使用Kryo序列化机制。Kryo序列化机制,比默认的Java序列化机制,速度要快 ,序列化后的数据要更小,大概是Java序列化机制的1/10。所以Kryo序列化优化以后,可 以让网络传输的数据变少;在集群中耗费的内存资源大大减少。 

对于这三种出现序列化的地方,我们都可以通过使用Kryo序列化类库,来优化序列化和 反序列化的性能。Spark默认使用的是Java的序列化机制,也就是 ObjectOutputStream/ObjectInputStream API来进行序列化和反序列化。但是Spark同 时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序列化类库的性能要高很多。 官方介绍,Kryo序列化机制比Java序列化机制,性能高10倍左右。Spark之所以默认没有 使用Kryo作为序列化类库,是因为Kryo要求最好要注册所有需要进行序列化的自定义类 型,因此对于开发者来说,这种方式比较麻烦 
package com.shujia.optimize

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

object TestKryo {

  /**
    * 使用kryo序列化方式代替默认序列化方式(objectOutPutStream/objectInPutStream)
    * 性能提高10倍
    *
    *
    * spark  三个地方涉及到序列化
    *
    * 1、算子里面用到可外部变量
    * 2、RDD 类型为自定义类型,同时使用checkpoint 或者  使用shuffle类算子的时候会产生序列化
    * 3、 cache  SER
    */
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("app")
    //序列化方式
      .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
      //指定注册序列化的类,自定义
      .set("spark.kryo.registrator", "com.shujia.optimize.MyRegisterKryo")

    val sc: SparkContext = new SparkContext(conf)

    sc.setCheckpointDir("spark/data/checkpoint")

    val data: RDD[String] = sc.textFile("spark/data/students.txt")


    /**
      * 自定义对象比字符串赵勇内存更多
      * 因为自定义对象由对象头信息
      */
    var stuRDD: RDD[Student] = data
      .map(_.split(","))
      .map(line => Student(line(0), line(1), line(2).toInt, line(3), line(4)))


    ///checkpoint  产生序列化
    stuRDD.checkpoint()

    //shuffle 类算子产生序列化
    stuRDD.map(s => (s.id, s)).groupByKey().foreach(println)

    //对RDD  持久化会产生序列化
    stuRDD = stuRDD.persist(StorageLevel.MEMORY_AND_DISK_SER)
    stuRDD.foreach(println)


    while (true) {
    }

  }
}

case class Student(id: String, name: String, age: Int, gender: String, clazz: String)
  • 序列化时用到的自定义的一个类
package com.shujia.optimize

import com.twitter.chill.Kryo
import org.apache.spark.serializer.KryoRegistrator

class MyRegisterKryo extends KryoRegistrator {
  //注册类
  override def registerClasses(kryo: Kryo): Unit = {
    //注册Student类
    //注册之后student类序列化的时候就会使用kryo
    //classOf 获取类对象
    kryo.register(classOf[Student])
    kryo.register(classOf[Int])
    kryo.register(classOf[String])
    //可以同时注册多个
    //    kryo.register()
  }
}
  • 优化数据结构
Java中,有三种类型比较耗费内存: 
1、对象,每个Java对象都有对象头、引用等额外的信息,因此比较占用内存空间。 
2、字符串,每个字符串内部都有一个字符数组以及长度等额外信息。 
3、集合类型,比如HashMap、LinkedList等,因为集合类型内部通常会使用一些内部类来 封装集合元素,比如Map.Entry。 

因此Spark官方建议,在Spark编码实现中,特别是对于算子函数中的代码,尽 量不要使用上述三种数据结构,尽量使用字符串替代对象,使用原始类型(比如 Int、Long)替代字符串,使用数组替代集合类型,这样尽可能地减少内存占用 ,从而降低GC频率,提升性能。 
  • 使用高性能的库fastutil
fastutil介绍: 
1、fastutil是扩展了Java标准集合框架(Map、List、Set;HashMap、ArrayList、 HashSet)的类库,提供了特殊类型的map、set、list和queue; 
2、fastutil能够提供更小的内存占用,更快的存取速度;我们使用fastutil提供的集合类,来 替代自己平时使用的JDK的原生的Map、List、Set,好处在于,fastutil集合类,可以减 小内存的占用,并且在进行集合的遍历、根据索引(或者key)获取元素的值和设置元素 的值的时候,提供更快的存取速度; 
3、fastutil最新版本要求Java 7以及以上版本; 
4、fastutil的每一种集合类型,都实现了对应的Java中的标准接口(比如fastutil的map,实 现了Java的Map接口),因此可以直接放入已有系统的任何代码中。 
5、fastutil的每一种集合类型,都实现了对应的Java中的标准接口(比如fastutil的 map,实现了Java的Map接口),因此可以直接放入已有系统的任何代码中。
  • 参数优化

在这里插入图片描述

  • 数据本地性
  1. Application任务执行流程:

在Spark Application提交后,Driver会根据action算子划分成一个个的job,然后对每一 个job划分成一个个的stage,stage内部实际上是由一系列并行计算的task组成的,然后 以TaskSet的形式提交给你TaskScheduler,TaskScheduler在进行分配之前都会计算出 每一个task最优计算位置。Spark的task的分配算法优先将task发布到数据所在的节点上 ,从而达到数据最优计算位置。

  1. 数据本地化级别:
  • PROCESS_LOCAL
  • NODE_LOCA
  • NO_PREF
  • RACK_LOCAL
  • ANY
  1. 配置参数:
  • spark.locality.wait
  • spark.locality.wait.process
  • spark.locality.wait.node
  • spark.locality.wait.rack

在这里插入图片描述

  • JVM调优
概述:
1、Spark task执行算子函数,可能会创建很多对象,这些对象,都是要放入JVM年轻代中 
2、RDD的缓存数据也会放入到堆内存中 

配置:
spark.storage.memoryFraction 默认是0.6
  • shuffle调优
概述: 
reduceByKey:要把分布在集群各个节点上的数据中的同一个key,对应的values,都给 集中到一个节点的一个executor的一个task中,对集合起来的value执行传入的函数进行 reduce操作,最后变成一个value 

配置 
spark.shuffle.manager, 默认是sort 
spark.shuffle.consolidateFiles,默认是false 
spark.shuffle.file.buffer,默认是32k 
spark.shuffle.memoryFraction,默认是0.2 
  • 调节Executor堆外内存
问题原因: 
1、Executor由于内存不足或者对外内存不足了,挂掉了,对应的Executor上面的block manager也挂掉了,找不到对应的shuffle map output文件,Reducer端不能够拉取数据 
2、Executor并没有挂掉,而是在拉取数据的过程出现了问题

上述情况下,就可以去考虑调节一下executor的堆外内存。也许就可以避免报错; 
一定要注意,spark-submit脚本里面,去用--conf的方式去添加配置。

解决办法: 
1、yarn下:--conf spark.yarn.executor.memoryOverhead=2048 单位M 
2、standlone下:--conf spark.executor.memoryOverhead=2048单位M 

默认情况下,这个堆外内存上限默认是每一个executor的内存大小的10%;真正处理大数据的时候, 这里都会出现问题,导致spark作业反复崩溃,无法运行;此时就会去调节这个参数,到至少1G (1024M),甚至说2G、4G 

调节等待时长 :
1、executor在进行shuffle write,优先从自己本地关联的BlockManager中获取某份数据如果本地 block manager没有的话,那么会通过TransferService,去远程连接其他节点上executor的block manager去获取,尝试建立远程的网络连接,并且去拉取数据 
2、频繁的让JVM堆内存满溢,进行垃圾回收。正好碰到那个exeuctor的JVM在垃圾回收。处于垃圾回 收过程中,所有的工作线程全部停止;相当于只要一旦进行垃圾回收,spark / executor停止工作, 无法提供响应,spark默认的网络连接的超时时长,是60s;如果卡住60s都无法建立连接的话,那 么这个task就失败了。 

解决?--conf spark.core.connection.ack.wait.timeout=300 

  • 参数调优模板:
spark-submit 
--class com.shujia.*.* 
--master yarn-client 
--num-executors 100   // 资源大小,数据量
--executor-memory 8G // 广播变量,cache数据量
--executor-cores 4  
--driver-memory 2G
--conf spark.default.parallelism=100 // shuffle之后rdd分区数
--conf spark.storage.memoryFraction=0.4 // rdd持久化内存占比
--conf spark.shuffle.memoryFraction0.4 //rddshuffle内存占比
--conf spark.locality.wait=10 //task在Executor中的等待的超时时间
--conf spark.shuffle.file.buffer=64k
--conf spark.yarn.executor.memoryOverhead=2048M
--conf spark.network.timeout=600s


任务使用的资源
400C   800G
  • 数据倾斜的原因:

在这里插入图片描述

1、key 分布不均

2、产生了shuffle。

两个条件都要满足才会产生

  • 1、使用Hive ETL预处理数据
方案适用场景:如果导致数据倾斜的是Hive表。如果该Hive表中的数据本身很不均匀(比如某个 key对应了100万数据,其他key才对应了10条数据),而且业务场景需要频繁使用Spark对Hive表 执行某个分析操作,那么比较适合使用这种技术方案。 

方案实现思路:此时可以评估一下,是否可以通过Hive来进行数据预处理(即通过Hive ETL预先对 数据按照key进行聚合,或者是预先和其他表进行join),然后在Spark作业中针对的数据源就不是 原来的Hive表了,而是预处理后的Hive表。此时由于数据已经预先进行过聚合或join操作了,那么 在Spark作业中也就不需要使用原先的shuffle类算子执行这类操作了。 

方案实现原理:这种方案从根源上解决了数据倾斜,因为彻底避免了在Spark中执行shuffle类算子 ,那么肯定就不会有数据倾斜的问题了。但是这里也要提醒一下大家,这种方式属于治标不治本。 因为毕竟数据本身就存在分布不均匀的问题,所以Hive ETL中进行group by或者join等shuffle操作 时,还是会出现数据倾斜,导致Hive ETL的速度很慢。我们只是把数据倾斜的发生提前到了Hive ETL中,避免Spark程序发生数据倾斜而已。
  • 2、过滤少数导致倾斜的key
方案适用场景:如果发现导致倾斜的key就少数几个,而且对计算本身的影响并不大的话,那么很 适合使用这种方案。比如99%的key就对应10条数据,但是只有一个key对应了100万数据,从而导 致了数据倾斜。 

方案实现思路:如果我们判断那少数几个数据量特别多的key,对作业的执行和计算结果不是特别 重要的话,那么干脆就直接过滤掉那少数几个key。比如,在Spark SQL中可以使用where子句过滤 掉这些key或者在Spark Core中对RDD执行filter算子过滤掉这些key。如果需要每次作业执行时, 动态判定哪些key的数据量最多然后再进行过滤,那么可以使用sample算子对RDD进行采样,然后 计算出每个key的数量,取数据量最多的key过滤掉即可。 

方案实现原理:将导致数据倾斜的key给过滤掉之后,这些key就不会参与计算了,自然不可能产生 数据倾斜。 
package com.shujia.optimize

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

object FilterKey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("app")
    val sc: SparkContext = new SparkContext(conf)
    val lines: RDD[String] = sc.textFile("spark/data/word")

    println("第一个RDD分区数量:" + lines.getNumPartitions)
    val countRDD: RDD[(String, Int)] = lines
      .flatMap(_.split(","))
      .map((_, 1))
      .groupByKey()
      .map(x => (x._1, x._2.toList.sum))
    println("聚合之后RDD分区的数量" + countRDD.getNumPartitions)
   //countRDD.foreach(println)


        /**
          * 采样key  ,g过滤掉导致数据倾斜并且对业务影响不大的key
          */
        val wordRDD: RDD[(String, Int)] = lines
          .flatMap(_.split(","))
          .map((_, 1))
        val top1: Array[(String, Int)] = wordRDD
          .sample(true, 0.1)
          .reduceByKey(_ + _)
          .sortBy(-_._2)
          .take(1)
        //导致数据倾斜额key
        val key: String = top1(0)._1
        //过滤导致倾斜的key
        wordRDD
          .filter(t => !key.equals(t._1))
          .groupByKey()
          .map(x => (x._1, x._2.toList.sum))
          .foreach(println)

    while (true) {
    }

  }
}
  • 3、提高shuffle操作的并行度
方案实现思路:在对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如 reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量。对于 Spark SQL中的shuffle类语句,比如group by、join等,需要设置一个参数,即 spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,该值默认是200,对于很 多场景来说都有点过小。 

方案实现原理:增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个 task,从而让每个task处理比原来更少的数据。举例来说,如果原本有5个key,每个key对应10条 数据,这5个key都是分配给一个task的,那么这个task就要处理50条数据。而增加了shuffle read task以后,每个task就分配到一个key,即每个task就处理10条数据,那么自然每个task的执行时 间都会变短了。
  • 4、双重聚合
方案适用场景:对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by 语句进行分组聚合时,比较适用这种方案。 

方案实现思路:这个方案的核心实现思路就是进行两阶段聚合。第一次是局部聚合,先给每个key 都打上一个随机数,比如10以内的随机数,此时原先一样的key就变成不一样的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着 对打上随机数后的数据,执行reduceByKey等聚合操作,进行局部聚合,那么局部聚合结果,就会 变成了(1_hello, 2) (2_hello, 2)。然后将各个key的前缀给去掉,就会变成(hello,2)(hello,2),再次 进行全局聚合操作,就可以得到最终结果了,比如(hello, 4)。 

方案实现原理:将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被 一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。 接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果 

package com.shujia.optimize

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

import scala.util.Random

object DoubleReduce {

  /**
    * 双重聚合
    * 一般适用于  业务不复杂的情况
    */
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("app")
    val sc: SparkContext = new SparkContext(conf)
    val lines: RDD[String] = sc.textFile("spark/data/word")

    val wordRDD: RDD[String] = lines
      .flatMap(_.split(","))
      .filter(!_.equals(""))

    // 对每一个key打上随机5以内前缀
    wordRDD.map(word => {
      val pix: Int = Random.nextInt(5)
      (pix + "-" + word, 1)
    })

      .groupByKey() //第一次聚合

      .map(t => (t._1, t._2.toList.sum))

      .map(t => {
        ///去掉随机前缀
        (t._1.split("-")(1), t._2)
      })

      .groupByKey() //第二次聚合

      .map(t => (t._1, t._2.toList.sum))
      .foreach(println)

    while (true) {
    }

  }
}
  • 5、将reduce join转为map join
方案适用场景:在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,而且join操作中 的一个RDD或表的数据量比较小(比如几百M或者一两G),比较适用此方案。 

方案实现思路:不使用join算子进行连接操作,而使用Broadcast变量与map类算子实现join操作, 进而完全规避掉shuffle类的操作,彻底避免数据倾斜的发生和出现。将较小RDD中的数据直接通过 collect算子拉取到Driver端的内存中来,然后对其创建一个Broadcast变量;接着对另外一个RDD 执行map类算子,在算子函数内,从Broadcast变量中获取较小RDD的全量数据,与当前RDD的每 一条数据按照连接key进行比对,如果连接key相同的话,那么就将两个RDD的数据用你需要的方式 连接起来。 

方案实现原理:普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉 取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个RDD是比较小的, 则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是map join,此时就不 会发生shuffle操作,也就不会发生数据倾斜 

package com.shujia.optimize

import java.util

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

import scala.util.Random

object DoubleJoin {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf().setAppName("app").setMaster("local")
    val sc = new SparkContext(conf)
    val dataList1 = List(
      ("java", 1),
      ("shujia", 2),
      ("shujia", 3),
      ("shujia", 1),
      ("shujia", 1))

    val dataList2 = List(
      ("java", 100),
      ("java", 99),
      ("shujia", 88),
      ("shujia", 66))

    val RDD1: RDD[(String, Int)] = sc.parallelize(dataList1)
    val RDD2: RDD[(String, Int)] = sc.parallelize(dataList2)

    val sampleRDD: RDD[(String, Int)] = RDD1.sample(false, 1.0)

    //skewedKey  导致数据倾斜的key   shujia
    val skewedKey: String = sampleRDD.map(x => (x._1, 1))
      .reduceByKey(_ + _)
      .map(x => (x._2, x._1))
      .sortByKey(ascending = false)
      .take(1)(0)._2


    //导致数据倾斜key的RDD
    val skewedRDD1: RDD[(String, Int)] = RDD1.filter(tuple => {
      tuple._1.equals(skewedKey)
    })

    //没有倾斜的key
    val commonRDD1: RDD[(String, Int)] = RDD1.filter(tuple => {
      !tuple._1.equals(skewedKey)
    })

    val skewedRDD2: RDD[(String, Int)] = RDD2.filter(tuple => {
      tuple._1.equals(skewedKey)
    })

    val commonRDD2: RDD[(String, Int)] = RDD2.filter(tuple => {
      !tuple._1.equals(skewedKey)
    })

    val n = 2

    //对产生数据倾斜的key 使用mapjoin

    val skewedMap: Map[String, Int] = skewedRDD2.collect().toMap

    val bro: Broadcast[Map[String, Int]] = sc.broadcast(skewedMap)

    val resultRDD1: RDD[(String, (Int, Int))] = skewedRDD1.map(kv => {
      val word: String = kv._1

      val i: Int = bro.value.getOrElse(word, 0)
      (word, (kv._2, i))
    })


    //没有数据倾斜的RDD  正常join
    val resultRDD2: RDD[(String, (Int, Int))] = commonRDD1.join(commonRDD2)

    //将两个结果拼接
    resultRDD1.union(resultRDD2)
      .foreach(println)

  }
}

在这里插入图片描述

  • 6、采样倾斜key并分拆join操作
方案适用场景:两个RDD/Hive表进行join的时候,如果数据量都比较大,无法采用“解决方案五 ”,那么此时可以看一下两个RDD/Hive表中的key分布情况。如果出现数据倾斜,是因为其中某一 个RDD/Hive表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有key都分布比较均 匀,那么采用这个解决方案是比较合适的。

方案实现思路: 
对包含少数几个数据量过大的key的那个RDD,通过sample算子采样出一份样本来,然后统计一下每个 key的数量,计算出来数据量最大的是哪几个key。 
然后将这几个key对应的数据从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上n以 内的随机数作为前缀,而不会导致倾斜的大部分key形成另外一个RDD。 
接着将需要join的另一个RDD,也过滤出来那几个倾斜key对应的数据并形成一个单独的RDD,将每条数 据膨胀成n条数据,这n条数据都按顺序附加一个0~n的前缀,不会导致倾斜的大部分key也形成另外一个 RDD。 
再将附加了随机前缀的独立RDD与另一个膨胀n倍的独立RDD进行join,此时就可以将原先相同的key打 散成n份,分散到多个task中去进行join了。 
而另外两个普通的RDD就照常join即可。 
最后将两次join的结果使用union算子合并起来即可,就是最终的join结果。 
  • 7、使用随机前缀和扩容RDD进行join
方案适用场景:如果在进行join操作时,RDD中有大量的key导致数据倾斜,那么进行分拆key也没 什么意义,此时就只能使用最后一种方案来解决问题了。 

方案实现思路: 
该方案的实现思路基本和“解决方案六”类似,首先查看RDD/Hive表中的数据分布情况,找到那个造成 数据倾斜的RDD/Hive表,比如有多个key都对应了超过1万条数据。 
然后将该RDD的每条数据都打上一个n以内的随机前缀。 
同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一 个0~n的前缀。 
最后将两个处理后的RDD进行join即可。 

方案实现原理:将原先一样的key通过附加随机前缀变成不一样的key,然后就可以将这些处理后的 “不同key”分散到多个task中去处理,而不是让一个task处理大量的相同key。该方案与“解决方 案六”的不同之处就在于,上一种方案是尽量只对少数倾斜key对应的数据进行特殊处理,由于处 理过程需要扩容RDD,因此上一种方案扩容RDD后对内存的占用并不大;而这一种方案是针对有大 量倾斜key的情况,没法将部分key拆分出来进行单独处理,因此只能对整个RDD进行数据扩容,对 内存资源要求很高。 

Spark Streaming

无界流有一个开始但没有定义的结束;

有界流具有定义的开始和结束。

流处理:数据采集 --> MQ(消息队列) --> 计算引擎 --> DB

批处理:数据采集 --> MQ(消息队列) --> DB(hdfs/hive/hbase) --> 计算引擎

在这里插入图片描述

package com.shujia.stream
//每隔5秒的结果是独立的,结果不会累加
import org.apache.spark.sql.SQLContext
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Duration, Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object Demo1WordCOunt {
  def main(args: Array[String]): Unit = {

    /**
      * 创建spark 执行环境
      */
    val conf: SparkConf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("stream")
    val sc = new SparkContext(conf)


    /**
      * 创建 spark streaming 执行环境
      *
      * 指定处理数据的间隔时间
      */
    val ssc = new StreamingContext(sc, Durations.seconds(5))

    //读取实时数据,比如消息队列
    //可以通过nc -lk 模拟一个消息队列。(再linux中安装yum install nc)
    val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)


    linesDS
      .flatMap(_.split(","))
      .map((_, 1))
      .reduceByKey(_ + _)	//reduceByKey: 只会统计当前batch
      .print()	//目前每一次结果都是独立的,不会有累加

    //启动spark streaming
    ssc.start()
    ssc.awaitTermination()//等待关闭
    ssc.stop()

  }

}
  • 有状态算子(结果会累加)
package com.shujia.stream
//只能做一些简单的累加,统计
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}

object Demo2UpdataStateByKey {
  def main(args: Array[String]): Unit = {


    val conf: SparkConf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("stream")
    val sc = new SparkContext(conf)

    val ssc = new StreamingContext(sc, Durations.seconds(5))


    //指定checkpoint地址
    ssc.checkpoint("spark/data/checkpoint")

    val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)

    val wordsDS: DStream[String] = linesDS.flatMap(_.split(","))

    val kvDS: DStream[(String, Int)] = wordsDS.map((_, 1))

    /**
      * updateStateByKey
      * 有状态算子(状态: 之前计算的结果/之前统计的单词的数量)
      */


    /**
      * seq:当前batch每个单词后面的1
      * opt: 计算每个单词的计算结果   (状态)
      * 返回值: 返回新的单词数量
      *
      *
      * 需要指定checkpoint的地址,用于保存计算的状态
      *
      */
    def udatteFun(seq: Seq[Int], opt: Option[Int]): Option[Int] = {
      //统计当前batch的单词的数量
      val currCount: Int = seq.sum
      //获取之前单词统计的结果
      val lastCount: Int = opt.getOrElse(0)
      //返回最新单词的数量
      Some(currCount + lastCount)
    }

    val countDS: DStream[(String, Int)] = kvDS.updateStateByKey(udatteFun)
	//打印
    countDS.print()
	//启动
    ssc.start()
    ssc.awaitTermination()
    ssc.stop()

  }

}
  • 窗口
package com.shujia.stream

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Duration, Durations, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}

object Demo3Window {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("stream")
    val sc = new SparkContext(conf)

    val ssc = new StreamingContext(sc, Durations.seconds(5))

    ssc.checkpoint("spark/data/checkpoint")

    val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)

    val wordsDS: DStream[String] = linesDS.flatMap(_.split(","))
    val kvDS: DStream[(String, Int)] = wordsDS.map((_, 1))


    /** reduceByKeyAndWindow
      * 窗口计算
      * 统计最近15秒单词的数量,每个10秒统计一次   --  滑动窗口
      *
      */

    /* val countDS: DStream[(String, Int)] =  kvDS.reduceByKeyAndWindow(
        (x: Int, y: Int) => x + y,
        Durations.seconds(15), //窗口大小
        Durations.seconds(5) //滑动时间
      )
      countDS.print()*/

    /**
      * 对窗口计算进行优化,避免重复计算
      *
      * 需要设置checkpoint将上一个窗口计算结果保存起来
      *
      */
    val countDS: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow(
      (x: Int, y: Int) => x + y, //累加
      (x: Int, y: Int) => x - y, //减去比上一个窗口多余的数据
      Durations.seconds(15), //窗口大小
      Durations.seconds(5) //滑动时间
    )

    countDS.filter(_._2 != 0).print()


    ssc.start()
    ssc.awaitTermination()
    ssc.stop()
  }

}
  • 缉查布控
package com.shujia.stream

import java.sql.{Connection, DriverManager, PreparedStatement, ResultSet}

import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}

import scala.collection.mutable.ListBuffer

object Demo4Bukong {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("stream")
    val sc = new SparkContext(conf)

    val ssc = new StreamingContext(sc, Durations.seconds(5))

    val linesDS: ReceiverInputDStream[String] = ssc.socketTextStream("master", 8888)


    /**
      * transform 将ds 转换成rdd , 每一个batch都会执行一次
      * transform 里面还是属于Driver端
      *
      */
    val filterDS: DStream[String] = linesDS.transform(rdd => {

      //从数据库中查询布控列表
      println("获取布控列表")
      val mdns: List[String] = getList

      //获取sparkContext
      val context: SparkContext = rdd.sparkContext

      /**
        * 动态修改广播变量,每隔5秒广播一次
        */
      //广播
      val bro: Broadcast[List[String]] = context.broadcast(mdns)

      //过滤数据
      val filterRDD: RDD[String] = rdd.filter(line => {
        val split: Array[String] = line.split(",")
        val mdn: String = split(0)
        bro.value.contains(mdn)
      })


      //返回一个rdd
      filterRDD
    })

    /**
      * foreachRDD : 将DS转换成rdd  没有返回值
      *
      */


    //保存数据到mysql
    filterDS.foreachRDD(rdd => {


      rdd.foreachPartition(iter => {


        /**
          * foreachPartition : 只为每一个分区创建一个链接
          *
          */

        //查询数据库获取布控列表
        Class.forName("com.mysql.jdbc.Driver")
        val con: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")


        //循环一个分区的数据,将数据保存到mysql中
        iter.foreach(line => {

          val stat: PreparedStatement = con.prepareStatement("insert into t_result(line) values(?)")

          stat.setString(1, line)

          stat.executeUpdate()

        })

        con.close()
      })

    })


    ssc.start()
    ssc.awaitTermination()
    ssc.stop()


  }

  /**
    * 获取布控列表的方法:
    */
  def getList: List[String] = {
    val mdns = new ListBuffer[String]
    //查询数据库获取布控列表
    Class.forName("com.mysql.jdbc.Driver")
    val con: Connection = DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")

    val stat: PreparedStatement = con.prepareStatement("select mdn from t_mdns")
    val resultSet: ResultSet = stat.executeQuery()
    while (resultSet.next()) {
      val mdn: String = resultSet.getString("mdn")
      mdns += mdn
    }

    con.close()

    //返回数据

    mdns.toList
  }

}
  • StructuredStreaming
package com.shujia.stream

import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.streaming.OutputMode
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}

object Demo5StructuredStreaming {

  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession
      .builder()
      .master("local[2]")
      .appName("ssc")
      .config("spark.sql.shuffle.partitions", 1)
      .getOrCreate()


    import spark.implicits._
    import org.apache.spark.sql.functions._

    val df: DataFrame = spark
      .readStream //读取实时数据
      .format("socket")
      .option("host", "master")
      .option("port", 8888)
      .load()


    val wordsDF: DataFrame = df.select(explode(split($"value", ",")) as "word")


    /**
      * DSL
      */
    //统计单词的数量
    /*val countDF: DataFrame = wordsDF
      .groupBy($"word")
      .agg(count($"word"))*/

    /**
      * 再流处理上写sql
      */
    wordsDF.createOrReplaceTempView("word")
    val countDF: DataFrame = spark.sql(
      """
        |select word,count(1) as c from word group by word
        |
      """.stripMargin)

    /**
      * outputMode
      * Update :每次只输出更新的数据
      * Complete: 每次输出所有数据
      * Append: 只适用于追加的数据,使用分组的算子不能再使用Append
      *
      */


    //在控制台打印
    /*countDF
      .writeStream
      .format("console") //输出到控制台
      .outputMode(OutputMode.Update())
      .start() //启动
      .awaitTermination() //等待关闭*/


    //将结果保存到mysql
    countDF
      .writeStream
      .outputMode(OutputMode.Complete())
      .foreachBatch((df, l) => {

        df.write
          .format("jdbc")
          .mode(SaveMode.Overwrite)
          .option("url", "jdbc:mysql://master:3306")
          .option("dbtable", "student.t_count")
          .option("user", "root")
          .option("password", "123456")
          .save()
      })
      .start() //启动
      .awaitTermination() //等待关闭

  }
}

机器学习

机器学习,是人工智能一个基本条件,是建立大数据基础之上。从数据中提取出模型,并可以利用模型对未知的数据做出预测。

  • 监督学习

定义:输入数据是由输入特征值和目标值所组成。函数的输出可以是一个连续的值(称为回归),或是输出是 有限个离散值(称作分类)。

算法:分类( k-近邻算法、贝叶斯分类、决策树与随机森林、逻辑回归、神经网络) 回归(线性回归、岭回归。

  • 无监督学习

定义:输入数据是由输入特征值所组成。

算法:聚类(k-means)

在这里插入图片描述

  • 人体指标
package com.shujia.mllib

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.ml.feature.LabeledPoint
import org.apache.spark.ml.linalg
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.mllib.regression
import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.rdd.RDD

object Demo1Vertor {

  def main(args: Array[String]): Unit = {

    //向量: 有方向有大小。只能存double类型

    //稠密向量
    val dense: linalg.Vector = Vectors.dense(Array(1.0, 2.3, 4.0, 5.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))
    println(dense)

    //稀疏向量
    /**
      * 稀疏向量再0 比较多的情况下占用的空间更小
      */
    val sparse: linalg.Vector = Vectors.sparse(17, Array(0, 1, 2, 3, 7), Array(1.0, 2.3, 4.0, 5.0, 4.0))
    println(sparse)

    /**
      * 相互转换
      */
    println(sparse.toDense)


    /**
      * 标记数据, 一条训练数据
      */
    val pos = LabeledPoint(1.0, Vectors.dense(1.0, 0.0, 3.0))
    println(pos)


    val neg = LabeledPoint(0.0, Vectors.sparse(3, Array(0, 2), Array(1.0, 3.0)))
    println(neg)



    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("svm")
    val sc = new SparkContext(conf)

    /**
      * 读取SVM格式的数据
      */
    val data: RDD[regression.LabeledPoint] = MLUtils.loadLibSVMFile(sc,"spark/data/人体指标.txt")
    data.foreach(println)
  }

}
package com.shujia.mllib

import org.apache.spark.ml.classification.{LogisticRegression, LogisticRegressionModel}
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

object Demo2Person {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local")
      .appName("person")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()

    import spark.implicits._
    import org.apache.spark.sql.functions._


    /**
      * 1、特征工程
      * 将原始数据转换成公式可以识别的数据
      *
      *
      * 我们这里已经是被做完特征工程的数据了
      */

    //通过spark sql 读取svm格式的数据
    val personDF: DataFrame = spark
      .read
      .format("libsvm")
      .load("spark/data/人体指标.txt")

    personDF.show(false)

    /**
      * 2、切分训练集和测试集
      * 训练集--- 训练模型
      * 测试集-- 测试模型准确率
      *
      * 一般训练集0.7  测试集0.3
      *
      */
    val splitDF: Array[Dataset[Row]] = personDF.randomSplit(Array(0.7, 0.3))
    //训练集
    val trainDF: Dataset[Row] = splitDF(0)
    //测试集
    val testDF: Dataset[Row] = splitDF(1)


    /**
      * 2、选择算法
      *
      * 结果是离散值-- -分类
      *
      * 逻辑回归
      *
      */

    //构建算法执行参数
    val logisticRegression: LogisticRegression = new LogisticRegression()
      .setMaxIter(10) //最大迭代次数
      .setFitIntercept(true) //是否有截距


    /**
      * 3、训练模型
      * 将训练集带入算法训练模型 --  确定k和b
      *
      * 模型包含公式和参数
      */
    val model: LogisticRegressionModel = logisticRegression.fit(trainDF)


    /**
      * 4、模型评估
      * 使用模型预测测试集的数据,判断和原始标记是否一直,计算准确率
      */
    val frame: DataFrame = model.transform(testDF)

    /**
      * 计算准确率
      */
    val result: DataFrame = frame.select(sum(when($"label" === $"prediction", 1).otherwise(0)) / count($"label"))
    result.show()

    /**
      * 如果准确率还可以就保存模型
      * 保存再hdfs
      */
    model.save("spark/data/model")
  }

}
package com.shujia.mllib

import org.apache.spark.ml.classification.LogisticRegressionModel
import org.apache.spark.ml.linalg
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.sql.SparkSession

object Demo3Model {

  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local")
      .appName("person")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()


    /**
      * 模型使用
      */
    val model: LogisticRegressionModel = LogisticRegressionModel.load("spark/data/model")


    /**
      * 使用模型进行预测
      *
      * 1:4.2 2:3.0 3:2.4 4:97.3 5:57.7 6:58.5 7:89
      *
      * 1 1:5.7 2:4.3 3:3.5 4:130.1 5:85.9 6:84.0 7:65
      */

    //    val vector: linalg.Vector = Vectors.dense(Array(4.2, 3.0, 2.4, 97.3, 57.7, 58.5, 89))
    val vector: linalg.Vector = Vectors.dense(Array(5.7, 4.3, 3.5, 130.1, 85.9, 84.0, 65))

    //预测
    val d: Double = model.predict(vector)
    println(d)

  }

}
  • Image模型训练
package com.shujia.mllib

import org.apache.spark.ml.linalg
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
import org.apache.spark.ml.linalg.{SparseVector, Vectors}

object Demo4ReadImage {
  def main(args: Array[String]): Unit = {

    /**
      * 特征工程
      */
    val spark: SparkSession = SparkSession.builder()
      .master("local[8]")
      .appName("person")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()


    val images: DataFrame = spark
      .read
      .format("image")
      .load("D:\\课件\\机器学习数据\\手写数字\\train")

    images.printSchema()
    import spark.implicits._

    val data: DataFrame = images
      .select($"image.origin", $"image.data")
      .as[(String, Array[Byte])]
      .map {
        case (name: String, data: Array[Byte]) => {
          val ints: Array[Int] = data.map(b => b.toInt)

          //将数据归一化
          val result: Array[Double] = ints.map(i => {
            if (i < 0) {
              1.0
            } else {
              0.0
            }
          })

          //将数组转换成向量
          val fea: linalg.Vector = Vectors.dense(result)

          val filename: String = name.split("/").last

          (filename, fea)
        }
      }.toDF("name", "features")


    /**
      * 读取标签数据
      */
    val labelDF: DataFrame = spark
      .read.format("csv")
      .option("sep", " ")
      .schema("name STRING, label DOUBLE")
      .load("D:\\课件\\机器学习数据\\手写数字\\train.txt")


    val trainDF: DataFrame = data
      .join(labelDF, "name")
      .select("label", "features")


    trainDF
      .write
      .format("libsvm")
      .mode(SaveMode.Overwrite)
      .save("spark/data/images")


  }

}
  • 训练模型
package com.shujia.mllib

import org.apache.spark.ml.classification.{LogisticRegression, LogisticRegressionModel}
import org.apache.spark.sql.functions.{count, sum, when}
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

object DEmo5TrainModel {

  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local[8]")
      .appName("person")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()


    /**
      * 1、读取数据
      */
    val data: DataFrame = spark
      .read
      .format("libsvm")
      .load("spark/data/images")

    /**
      * 2、切分训练集和测试集
      */
    val split: Array[Dataset[Row]] = data.randomSplit(Array(0.7, 0.3))

    val train: Dataset[Row] = split(0)
    val test: Dataset[Row] = split(1)

    //构建算法执行参数
    val logisticRegression: LogisticRegression = new LogisticRegression()
      .setMaxIter(10) //最大迭代次数
      .setFitIntercept(true) //是否有截距


    /**
      * 训练模型
      *
      * 通过spark 进行分布式迭代计算
      */
    val model: LogisticRegressionModel = logisticRegression.fit(train)


    /**
      * 4、模型评估
      * 使用模型预测测试集的数据,判断和原始标记是否一直,计算准确率
      */
    val frame: DataFrame = model.transform(test)

    /**
      *
      * 计算准确率
      */
    import spark.implicits._
    import org.apache.spark.sql.functions._

    val result: DataFrame = frame.select(sum(when($"label" === $"prediction", 1).otherwise(0)) / count($"label"))
    result.show()

    /**
      * 保存模型
      */
    model.save("spark/data/image_model")
  }

}
  • 识别图片
package com.shujia.mllib

import org.apache.spark.ml.classification.LogisticRegressionModel
import org.apache.spark.ml.linalg.{SparseVector, Vectors}
import org.apache.spark.sql.{DataFrame, SparkSession}

object Demo6ProImage {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local[8]")
      .appName("person")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()

    val image: DataFrame = spark
      .read
      .format("image")
      .load("D:\\课件\\机器学习数据\\手写数字\\131.jpg")

    import spark.implicits._

    val data: DataFrame = image
      .select($"image.origin", $"image.data")
      .as[(String, Array[Byte])]
      .map {
        case (name: String, data: Array[Byte]) => {
          val ints: Array[Int] = data.map(b => b.toInt)

          //将数据归一化
          val result: Array[Double] = ints.map(i => {
            if (i < 0) {
              1.0
            } else {
              0.0
            }
          })

          //将数组转换成向量
          val fea: SparseVector = Vectors.dense(result).toSparse

          val filename: String = name.split("/").last

          (filename, fea)
        }
      }.toDF("name", "features")


    /**
      * 加载模型
      */
    val model: LogisticRegressionModel = LogisticRegressionModel.load("spark/data/image_model")

    //预测
    val frame: DataFrame = model.transform(data)
    frame.show()
  }

}
package com.shujia.mllib

import org.apache.spark.sql.{DataFrame, Dataset, SaveMode, SparkSession}
import org.apache.spark.ml.clustering.{KMeans, KMeansModel}

object Demo7Kmeans {

  def main(args: Array[String]): Unit = {

    /**
      * 据类算法-kmeana   无监督机器学习
      */
    val spark: SparkSession = SparkSession.builder()
      .master("local[8]")
      .appName("person")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()

    import spark.implicits._

    val pointDF: DataFrame = spark.read.format("csv")
      .schema("x DOUBLE, y DOUBLE")
      .load("spark/data/kmeans.txt")

    val ds: Dataset[(Double, Double)] = pointDF.as[(Double, Double)]

    //将每一行转换成一个数组
    val train: DataFrame = ds.map(point => Array(point._1, point._2)).toDF("features")

    //构建算法
    val means: KMeans = new KMeans().setK(2)
    //训练模型
    val model: KMeansModel = means.fit(train)
    //聚类
    val frame: DataFrame = model.transform(train)

    frame.show(10000)
  }

}
  • 文本分类
package com.shujia.mllib

import org.apache.spark.ml.classification.{NaiveBayes, NaiveBayesModel}
import org.apache.spark.ml.feature.{HashingTF, IDF, IDFModel, Tokenizer}
import org.apache.spark.sql.functions.{count, sum, when}
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

object Demo8TextClass {
  def main(args: Array[String]): Unit = {

    val spark: SparkSession = SparkSession.builder()
      .master("local[8]")
      .appName("person")
      .config("spark.sql.shuffle.partitions", 2)
      .getOrCreate()

    import spark.implicits._

    val texts: DataFrame = spark.read
      .format("csv")
      .schema("label DOUBLE, text STRING")
      .option("sep", "\t")
      .load("spark/data/train.txt")

    /**
      * 通过ik分词器对文本进行分词
      */
    val ikDF: DataFrame = texts.as[(Double, String)]
      .map {
        case (label: Double, text: String) =>
          //对文本进行分词
          val words: String = Demo9IK.fit(text).mkString(" ")

          (label, words)
      }
      .filter(_._2.nonEmpty)
      .toDF("label", "text")


    /**
      * 使用英文分词器对数据做一次转换---必须有
      */
    val tokenizer: Tokenizer = new Tokenizer()
      .setInputCol("text")
      .setOutputCol("words")

    val wordsData: DataFrame = tokenizer.transform(ikDF)

    /**
      * 增加TF
      */
    val hashingTF: HashingTF = new HashingTF()
      .setInputCol("words")
      .setOutputCol("rawFeatures")

    val featurizedData: DataFrame = hashingTF.transform(wordsData)


    /**
      * 增加idf
      */
    val idf: IDF = new IDF()
      .setInputCol("rawFeatures")
      .setOutputCol("features")

    //训练idf的模型
    val idfModel: IDFModel = idf.fit(featurizedData)
    //训练集
    val trainDF: DataFrame = idfModel.transform(featurizedData)
    /**
      * 切分训练集和测试集
      */
    val split: Array[Dataset[Row]] = trainDF.randomSplit(Array(0.7, 0.3))

    val train: Dataset[Row] = split(0)
    val test: Dataset[Row] = split(1)


    /**
      * 选择算法
      *
      * 贝叶斯分类---一般用于文本分类
      */
    val naiveBayes = new NaiveBayes()
    //训练模型
    val model: NaiveBayesModel = naiveBayes.fit(train)
    /**
      * 模型评估
      */
    val frame: DataFrame = model.transform(test)

    val result: DataFrame = frame.select(sum(when($"label" === $"prediction", 1).otherwise(0)) / count($"label"))
    result.show()

    /**
      * 保存模型
      */
    idfModel.save("spark/data/idfmodel")
    model.save("spark/data/NaiveBayesModel")
  }

}
package com.shujia.mllib

import java.io.StringReader
import org.wltea.analyzer.core.{IKSegmenter, Lexeme}
import scala.collection.mutable.ListBuffer

object Demo9IK {

  def main(args: Array[String]): Unit = {

    val text = "数加学院牛逼"
    println(fit(text))

  }

  def fit(text: String): List[String] = {

    val words = new ListBuffer[String]

    val sr = new StringReader(text)
    val ik = new IKSegmenter(sr, true)

    //取第一个次
    var lexeme: Lexeme = ik.next()

    while (lexeme != null) {
      val word: String = lexeme.getLexemeText
      words += word
      //取下一个
      lexeme = ik.next()
    }
    words.toList
  }
}

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值