Spark组件(sql,streaming)及spark优化

一.sparkSQL和Spark streaming概念

1.Spark SQL和Spark Streaming都是Apache Spark提供的重要组件,用于不同的数据处理和分析需求。

2.Spark SQL是Apache Spark中用于处理结构化数据的模块,它提供了用于数据查询、数据处理和数据分析的高级API。Spark SQL支持使用SQL查询、DataFrame API进行数据操作。Spark Streaming是Apache Spark中用于实时流数据处理的组件,它提供了高级别的API来处理实时数据流,允许以微批次的方式处理连续流数据。

二.Spark sql核心数据结构

Spark sql: SparkSession 核心数据结构:DataFrame

object Demo1WordCount {
  /**
   * spark sql 处理数据的步骤
   * 1.读取数据源
   * 2.将读取到的DF注册成一个临时视图
   * 3.使用sparkSession的sql函数,编写sql语句的操作临时视图,返回的依旧是一个DataFrame
   * 4.将结果写到hdfs上
   */
  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local")
      .appName("sql语法风格编写WordCount")
      .getOrCreate()

    /**
     * spark sql 是spark core的上层api,如果要想使用rdd的编程
     * 可以直接通过sparkSession获取sparkConText对象
     */
//        val context: SparkContext = sparkSession.sparkContext
    //    spark sql的核心数据类型是DataFrame
val df1: DataFrame = sparkSession.read
  .format("csv") // 读取csv格式的文件,但是实际上这种做法可以读取任意分隔符的文本文件
  .option("sep", "\n") //指定读取数据的列与列之间的分隔符
  .schema("line STRING") // 指定表的列字段 包括列名和列数据类型
  .load("spark/data/wcs/words.txt")

    /**
     * sql语句是无法直接作用在dataFrame上面的
     * 需要提前将使用的sql分析的dataFrame注册成一张表(临时视图)
     */
 df1.createOrReplaceTempView("wcs")

三.sparkSQL

3.1、DSLWordCount

object sparkDemo2DSLWordCount {
  def main(args: Array[String]): Unit = {
    //创建SparkSession对象
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local")
      .appName("DSL语法编写sparkSQL")
      .getOrCreate()

    val df1: DataFrame = sparkSession.read
      .format("csv")
      .schema("line STRING")
      .option("sep", "\n")
      .load("spark/data/wcs/words.txt")

    /**
     * 如果要想使用DSL语法编写spark sql的话,需要导入两个隐式转换
     */
    import org.apache.spark.sql.functions._
    import sparkSession.implicits._

    //老版本聚合操作
    //    df1.select(explode(split($"line","\\|")) as "word")
    //      .groupBy($"word")
    //      .count().show()

    //新版本聚合操作
    val resDF: DataFrame = df1.select(explode(split($"line", "\\|")) as "word")
      .groupBy($"word")
      .agg(count($"word") as "counts")

    resDF.repartition(1)
      .write
      .format("csv")
      .option("sep","\t")
      .mode(SaveMode.Overwrite)
      .save("spark/data/sqlOut2")
  }

}

3.2、DSL语法函数

object Demo3DSLApi {
  def main(args: Array[String]): Unit = {
    //创建SparkSession对象
    val sparkSession: SparkSession = SparkSession.builder()
      .config("spark.sql.shuffle.partitions", "1")
      .master("local")
      .appName("DSL语法风格编写spark sql")
      .getOrCreate()

    /**
     * 如果要想使用DSL语法编写spark sql的话,需要导入两个隐式转换
     */
    //将sql中的函数,封装成spark程序中的一个个的函数直接调用,以传参的方式调用
    import org.apache.spark.sql.functions._
    //主要作用是,将来可以在调用的函数中,使用$函数,将列名字符串类型转成一个ColumnName类型
    //而ColumnName是继承自Column类的
    import sparkSession.implicits._

    /**
     * 读取json数据文件,转成DF
     * 读取json数据的时候,是不需要指定表结构,可以自动根据json的键值来构建DataFrame
     */
   
    //df1.show(30,truncate = false)

    /**
     * DSL语法的第一个函数 select
     * 类似于纯sql语法中的select关键字,传入要查询的列
     */
    select name,clazz from xxx;
    df1.select("name","clazz").show()
    df1.select($"name",$"age",$"gender").show()

    //查询每个学生的姓名,原本的年龄,年龄+1
    /**
     * 与select功能差不多的查询函数
     * 如果要以传字符串的形式给到select的话,并且还想对列进行表达式处理的话
     * 可以使用selectExpr函数
     */
//    df1.selectExpr("name","age","age+1 as new_age").show()

    //如果要想使用select函数查询的时候对列做操作的话,可以使用$函数将列变成一个对象
    df1.select($"name",$"age",$"age"+1 as "xde_age").show()


    /**
     * DSL语法函数:where
     * === : 类似于sql中的= 等于某个值
     * =!= : 类似于sql中的!= 或者<>  不等于某个值
     */
    //df1.where("gender='男‘").show()
    //df1.where("gender='男' and substring(clazz,0,2)='理科'").show()

    //建议使用隐式转换中的功能进行处理过滤
    //过滤出男生且理科的
    df1.where($"gender"==='男' and substring($"clazz",0,2)==="理科").show()
    //过滤出女生且文科的
    df1.where($"gender"=!='男' and substring($"clazz",0,2)==="文科").show()

    /**
     * DSL语法函数:groupBy
     *
     * 非分组字段是无法出现在select查询语句中的
     */
    //查询每个班级的人数
    df1.groupBy("clazz")
      .agg(count("clazz")as "counts")
      .show()

    /**
     * DSL语法函数:orderBy
     */
      df1.groupBy("clazz")
        .agg(count("clazz") as "counts")
        .orderBy($"counts".desc)
        .show(3)

    /**
     * DSL语法函数: join
     */
    val df2: DataFrame = sparkSession.read
      .format("csv")
      .option("sep", ",")
      .schema("sid STRING,subject_id STRING,score INT")
      .load("spark/data/score.txt")

    // 关联的字段名不一样的情况
    df2.join(df1,$"id"===$"sid","inner")
      .select("id","name","age","gender","clazz","subject_id","score")
      .show(10)

    // 关联的字段名一样的情况
    val df3: DataFrame = sparkSession.read
      .format("csv")
      .option("sep", ",")
      .schema("id STRING,subject_id STRING,score INT")
      .load("spark/data/score.txt")

    df3.join(df1,"id")
      .select("id","name","age","gender","clazz","subject_id","score")
      .show(20)

    //如果关联的字段名一样且想使用其他连接方式的话,可以将字段名字用Seq()传入,同时可以传连接方式
    df3.join(df1,Seq("id"),"right")
      .select("id", "name", "age", "gender", "clazz", "subject_id", "score")
      .show(10)


    /**
     * DSL语法函数: 开窗
     * 无论是在纯sql中还是在DSL语法中,开窗是不会改变原表条数
     */
   
    //DSL语法实现
    //计算每个班级总分前3的学生
    df3.groupBy("id")
      .agg(sum("score")as "sumScore")
      .join(df1,"id")
      .select($"id",$"name",$"clazz",$"sumScore",row_number() over Window.partitionBy("clazz").orderBy($"sumScore".desc) as "rn")
      .where($"rn"<=3)
      .write
      .format("csv")
      .mode(SaveMode.Overwrite)
      .save("spark/data/sqlOut3")
  }
}

3.3、sparkSQL读写数据

object Demo4SourceApi {
  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local")
      .appName("sourceAPI读写")
      .config("spark.sql.shuffle.partitions", 1)
      .getOrCreate()

    //调用隐式转换
    import org.apache.spark.sql.functions._
    import sparkSession.implicits._

    "=================读写csv格式数据===================="
    //如果直接调用csv函数读取数据的话,无法做表结构设置
//    sparkSession.read
//      .csv("spark/data/test1.csv")//_c0 ,_c1, -c2

    //使用format的形式读取数据的同时可以设置表结构
//    val df1: DataFrame = sparkSession.read
//      .format("csv")
//      .schema("id STRING,name STRING,age INT")
//      .load("spark/data/test1.csv")
//    df1.show()

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

    val resDF1: DataFrame = sparkSession.sql(
      """
        |select
        |clazz,
        |count(1) as counts
        |from
        |students
        |group by clazz
        |""".stripMargin
    )
    // resDF1.show()
    // 以csv格式写出到磁盘文件夹中
    resDF1.write
      .format("csv")
      .mode(SaveMode.Overwrite)
      .save("spark/data/sqlOut4")

    /**
     *  ====================读写json格式的数据======================
     */
    val df2: DataFrame = sparkSession.read
      .json("spark/data/students.json")

    df2.groupBy("age")
      .agg(count("age") as "counts")
      .write
      .json("spark/data/sqlOut5")

    /**
     * =================读写parquet格式的数据====================
     *
     * parquet格式的文件存储,是由[信息熵]决定的
     */
    val df3: DataFrame = sparkSession.read
      .json("spark/data/students2.json")

    //以parquet格式写出去
    df3.write
      .parquet("spark/data/sqlOut6")

    /**
     * ====================读写orc格式的数据=====================
     */
    val df4: DataFrame = sparkSession.read
      .json("spark/data/students2.json")
      df4.write
        .orc("spark/data/sqlOut7")

    sparkSession.read
      .orc("spark/data/sqlOut7/part-00000-c2fe2d2b-710d-4498-831f-7627e3057b26-c000.snappy.orc")
      .show()

    /**
     * ==========================读写jdbc格式的数据======================
     *
     */
    sparkSession.read
      .format("jdbc")
      .option("url","jdbc:mysql://192.168.236.100:3306/bigdata_30?useUnicode=true&characterEncoding=UTF-8&useSSL=false")
      .option("dbtable","bigdata_30.tmp")
      .option("user","root")
      .option("password","123456")
      .load()
      .show(10,truncate = false)
  }

}

3.4、RDD与DataFrame转换

  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local")
      .appName("rdd与df之间的转换")
      .config("spark.sql.shuffle.partitions", "1")
      .getOrCreate()

    //通过SparkSession获取sparkContext对象
    val sparkContext: SparkContext = sparkSession.sparkContext


    import sparkSession.implicits._
    
    val linesRDD: RDD[String] 
 =sparkContext.textFile("spark/data/students.txt")
    val stuRDD: RDD[(String, String, String, String, String)] = linesRDD.map((line: String) => {
      line.split(",") match {
        case Array(id: String, name: String, age: String, gender: String, clazz: String) =>
          (id, name, age, gender, clazz)
      }
    })
    val resRDD1: RDD[(String, Int)] = stuRDD.groupBy(_._5)
      .map((kv: (String, Iterable[(String, String, String, String, String)])) => {
        (kv._1, kv._2.size)
      })
    val df1: DataFrame = resRDD1.toDF
    val df2: DataFrame = df1.select($"_1" as "clazz", $"_2" as "counts")

       // DataFrame->RDD  .rdd
    val resRDD2: RDD[Row] = df2.rdd
    resRDD2.map{
      case Row(clazz:String,counts:Int)=>
        s"班级:$clazz,人数:$counts"
    }.foreach(println)
  }
}

3.5、开窗函数

/**
 * 开窗:over
 *  聚合开窗函数:sum  count  lag(取上一条)  lead(取后一条)
 *  排序开窗函数:row_number rank dense_rank
 */
    //统计总分年级排名前十学生各科的分数
    val resDS1: Dataset[Row] = scoresDF.join(studentsDF, "id")
      .withColumn("sumScore", sum("score") over Window.partitionBy("id"))
      .withColumn("rn", dense_rank() over Window.partitionBy(substring($"clazz", 0, 2)).orderBy($"sumScore".desc))
      .where($"rn" <= 10)
      .limit(20)

3.6、spark作业提交到Yarn集群

  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
//      .master("local")//不是本地运行模式注释
      .appName("提交到yarn 计算每个班级的人数")
      //参数设置的优先级:代码优先级 > 命令优先级 > 配置文件优先级
      .config("spark.sql.shuffle.partitions", "1")
      .getOrCreate()

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

    val df1: DataFrame = sparkSession.read
      .format("csv")
      .schema("id STRING,name STRING,age INT,gender STRING,clazz STRING")
      .load("/bigdata30/spark_in/students.csv")
      //.load(args(0))

    val df2: DataFrame = df1.groupBy($"clazz")
      .agg(count($"id") as "counts")

    df2.write
      .csv("/bigdata30/spark_out1")
     // .csv(args(1))

    /**
     * 1.spark-submit --master yarn --deploy-mode client --class com.shujia.sql.Demo8SubmitYarn --conf spark.sql.shuffle.partitions=100 spark-1.0.jar
     * 2.spark-submit --master yarn --deploy-mode client --class com.shujia.sql.Demo8SubmitYarn spark-1.0.jar /bigdata30/spark_in/students.csv /bigdata30/spark_out2/
     * 3.spark-submit --master yarn --deploy-mode client --class com.shujia.sql.Demo8SubmitYarn -- conf spark.sql.shuffle.partitions=100 spark-1.0.jar /bigdata30/spark_in/students.csv /bigdata30/spark_out3/
     */
   }
}

3.7、spark调用hive

object Demo10SparkOnHive {
  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local")
      .appName("spark调用hive")
      //参数设置的优先级:代码优先级 > 命令优先级 > 配置文件优先级
      .config("spark.sql.shuffle.partitions", "1")
      .enableHiveSupport() // 开启hive的配置,函数可以将字符串表达式转换为Spark SQL中的列或表达式对象,‌
      .getOrCreate()

    sparkSession.sql("use bigdata30")
    sparkSession.sql("select * from sqoop_students1 limit 10").show(truncate = false)

  }
}

3.8、udf自定义函数

object Demo12UDF {  
def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local")
      .appName("udf自定义函数")
      .getOrCreate()

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

    //当你配置了hdfs等一些配置文件,那么默认读取路径是hadoop的,否则是本地
    val df1: DataFrame = sparkSession.read
      .format("csv")
      .schema("id STRING,name STRING,age INT,gender STRING,clazz STRING")
      .option("sep", ",")
      .load("/bigdata30/students.csv")//读取路径是hadoop

//    df1.select($"id",$"name",concat(expr("'数加:'"),$"name") as "new_name").show()
//    concat(expr("'数加:'"),$"name")//对姓名做拼接

//    val 唐三: UserDefinedFunction = udf("数加:" + _)
//    df1.select($"id", $"name", 唐三($"name")).show()
    val huang: UserDefinedFunction = udf("数加:" + _)//调用函数实现拼接
    df1.select($"id", $"name", huang($"name"))//自定义函数

    //直接在udf创建一个变量,可以在dsl语法中直接使用
    /**
     * +----------+------+-----------+
     * |        id|  name|  UDF(name)|
     * +----------+------+-----------+
     * |1500100001|施笑槐|数加:施笑槐|
     * |1500100002|吕金鹏|数加:吕金鹏|
     * |1500100003|单乐蕊|数加:单乐蕊|
     */

    
df1.createOrReplaceTempView("students")
    //一张表写sql语句要想使用自定函数的话,要
    //将自定义的函数变量注册成sql语句中的函数
    sparkSession.udf.register("sh",huang)
    sparkSession.sql(
      """
        |select
        |id,
        |name,
        |sh(name) as new_name
        |from
        |students
        |""".stripMargin).show()
}}

/*==========================================================*/
class Demo13Str extends UDF{
  def evaluate(line:String):String="大威天龙"+line

}
/**
 * 1、自定义类继承UDF类,重写evaluate方法
 * 2、打包,spark-1.0.jar 将jar包放到spark目录下的jars目录下
 * 3、在spark-sql命令行中注册函数
 * create function dwtl as "com.shujia.sql.Demo13Str"
 * */

四、spark Streaming

4.1、Spark streaming单词统计

object Demo1WordCount {
  def main(args: Array[String]): Unit = {
    /**
     * Spark core: SparkContext(主要入口) 核心数据结构:RDD
     * Spark sql: SparkSession 核心数据结构:DataFrame
     * Spark streaming: StreamingContext  核心数据结构:DStream(底层封装了RDD)
     */
    val conf = new SparkConf()
    conf.setMaster("local[2]")
    conf.setAppName("spark Streaming 单词统计")
    val sparkContext = new SparkContext(conf)

    //创建Spark Streaming的运行环境,和前两个模块是不一样的
    //Spark Streaming是依赖于Spark core的环境的
    //this(sparkContext: SparkContext, batchDuration: Duration)
    //Spark Streaming处理之前,是有一个接收数据的过程
    //batchDuration,表示接收多少时间段内的数据
    val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))

    //Spark Streaming程序理论上是一旦启动,就不会停止,除非报错,人为停止,停电等其他突然场景导致程序终止
    //监控一个端口号中的数据,手动向端口号中打数据
    //nc -lk xxx‌用于在Linux系统中进行网络连接和端口监听
    /**
     * -l:‌表示监听模式,‌即让nc在指定的端口上等待连接请求。‌
     * -k:‌保持监听模式,‌即使连接被关闭也继续保持监听状态。‌
     * 12345:‌指定了监听的端口号为12345。‌
     */
    val rids: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 12345)

    val resDS: DStream[(String, Int)] = rids.flatMap(_.split(" "))
      .map((_, 1))
      .reduceByKey(_ + _)

    println("------------------------------")
    resDS.print()
    println("------------------------------")

    /**
     * sparkStreaming启动的方式和前两个模块启动方式不一样
     *
     * sparkStreaming数据不停止,stop是当前期间的停止和开始
     */
    streamingContext.start()
    streamingContext.awaitTermination()
    streamingContext.stop()//每个批次的开始,每个批次的结束
  }
}
/**
 * updateStateByKey[S: ClassTag](updateFunc: (Seq[V], Option[S]) => Option[S]): DStream[(K, S)]
 * Seq:  序列,表示历史键对应的值组成的序列 (hello, seq:[1,1,1])
 * Option: 当前批次输入键对应的value值,如果历史中没有该键,这个值就是None, 如果历史中出现了这个键,这个值就是Some(值)
 *
 * 有状态算子使用注意事项:
 * 1、有状态算子ByKey算子只适用于k-v类型的DStream
 * 2、有状态算子使用的时候,需要提前设置checkpoint的路径,因为需要将历史批次的结果存储下来
 */

4.2、spark streaming 窗口案例

/**
 * 如果只是为了计算当前批次接受的数据,直接调用reduceByKey
 * 如果想要将历史数据和当前最新批次的数据结合处理,需要调用状态算子updateStateByKey
 * 如果想要实现滑动窗口和滚动窗口,需要使用窗口类算子reduceByKeyAndWindow
 */

/**
 * def reduceByKeyAndWindow(reduceFunc: (V, V) => V,windowDuration: Duration,slideDuration: Duration): DStream[(K, V)]
 * reduceFunc 编写处理相同的键对应的value值进行处理
 * windowDuration 设置窗口大小
 * slideDuration  设置滑动大小
 */
  def main(args: Array[String]): Unit = {
    /**
     * 创建spark streaming环境
     * 旧版本创建方式
     */
//    val conf: SparkConf = new SparkConf().setMaster("local[2]")
//      .setAppName("窗口案例")
//    val sparkContext = new SparkContext(conf)
//    val sc = new StreamingContext(sparkContext, Durations.seconds(5))

    /**
     * 新版本创建方式
     */
    val context: SparkContext = SparkSession.builder()
      .master("local[2]")
      .appName("窗口案例")
      .config("spark.sql.shuffle.partitions", 1)
      .getOrCreate().sparkContext
    val sc = new StreamingContext(context, Durations.seconds(5))

    //端口号:1000 ~ 65535
    val infoDS: ReceiverInputDStream[String] = sc.socketTextStream("master", 12315)

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

     //每间隔slideDuration大小的时间计算一次数据,计算时间的范围是最近windowDuration大小时间的数据
      val resDS1: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow((v1: Int, v2: Int) => v1 + v2, Durations.seconds(15), Durations.seconds(5))

    /**
     * 当窗口大小与滑动大小一致的时候,那么就会从滑动窗口转变成滚动窗口的效果
     */
    val resDS2: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow((v1: Int, v2: Int) => v1 + v2, Durations.seconds(10), Durations.seconds(10))
    resDS2.print()

    sc.start()
    sc.awaitTermination()
    sc.stop()
  }
}

4.3、RDD与stream的关系

RDD和Stream在Apache Spark中扮演了不同的角色和功能,RDD是静态数据集的抽象和处理模型,而Stream是实时流数据的抽象和处理模型。Spark Streaming通过将实时流数据转换成RDD序列来实现对流式数据的处理,因此可以说Stream(Spark Streaming)是基于RDD的一种实时处理扩展。

  def main(args: Array[String]): Unit = {
    //使用DataFrame的语法
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local[2]")
      .appName("rdd与stream的关系")
      .config("spark.sql.shuffle.partitions", "1")
      .getOrCreate()

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

    //使用RDD的语法
    val sparkContext: SparkContext = sparkSession.sparkContext

    //使用DStream的语法
    val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))

    val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 12345)
    //如果DS不是键值形式的话,可以单独调用window函数进行设置窗口的形式
    val kvDS: DStream[String] = infoDS.window(Durations.seconds(10), Durations.seconds(5))

    /**
     * foreachRDD:在DS中使用rdd语法造作数据
     * 缺点:该函数时没有返回值的
     * 需求:我们在使用DS自动RDD同时,想要使用结束后,得到一个新的DS
     */
    kvDS.foreachRDD((rdd:RDD[String])=>{
      val resRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" "))
        .map((_, 1))
        .reduceByKey(_ + _)
      resRDD.foreach(println)

      //rdd和df之间可以转换
      val df1: DataFrame = rdd.toDF.select($"value" as "info")
      df1.createOrReplaceTempView("words")
      val resDF: DataFrame = sparkSession.sql(
        """
          |select
          |t1.wds as word,
          |count(1) as counts
          |from
          |(select
          |explode(split(info,' ')) as wds
          |from words)t1
          |group by t1.wds
          |""".stripMargin)
      resDF.show()
    })
  }

4.4、transform语法(转换操作)

/**
 * 面试题:foreachRDD与transform的区别
 * 1.返回值:foreachRDD 返回类型为 void,因为它用于执行输出操作,例如写入数据到外部系统,而不生成新的 DStream。
 * transform 可以返回一个新的 DStream,因为它允许对每个 RDD 进行转换操作。
 * 2.使用场景:foreachRDD 适合用于将 DStream 中的数据发送到外部系统进行持久化,如将数据存储到数据库、写入文件或发送消息。
 * transform 适合用于需要在每个微批次中对数据进行复杂转换的场景,例如数据清洗、特征提取等。
 * 3.执行时机:foreachRDD 在每个微批次完成后执行,对生成的 RDD 进行操作。
 * transform 在每个微批次中对 DStream 进行转换操作,生成新的 DStream,
 * 这些操作是在 Spark Streaming 内部实现的。
 */
object Demo5Transform {
  def main(args: Array[String]): Unit = {
    //使用DataFrame的语法
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local[2]")
      .appName("transform转换操作")
      .config("spark.sql.shuffle.partitions", "1")
      .getOrCreate()
    import org.apache.spark.sql.functions._
    import sparkSession.implicits._

    //使用RDD的语法
    val sparkContext: SparkContext = sparkSession.sparkContext

    //使用DStream的语法
    val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
    val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 10086)

    val resDS: DStream[(String, Int)] = infoDS.transform((rdd: RDD[String]) => {
      //直接对rdd进行处理,返回新的rdd
      //      val resRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" "))
      //        .map((_, 1))
      //        .reduceByKey(_ + _)
      //      resRDD

      //将rdd转df,使用sql做分析
      //rdd和df之间可以转换
      val df1: DataFrame = rdd.toDF.select($"value" as "info")
      df1.createOrReplaceTempView("words")
      val resDF: DataFrame = sparkSession.sql(
        """
          |select
          |t1.wds as word,
          |count(1) as counts
          |from
          |(
          |select
          |explode(split(info,' ')) as  wds
          |from words) t1
          |group by t1.wds
          |""".stripMargin)

      val resRDD: RDD[(String, Int)] = resDF.rdd.map((row: Row) => (row.getAs[String](0), row.getAs[Int](1)))
      resRDD
      })
    resDS.print()

    streamingContext.start()
    streamingContext.awaitTermination()
    streamingContext.stop()
  }
}

4.5、sparkStreaming YarnSubmit

object Demo6YarnSubmit {
  def main(args: Array[String]): Unit = {
    //使用DataFrame的语法
    val sparkSession: SparkSession = SparkSession.builder()
      //      .master("local[2]")
      .appName("yarn集群提交案例")
      .config("spark.sql.shuffle.partitions", "1")
      .getOrCreate()
    import org.apache.spark.sql.functions._
    import sparkSession.implicits._

    //使用RDD的语法
    val sparkContext: SparkContext = sparkSession.sparkContext

    //使用DStream的语法
    val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
    val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 10086)

    val resDS: DStream[(String, Int)] = infoDS.transform((rdd: RDD[String]) => {
      //直接对rdd进行处理,返回新的rdd
      //      val resRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" "))
      //        .map((_, 1))
      //        .reduceByKey(_ + _)
      //      resRDD

      //将rdd转df,使用sql做分析
      //rdd和df之间可以转换
      val df1: DataFrame = rdd.toDF.select($"value" as "info")
      df1.createOrReplaceTempView("words")
      val resDF: DataFrame = sparkSession.sql(
        """
          |select
          |t1.wds as word,
          |count(1) as counts
          |from
          |(
          |select
          |explode(split(info,' ')) as  wds
          |from words) t1
          |group by t1.wds
          |""".stripMargin)

      val resRDD: RDD[(String, Int)] = resDF.rdd.map((row: Row) => (row.getAs[String](0), row.getAs[Int](1)))
      resRDD
    })

    resDS.print()

    streamingContext.start()
    streamingContext.awaitTermination()
    streamingContext.stop()
  }

4.6、spark DS 存储文件

  def main(args: Array[String]): Unit = {
    //使用DataFrame的语法
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local[2]")
      .appName("SaveFile")
      .config("spark.sql.shuffle.partitions", "1")
      .getOrCreate()
    import org.apache.spark.sql.functions._
    import sparkSession.implicits._

    //使用RDD的语法
    val sparkContext: SparkContext = sparkSession.sparkContext

    //使用DStream的语法
    val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
    val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 10086)

    val resDS: DStream[(String, Int)] = infoDS.transform((rdd: RDD[String]) => {
      
      //将rdd转df,使用sql做分析
      //rdd和df之间可以转换
      val df1: DataFrame = rdd.toDF.select($"value" as "info")
      df1.createOrReplaceTempView("words")
      val resDF: DataFrame = sparkSession.sql(
        """
          |select
          |t1.wds as word,
          |count(1) as counts
          |from
          |(
          |select
          |explode(split(info,' ')) as  wds
          |from words) t1
          |group by t1.wds
          |""".stripMargin)

      val resRDD: RDD[(String, Int)] = resDF.rdd.map((row: Row) => (row.getAs[String](0), row.getAs[Int](1)))
      resRDD
    })

    //    resDS.print()
    //将结果存储到磁盘中
    //只能设置文件夹的名字和文件的后缀
    //每一批次运行,都会产生新的小文件夹,文件夹中有结果数据文件
    resDS.saveAsTextFiles("spark/data/streamOut/stream","txt")

    streamingContext.start()
    streamingContext.awaitTermination()
    streamingContext.stop()
     }
  }

4.7、sparkDS调用Mysql

  def main(args: Array[String]): Unit = {
    //使用DataFrame的语法
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local[2]")
      .appName("DS调用mysql")
      .config("spark.sql.shuffle.partitions", "1")
      .getOrCreate()
    import org.apache.spark.sql.functions._
    import sparkSession.implicits._

    //使用RDD的语法
    val sparkContext: SparkContext = sparkSession.sparkContext

    //使用DStream的语法
    val streamingContext = new StreamingContext(sparkContext, Durations.seconds(5))
    val infoDS: ReceiverInputDStream[String] = streamingContext.socketTextStream("master", 10086)

        infoDS.foreachRDD((rdd:RDD[String])=>{
          println("======================= 正在处理一批数据 ==========================")
          //处理rdd中每一条数据
          rdd.foreach((line:String)=>{
            //如果将创建连接的代码写在这里,这样的话,每条数据都会创建一次连接
            /**
             * 创建与数据库连接对象
             */
            //注册驱动
            Class.forName("com.mysql.jdbc.Driver")
            //创建数据库连接对象
            val conn: Connection = DriverManager.getConnection(
              "jdbc:mysql://master:3306/bigdata30?useUnicode=true&characterEncoding=UTF-8&useSSL=false",
              "root",
              "123456"
            )
            //创建预编译对象
            val statement: PreparedStatement = conn.prepareStatement("insert into students values(?,?,?,?,?)")

            val info: Array[String] = line.split(",")
            statement.setInt(1,info(0).toInt)
            statement.setString(2,info(1))
            statement.setInt(3,info(2).toInt)
            statement.setString(4,info(3))
            statement.setString(5,info(4))

            //执行sql语句
            statement.executeUpdate()

            //释放资源
            statement.close()
            conn.close()
          })
        })
    /**
     * rdd中有一个算子foreachPartition
     * rdd本质是由一系列分区构成的,如果我们可以将分区数设置为1,且每个分区创建一个连接不就好了么
     */
    infoDS.foreachRDD((rdd: RDD[String]) => {
      println("======================= 接收到 5s 一批次数据 ==========================")
      rdd.repartition(2)
      println(s" DS封装的RDD中的分区数为:${rdd.getNumPartitions} ")

      /**
       * foreachPartition,处理一个分区的数据
       * 将一个分区的数据,封装成了一个迭代器
       */
      rdd.foreachPartition((itr: Iterator[String]) => {
        println("======================= 正在处理一个分区的数据 ==========================")
        //如果将创建连接的代码写在这里,这样的话,每条数据都会创建一次连接
        /**
         * 创建与数据库连接对象
         */
        //注册驱动
        Class.forName("com.mysql.jdbc.Driver")
        //创建数据库连接对象
        val conn: Connection = DriverManager.getConnection(
          "jdbc:mysql://master:3306/bigdata30?useUnicode=true&characterEncoding=UTF-8&useSSL=false",
          "root",
          "123456"
        )
        //创建预编译对象
        val statement: PreparedStatement = conn.prepareStatement("insert into students values(?,?,?,?,?)")
        println("========================= 创建了一次连接 =========================")
        itr.foreach((line: String) => {
          val info: Array[String] = line.split(",")
          statement.setInt(1, info(0).toInt)
          statement.setString(2, info(1))
          statement.setInt(3, info(2).toInt)
          statement.setString(4, info(3))
          statement.setString(5, info(4))
          //执行sql语句
          statement.executeUpdate()
        })
        statement.close()
        conn.close()
      })
    })


    streamingContext.start()
    streamingContext.awaitTermination()
    streamingContext.stop()
  }
}

五、spark调优

/**
 * 避免创建重复的RDD 
 * 尽可能复用同一个RDD 
 * 对多次使用的RDD进行持久化 
 * 尽量避免使用shuffle类算子 
 * 使用map-side预聚合的shuffle操作 
 * 使用高性能的算子 
 * 广播大变量
 * 使用Kryo优化序列化性能 
 * 优化数据结构
 * 使用高性能的库fastutil
 */

5.1、缓存优化

object Demo1Cache {
  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local[2]")
      .appName("缓存优化")
      .config("spark.sql.shuffle.partitions", 1)
      .getOrCreate()

    val sc: SparkContext = sparkSession.sparkContext

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

    /**
     * 缓存的目的是避免每一次job作业执行的时候,都需要从第一个rdd算起
     * 对重复使用RDD进行缓存
     * cache 设置不了缓存级别
     * persist 可以设置缓存级别
     */
//    stuRDD.cache() // 默认的缓存级别是MEMORY_ONLY
    stuRDD.persist(StorageLevel.MEMORY_ONLY_SER)

    /**
     * 计算每个班级的人数
     */
    val resClazzRDD: RDD[(String, Int)] = stuRDD.map(_.split(",") match {
      case Array(_, _, _, _, clazz: String) =>
        (clazz, 1)
    }).reduceByKey(_ + _)
    resClazzRDD.foreach(println)

    /**
     * 计算每个性别的人数
     */
    val resGenderRDD: RDD[(String, Int)] = stuRDD.map(_.split(",") match {
      case Array(_, _, _, gender: String, _) =>
        (gender, 1)
    }).reduceByKey(_ + _)
    resGenderRDD.foreach(println)
//    while (true){
//
//    }
  }
}

5.2、使用高性能算子

使用reduceByKey/aggregateByKey替代groupByKey 

  def main(args: Array[String]): Unit = {
    //使用reduceByKey/aggregateByKey替代groupByKey
    val sparkSession: SparkSession = SparkSession.builder()
      .config("spark.sql.shuffle.partitions", "1")
      .master("local[2]")
      .appName("aggregateByKey替代groupByKey")
      .getOrCreate()

    val sparkContext: SparkContext = sparkSession.sparkContext
    val stuRDD: RDD[String] = sparkContext.textFile("spark/data/students.txt")

    val clazzKVRDD: RDD[(String, Int)] = stuRDD.map(_.split(",") match {
      case Array(_, _, _, _, clazz: String) =>
        (clazz, 1)
    })
    //groupByKey 不做聚合,只做前一个RDD中的分区中数据相同的键到后一个RDD中同一个分区 (尽量使用reduceByKey去替代)
        val resRDD2: RDD[(String, Int)] = clazzKVRDD.groupByKey()
          .map((kv: (String, Iterable[Int])) => {
            (kv._1, kv._2.sum)
          })
        resRDD2.foreach(println)

    //reduceByKey的使用,分组之后,直接使用聚合
    //理解为普通的MapReduce中的根据相同的键进入到同一个reduce, 然后在reduce端聚合
    //实际上这里对应的是前一个RDD中的分区中数据相同的键到后一个RDD中同一个分区,在后一个RDD分区中的聚合
    //    val resRDD: RDD[(String, Int)] = clazzKVRDD.reduceByKey(_ + _)
    //    resRDD.foreach(println)

    //aggregateByKey
    //aggregateByKey(zeroValue: U)(seqOp: (U, V) => U,  combOp: (U, U) => U)
    //zeroValue: 初始值,这个参数只会被后面第一个参数函数所使用
    //seqOp: 相当于map端预聚合的逻辑
    //combOp: 相当于reduce端的聚合逻辑
    val resRDD: RDD[(String, Int)] = clazzKVRDD.aggregateByKey(0)(
      //相当于map端预聚合的逻辑
      (a1: Int, a2: Int) => a1 + a2,
      //相当于reduce端的聚合逻辑
      (b1: Int, b2: Int) => b1 + b2
    )
  resRDD.foreach(println)
  }

}

使用mapPartitions替代普通map Transformation算子

object Demo3MapPartitions {
  def main(args: Array[String]): Unit = {
    //使用mapPartitions替代普通map Transformation算子
    //使用reduceByKey/aggregateByKey替代groupByKey
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local")
      .appName("mapPartitions代替map")
      .config("spark.sql.shuffle.partitions", 11)
      .getOrCreate()

    val context: SparkContext = sparkSession.sparkContext
    val linesRDD: RDD[String] = context.textFile("spark/data/ant_user_low_carbon.txt")
    println(s"linesRDD的分区数为:${linesRDD.getNumPartitions}")

    /**
     * map算子主要作用是,遍历RDD中的每一条数据,进行处理返回新的一条数据
     * 如果在处理过程中,需要创建工具对象的话,那么使用map不太好,原因是因为每一条数据都需要new一下
     * 可能会造成内存溢出
     */
    val resRDD1: RDD[(String, String, String)] = linesRDD.map((line: String) => {
      println("======================创建一次对象====================")
      val info: Array[String] = line.split("\t")
      val t1: String = info(1)
      val df1 = new SimpleDateFormat("yyyy/MM/dd")
      val date: Date = df1.parse(t1)
      val df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
      val t2: String = df2.format(date)
      (info(0), t2, info(2))
    })
    resRDD1.foreach(println)

    /**
     * 实际上针对上面的案例,我们可以针对rdd中的每一个分区创建一个工具对象,在每条数据上使用
     * mapPartitions,将每一个分区中的数据封装成了一个迭代器
     */
    val resRDD2: RDD[(String, String, String)] = linesRDD.mapPartitions((itr: Iterator[String]) => {
      println("======================创建一次对象====================")
      val df1 = new SimpleDateFormat("yyyy/MM/dd")
      val df2 = new SimpleDateFormat("YYYY-MM-DD,HH:MM:SS")
      itr.map((linesRDD: String) => {
        val info: Array[String] = linesRDD.split(",")
        val t1: String = info(1)
        val date: Date = df1.parse(t1)
        val t2: String = df2.format(date)
        (info(0), t2, info(2))
      })
    })
    resRDD2.foreach(println)
  }
}

5.3、重分区(repartition,coalesce)

重分区是优化 Apache Spark 作业性能和效率的重要手段之一,特别是在处理大数据时,合理的分区策略能够显著提升整体的数据处理速度和效率。

重分区的主要好处包括:1. 数据均衡,2. 提高并行度,3. 数据局部性优化,4. 数据压缩和优化存储,5. 算子性能优化

object Demo4Coalesce1 {
  def main(args: Array[String]): Unit = {
    //repartition:coalesce(numPartitions,true) 增多分区使用这个
    //coalesce(numPartitions,false) 减少分区 没有shuffle只是合并 partition
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("重分区")
    val sc: SparkContext = new SparkContext(conf)
    val linesRDD: RDD[String] = sc.textFile("spark/data/students.txt")
    println(s"lineRDD的分区数:${linesRDD.getNumPartitions}")

    /**
     * 使用repartition
     */
    //增大分区数,使用repartition,返回一个新的rdd,会产生shuffle
    val resRDD1: RDD[String] = linesRDD.repartition(10)
    println(s"resRDD1的分区为:${resRDD1.getNumPartitions}")
    resRDD1.foreach(println)
    //减少分区数,使用repartition,返回一个新的rdd,会产生shuffle
    val resRDD2: RDD[String] = linesRDD.repartition(1)
    println(s"resRDD2的分区为:${resRDD2.getNumPartitions}")
    resRDD2.foreach(println)


    /**
     * coalesce
     *
     * 1、默认增大分区是不会产生shuffle的
     * 2、合并分区直接给分区数,不会产生shuffle
     *
     * 可以手动指定增大分区 linesRDD.coalesce(10,true),从而产生shuffle
     * 可以手动指定减小分区 linesRDD.coalesce(1,true),从而产生shuffle
     */
    val resRDD3: RDD[String] = linesRDD.coalesce(10)
    println(s"resRDD3的分区数:${resRDD3.getNumPartitions}")
    resRDD3.foreach(println)

    val resRDD4: RDD[String] = linesRDD.coalesce(1,true)
    println(s"resRDD4的分区数:${resRDD4.getNumPartitions}")
    resRDD4.foreach(println)
    
    while (true){
    }
  }
}
  def main(args: Array[String]): Unit = {
    //repartition:coalesce(numPartitions,true) 增多分区使用这个
    //coalesce(numPartitions,false) 减少分区 没有shuffle只是合并 partition
    val conf: SparkConf = new SparkConf()
      .setMaster("local")
      .setAppName("重分区")
    val sc = new SparkContext(conf)

    val lineRDD: RDD[String] = sc.textFile("spark/data/stu1/*")//stu/*包括stu1.txt,stu2.txt,stu3.txt
    println(s"lineRDD的分区数:${lineRDD.getNumPartitions}")

    /**
     * Coalesce算子通常是用在合并小文件时候使用
     * 对应的spark core中的话,通常使用该算子进行合并分区
     */
    val lineRDD2: RDD[String] = lineRDD.coalesce(1)
    lineRDD2.foreach(println)
    println(s"lineRDD2的分区数:${lineRDD2.getNumPartitions}")
  }
}

5.4、广播大变量

1.开发过程中,会遇到需要在算子函数中使用外部变量的场景(尤其是大变量,比如 100M以上的大集合),那么此时就应该使用Spark的广播(Broadcast)功能来提 升性能

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

object Demo6Join {
  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
      .master("local")
      .appName("spark sql使用广播变量")
      .config("spark.sql.shuffle.partitions", "1")
      .getOrCreate()

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

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

    val scoresDF: DataFrame = sparkSession.read
      .format("csv")
      .option("seq", ",")
      .schema("id STRING,subject_id STRING,score INT")
      .load("spark/data/score.txt")

    /**
     * 如果在spark sql中是两个DF进行join关联的话,并且运行模式是local模式的话,会自动地将关联的DF进行广播
     * 如果不是local模式,不会自动进行,需要手动将要广播的DF给广播出去
     *
     * 广播大变量,1G的变量
     *
     * 会进行两次job作业
     * 第一次是将关联的DF广播
     * 第二次是使用广播的DF进行关联
     */
    val resDF: DataFrame = stuDF.join(scoresDF.hint("broadcast"), "id")
    resDF.show()
    while (true){
      
    }
  }

}

5.5、使用Kryo优化序列化性能

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

  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession.builder()
      .config("spark.sql.shuffle.partitions", "1")
      //将序列化方式设置为Kryo的序列化方式
      .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
      //自定义一个序列化类,指定要序列化的东西
      .config("spark.kryo.registrator", "com.shujia.opt.Demo8Kryo")
      .master("local[2]")
      .appName("缓存优化")
      .getOrCreate()

    val sparkContext: SparkContext = sparkSession.sparkContext


    val studentsRDD: RDD[Student] = sparkContext.textFile("spark/data/students.txt")
      .map(_.split(",") match {
        case Array(id: String, name: String, age: String, gender: String, clazz: String) =>
          Student(id, name, age.toInt, gender, clazz)
      })

    /**
     * 第二次job作业使用的数据大小
     * 未使用序列化进行缓存:238.3 KiB
     * 使用是默认的序列化方式:65.4 KiB
     * 使用kryo序列化:43.0 KiB
     */
//    studentsRDD.cache() // 默认的缓存级别是MEMORY_ONLY
    studentsRDD.persist(StorageLevel.MEMORY_ONLY_SER)

    /**
     * 计算每个班级的人数
     */
    val resRDD: RDD[(String, Int)] = studentsRDD.map((stu:Student)=>(stu.clazz,1)).reduceByKey(_ + _)
    resRDD.foreach(println)

    /**
     * 计算每个性别的人数
     */
    val resRDD2: RDD[(String, Int)] = studentsRDD.map((stu:Student)=>(stu.gender,1)).reduceByKey(_ + _)
    resRDD2.foreach(println)
    while (true) {

    }
  }
}

case class Student(id:String,name:String,age:Int,gender:String,clazz:String)
class Demo8Kryo extends KryoRegistrator{
  override def registerClasses(kryo: Kryo): Unit = {
    //告诉spark程序,使用kryo序列化,具体是什么要进行kryo序列化
    kryo.register(classOf[Student])
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值