目录

一、

1、spark是什么

2、spark四大特性

速度快

易用性

通用性

兼容性

3、简述spark与mapreduce的区别?

基于内存与磁盘

进程与线程

二、

1、rdd的概念

2、rdd的五大属性

3、rdd的创建方式

4、rdd的算子操作分类

1、transformation(转换)

2、action (动作)

5、RDD常见的算子操作说明重点需要掌握

三、

1、RDD的算子操作案例

2、RDD的依赖关系

窄依赖

宽依赖

Lineage(即血统)

3、RDD的缓存机制

1、什么是rdd的缓存机制、好处是什么?

2、如何对rdd设置缓存? cache和persist方法的区别是什么?

3、什么时候设置缓存?

4、如何清除缓存?

四、

1、sparksql简介

2、sparksql特性

3、DataFrame简介

4、DataFrame和RDD对比

RDD

DataFrame

5、DataFrame常用的操作

6、通过IDEA开发程序实现把RDD转换DataFrame

五、

1、sparksql操作hivesql

2、sparksql操作jdbc数据源

 3、sparksql中自定义函数

4、sparksql整合hive


一、

1、spark是什么

spark是针对于大规模数据处理的统一分析引擎,它是基于内存计算框架,计算速度非常之快,但是它仅仅只是涉及到计算,并没有涉及到数据的存储,后期需要使用spark对接外部的数据源,比如hdfs。

2、spark四大特性

速度快

job的输出结果可以保存在内存
spark任务以线程的方式运行在进程中

易用性

可以快速去编写spark程序通过 java/scala/python/R/SQL等不同语言

通用性

一个==生态系统==,包含了很多模块,
sparksql:通过sql去开发spark程序做一些离线分析
sparkStreaming:主要是用来解决公司有实时计算的这种场景
Mlib:它封装了一些机器学习的算法库
Graphx:图计算

兼容性

spark程序就是一个计算逻辑程序,这个任务要运行就需要计算资源(内存、cpu、磁盘),
哪里可以给当前这个任务提供计算资源,就可以把spark程序提交到哪里去运行
standAlone(后期使用较多)
它是spark自带的独立运行模式,整个任务的资源分配由spark集群的老大Master负责
yarn(后期使用较多)
可以把spark程序提交到yarn中运行,整个任务的资源分配由yarn中的老大ResourceManager负责
mesos
它也是apache开源的一个类似于yarn的资源调度平台

3、简述spark与mapreduce的区别?

spark处理速度为什么比mapreduce要快

基于内存与磁盘

(1)mapreduce任务后期再计算的时候,每一个job的输出结果会落地到磁盘,后续有其他的job需要依赖于前面job的输出结果,这个时候就需要进行大量的磁盘io操作。性能就比较低。
(2)spark任务后期再计算的时候,job的输出结果可以保存在内存中,后续有其他的job需要依赖于前面job的输出结果,这个时候就直接从内存中获取得到,避免了磁盘io操作,性能比较高
对于spark程序和mapreduce程序都会产生shuffle阶段,在shuffle阶段中它们产生的数据都会落地到磁盘。

进程与线程

(1)mapreduce任务以进程的方式运行在yarn集群中,比如程序中有100个MapTask,一个task就需要一个进程,这些task要运行就需要开启100个进程。
(2)spark任务以线程的方式运行在进程中,比如程序中有100个MapTask,后期一个task就对应一个线程,这里就不在是进程,这些task需要运行,
这里可以极端一点:只需要开启1个进程,在这个进程中启动100个线程就可以了。进程中可以启动很多个线程,而开启一个进程与开启一个线程需要的时间和调度代价是不一样。 开启一个进程需要的时间远远大于开启一个线程。


二、

1、rdd的概念

RDD(Resilient Distributed Dataset)叫做弹性 分布式 数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合.
Dataset: 就是一个集合,存储很多数据.
Distributed:它内部的元素进行了分布式存储,方便于后期进行分布式计算.
Resilient: 表示弹性,rdd的数据是可以保存在内存或者是磁盘中.

2、rdd的五大属性

(1)A list of partitions

一个分区列表,数据集的基本组成单位。
(2)A function for computing each split

一个计算每个分区的函数
(3)A list of dependencies on other RDDs

一个rdd会依赖于其他多个rdd
通过lineage血统记录下rdd与rdd之间的依赖关系
好处
就在于后期某个rdd的部分分区数据丢失的时候,可以通过血统进行重新计算恢复得到
这也是spark任务自身的一个容错机制
(4)Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)

(可选项)一个Partitioner,即RDD的分区函数
(5)Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)

一个列表,存储每个Partition的优先位置(可选项)

3、rdd的创建方式

1、通过已经存在的scala集合去构建

val rdd1=sc.parallelize(List(1,2,3,4,5))
val rdd2=sc.parallelize(Array("hadoop","hive","spark"))
val rdd3=sc.makeRDD(List(1,2,3,4))

2、加载外部的数据源去构建

val rdd1=sc.textFile("/words.txt")

3、从已经存在的rdd进行转换生成一个新的rdd

val rdd2=rdd1.flatMap(_.split(" "))
val rdd3=rdd2.map((_,1))

4、rdd的算子操作分类

1、transformation(转换)

根据已经存在的rdd转换生成一个新的rdd, 它是延迟加载,它不会立即执行
map / flatMap / reduceByKey 等

2、action (动作)

它会真正触发任务的运行,将rdd的计算的结果数据返回给Driver端,或者是保存结果数据到外部存储介质中
collect / saveAsTextFile 等

5、RDD常见的算子操作说明
重点需要掌握

map / mapPartitions foreach / foreachPartition算子区别操作?

  • 1) map / mapPartitions (transformation算子)

map:用于遍历RDD,将函数f应用于每一个元素,返回新的RDD
mapPartitions:用于遍历操作RDD中的每一个分区,返回生成一个新的RDD。
如果在映射的过程中,需要频繁创建额外的对象,使用mapPartitions要比map高效。
比如,将RDD中的所有数据通过JDBC连接写入数据库,如果使用map函数,可能要为每一个元素都创建一个connection,这样开销很大,如果使用mapPartitions,那么只需要针对每一个分区建立一个connection。

  • 2) foreach / foreachPartition (action算子)

foreach: 用于遍历RDD, 将函数f应用于每一个元素,无返回值。

foreachPartition: 用于遍历操作RDD中的每一个分区,无返回值。:

一般使用mapPartitions或者foreachPartition算子比map和foreach更加高效,推荐使用。

  • 3) coalesce/ repartition 算子

coalesce: 合并分区/减少分区 默认不shuffle
默认 coalesce 不能扩大分区数量。除非添加true的参数,或者使用repartition。
repartition: 重新分区, 有shuffle
repartition(numPartitions)其本质是调用了coalesce(numPartitions,true)方法, 第二个参数默认是true,表示会产生shuffle。
适用场景:
1、如果要shuffle,都用 repartition
2、不需要shuffle,仅仅是做分区的合并,coalesce
3、repartition常用于扩大分区。


三、

1、RDD的算子操作案例

  • 重点掌握rdd常见的一些算子操作
  • flatMap
  • map
  • reduceByKey
  • sortBy
  • distinct
  • count
  • mapPartitions
  • foreach

foreachPartition

//todo:利用spark实现点击流日志分析--TopN(求页面访问次数最多的前N位)
object TopN {
  def main(args: Array[String]): Unit = {
    //1、构建SparkConf
    val sparkConf: SparkConf = new SparkConf().setAppName("TopN").setMaster("local[2]")

    //2、构建SparkContext
    val sc = new SparkContext(sparkConf)
    sc.setLogLevel("warn")

    //3、读取数据文件
    val data: RDD[String] = sc.textFile("E:\\data\\access.log")

    //4、切分每一行,过滤出丢失的字段数据,获取页面地址
    val filterRDD: RDD[String] = data.filter(x=>x.split(" ").length>10)
    val urlAndOne: RDD[(String, Int)] = filterRDD.map(x=>x.split(" ")(10)).map((_,1))

    //5、相同url出现的1累加
    val result: RDD[(String, Int)] = urlAndOne.reduceByKey(_+_)

    //6、按照次数降序
    val sortedRDD: RDD[(String, Int)] = result.sortBy(_._2,false)


    //7、取出url出现次数最多的前5位
    val top5: Array[(String, Int)] = sortedRDD.take(5)
    top5.foreach(println)

    sc.stop()

  }

}
object Data2MysqlForeachPartitions {
  def main(args: Array[String]): Unit = {
    //1、构建SparkConf
    val sparkConf: SparkConf = new SparkConf().setAppName("Data2MysqlForeachPartitions").setMaster("local[2]")

    //2、构建SparkContext
    val sc = new SparkContext(sparkConf)
    sc.setLogLevel("warn")

    //3、读取数据文件
    val data: RDD[String] = sc.textFile("E:\\data\\person.txt")

    //4、切分每一行    // id  name  age
    val personRDD: RDD[(String, String, Int)] = data.map(x => x.split(",")).map(x => (x(0), x(1), x(2).toInt))

    //5、把数据保存到mysql表中
    //使用foreachPartition每个分区建立一次链接,减少与mysql链接次数
    personRDD.foreachPartition( iter =>{
      //把数据插入到mysql表操作
      //1、获取连接
      val connection: Connection = DriverManager.getConnection("jdbc:mysql://node03:3306/spark","root","123456")

      //2、定义插入数据的sql语句
      val sql="insert into person(id,name,age) values(?,?,?)"

      //3、获取PreParedStatement

      try {
        val ps: PreparedStatement = connection.prepareStatement(sql)

        //4、获取数据,给?号 赋值
        iter.foreach(line =>{

          ps.setString(1, line._1)
          ps.setString(2, line._2)
          ps.setInt(3, line._3)
         //设置批量提交
          ps.addBatch()
        })
		//执行批量提交
        ps.executeBatch()
      } catch {
        case e:Exception => e.printStackTrace()
      } finally {
        if(connection !=null){
          connection.close()
        }

      }
    }

  }
}

2、RDD的依赖关系

RDD和它依赖的父RDD的关系有两种不同的类型: 窄依赖(narrow dependency)和宽依赖(wide dependency)

窄依赖

  • 窄依赖指的是每一个父RDD的Partition, 最多被子RDD的一个Partition使用,
  • 所有的窄依赖不会产生shuffle: map/flatMap/filter/union等
  • 总结:窄依赖我们形象的比喻为独生子女

宽依赖

  • 宽依赖指的是多个子RDD的Partition, 会依赖同一个父RDD的Partition,
  • 所有的宽依赖会产生shuffle: reduceByKey/sortByKey/groupBy/groupByKey/join等等
  • 总结:宽依赖我们形象的比喻为超生


  • join分为宽依赖和窄依赖,如果RDD有相同的partitioner,那么将不会引起shuffle,这种join是窄依赖,反之就是宽依赖

Lineage(即血统)

  • RDD只支持粗粒度转换, 即只记录单个块上执行的单个操作。
  • 将创建RDD的一系列Lineage(即血统)记录下来,以便恢复丢失的分区
  • RDD的Lineage会记录RDD的元数据信息和转换行为,lineage保存了RDD的依赖关系,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

3、RDD的缓存机制

1、什么是rdd的缓存机制、好处是什么?

可以把一个rdd的数据缓存起来,后续有其他的job需要用到该rdd的结果数据,可以直接从缓存中获取得到,避免了重复计算。缓存是加快后续对该数据的访问操作。

2、如何对rdd设置缓存? cache和persist方法的区别是什么?

RDD通过persist方法或cache方法可以将前面的计算结果缓存。
但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。

3、什么时候设置缓存?

  • 1、某个rdd的数据后期被使用了多次
  • 公共rdd进行持久化,避免后续需要,再次重新计算,提升效率。
  • 2、rdd的数据来之不易时
  • 为了获取得到一个rdd的结果数据,经过了大量的算子操作或者是计算逻辑比较复杂

4、如何清除缓存?

1、自动清除 :一个application应用程序结束之后,对应的缓存数据也就自动清除

2、手动清除 :调用rdd的unpersist方法


四、

1、sparksql简介

Spark SQL is Apache Spark’s module for working with structured data.

SparkSQL是apache Spark用来处理结构化数据的一个模块

大数据技术宏观上进行分类:

(1)数据存储
	HDFS  HBASE
	
(2)数据计算
	   a. 离线计算
	   		MR 、Hive 、RDD(spark-core)、sparksql
	   b. 实时计算
	        sparkStreaming 、Flink

2、sparksql特性

  • 1、易整合

将SQL查询与Spark程序无缝混合
可以使用不同的语言进行代码开发(java、scala、python、R)

  • 2、统一的数据源访问

以相同的方式连接到任何数据源

  • 3、兼容hive

sparksql兼容hivesql

  • 4、标准的数据库连接

支持标准的数据库连接JDBC或者ODBC

3、DataFrame简介

在Spark中,DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库的二维表格
DataFrame带有Schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型,但底层做了更多的优化

4、DataFrame和RDD对比

RDD可以把它内部元素看成是一个java对象
DataFrame可以把内部是一个Row对象,它表示一行一行的数据

RDD

优点

  • 1、编译时类型安全

开发会进行类型检查,在编译的时候及时发现错误

  • 2、具有面向对象编程的风格

缺点

  • 1、构建大量的java对象,占用了大量heap堆空间,导致频繁的GC
  • 2、数据的序列化和反序列性能开销很大

DataFrame

  • DataFrame引入了schema元信息和off-heap(堆外内存)

优点

  • DataFrame引入了schema元信息,解决了rdd数据的序列化和反序列性能开销很大这个缺点。
  • DataFrame引入了off-heap,解决了rdd构建大量的java对象 占用了大量heap堆空间,导致频繁的GC这个缺点。

缺点

  • 1、编译时类型不安全
  • 2、不在具有面向对象编程的风格

5、DataFrame常用的操作

  • 1、DSL风格语法 : spark自身提供了一套Api
/加载数据
val rdd1=sc.textFile("/person.txt").map(x=>x.split(" "))
//定义一个样例类
case class Person(id:String,name:String,age:Int)
//把rdd与样例类进行关联
val personRDD=rdd1.map(x=>Person(x(0),x(1),x(2).toInt))
//把rdd转换成DataFrame
val personDF=personRDD.toDF

//打印schema信息
personDF.printSchema

//展示数据
personDF.show

//查询指定的字段
personDF.select("name").show
personDF.select($"name").show
personDF.select(col("name").show
                
//实现age+1
 personDF.select($"name",$"age",$"age"+1)).show   

//实现age大于30过滤
 personDF.filter($"age" > 30).show
  
 //按照age分组统计次数
 personDF.groupBy("age").count.show 
   
//按照age分组统计次数降序
 personDF.groupBy("age").count().sort($"count".desc)show

2、SQL风格语法

  • 把dataFrame注册成一张表,通过sparkSession.sql(sql语句)操作该表数据
//DataFrame注册成表
personDF.createTempView("person")

//使用SparkSession调用sql方法统计查询
spark.sql("select * from person").show
spark.sql("select name from person").show
spark.sql("select name,age from person").show
spark.sql("select * from person where age >30").show
spark.sql("select count(*) from person where age >30").show
spark.sql("select age,count(*) from person group by age").show
spark.sql("select age,count(*) as count from person group by age").show
spark.sql("select * from person order by age desc").show

6、通过IDEA开发程序实现把RDD转换DataFrame

  • 1、利用反射机制
  • 事先可以确定DataFrame的schema信息
  • 定义一个样例类,样例类中的属性,通过反射之后生成DataFrame的schema信息
  • 2、通过StructType动态指定schema信息
  • 事先不确定DataFrame的schema信息,在开发代码的过程中动态指定
  • 其本质调用底层方法
//1、构建SparkSession对象
    val spark: SparkSession = SparkSession.builder().appName("StructTypeSchema").master("local[2]").getOrCreate()

    //2、获取sparkContext对象
    val sc: SparkContext = spark.sparkContext
    sc.setLogLevel("warn")

    //3、读取文件数据
    val data: RDD[Array[String]] = sc.textFile("E:\\person.txt").map(x=>x.split(" "))

    //4、将rdd与Row对象进行关联
    val rowRDD: RDD[Row] = data.map(x=>Row(x(0),x(1),x(2).toInt))

    //5、指定dataFrame的schema信息   
    //这里指定的字段个数和类型必须要跟Row对象保持一致
    val schema=StructType(
        StructField("id",StringType)::
        StructField("name",StringType)::
        StructField("age",IntegerType)::Nil
    )

    val dataFrame: DataFrame = spark.createDataFrame(rowRDD,schema)
    dataFrame.printSchema()
    dataFrame.show()

    dataFrame.createTempView("user")
    spark.sql("select * from user").show()


    spark.stop()

  }

五、

1、sparksql操作hivesql

  • 主要是理解sparksql的四大特性中的
  • 第三点 sparksql兼容hivesql
  • .enableHiveSupport() //-----开启对hive的支持
def main(args: Array[String]): Unit = {
    //1、构建SparkSession对象
    val spark: SparkSession = SparkSession.builder()
      .appName("HiveSupport")
      .master("local[2]")
      .enableHiveSupport() //-----开启对hive的支持
      .getOrCreate()
      
    //2、直接使用sparkSession去操作hivesql语句

      //2.1 创建一张hive表
       spark.sql("create table people(id string,name string,age int) row format delimited fields terminated by ','")

      //2.2 加载数据到hive表中
       spark.sql("load data local inpath './data/kaikeba.txt' into table people ")

      //2.3 查询
      spark.sql("select * from people").show()

    spark.stop()
  }
}

2、sparksql操作jdbc数据源

  • 1、sparksql通过 JDBC加载mysql表的数据
  • 2、sparksql处理完成的数据,保存到mysql表中
//todo:通过sparksql把结果数据写入到mysql表中
object Data2Mysql {
  def main(args: Array[String]): Unit = {
    //1、创建SparkSession
    val spark: SparkSession = SparkSession
                                .builder()
                                .appName("Data2Mysql")
                                .master("local[2]")
                                .getOrCreate()
    //2、读取mysql表中数据
        //2.1 定义url连接
        val url="jdbc:mysql://node03:3306/spark"
        //2.2 定义表名
        val table="user"
        //2.3 定义属性
        val properties=new Properties()
        properties.setProperty("user","root")
        properties.setProperty("password","123456")

    val mysqlDF: DataFrame = spark.read.jdbc(url,table,properties)

    //把dataFrame注册成一张表
      mysqlDF.createTempView("user")

    //通过sparkSession调用sql方法
      //需要统计经度和维度出现的人口总数大于1000的记录 保存到mysql表中
      val result: DataFrame = spark.sql("select * from user where age > 30")

    //保存结果数据到mysql表中
         result.write.mode("append").jdbc(url,"kaikeba",properties)
//result.write.mode(args(0)).jdbc(url,args(1),properties       
    //mode:指定数据的插入模式
        //overwrite: 表示覆盖,如果表不存在,事先帮我们创建
        //append   :表示追加, 如果表不存在,事先帮我们创建
        //ignore   :表示忽略,如果表事先存在,就不进行任何操作
        //error    :如果表事先存在就报错(默认选项)

    //关闭
     spark.stop()
  }
}

打包—提交到集群

spark-submit \
--master spark://node01:7077 \
--class com.kaikeba.sql.Data2Mysql \
--executor-memory 1g \
--total-executor-cores 4 \
--driver-class-path /home/hadoop/mysql-connector-java-5.1.38.jar \
--jars /home/hadoop/mysql-connector-java-5.1.38.jar \
spark_class02-1.0-SNAPSHOT.jar \
append  kaikeba

 3、sparksql中自定义函数

  • 自定义udf函数
  • 核心代码
def main(args: Array[String]): Unit = {
    //1、创建SparkSession
    val sparkSession: SparkSession = SparkSession.builder().appName("SparkSQLFunction").master("local[2]").getOrCreate()

    //2、构建数据源生成DataFrame
    val dataFrame: DataFrame = sparkSession.read.text("E:\\data\\test_udf_data.txt")

    //3、注册成表
    dataFrame.createTempView("t_udf")


    //4、实现自定义的UDF函数

        //小写转大写
        sparkSession.udf.register("low2Up",new UDF1[String,String]() {
          override def call(t1: String): String = {
            t1.toUpperCase
          }
        },StringType)

        //大写转小写
        sparkSession.udf.register("up2low",(x:String)=>x.toLowerCase)


    //4、把数据文件中的单词统一转换成大小写
    sparkSession.sql("select  value from t_udf").show()
    sparkSession.sql("select  low2Up(value) from t_udf").show()
    sparkSession.sql("select  up2low(value) from t_udf").show()

    sparkSession.stop()

  }

4、sparksql整合hive

步骤

  • 1、需要把hive安装目录下的配置文件hive-site.xml, 拷贝到每一个spark安装目录下对应的conf文件夹中
  • 2、需要一个连接mysql驱动的jar包,拷贝到spark安装目录下对应的jars文件夹中
  • 3、可以使用spark-sql脚本 后期执行sql相关的任务
  • 使用方式
spark-sql \
--master spark://node01:7077 \
--executor-memory 1g \
--total-executor-cores 4 \
--conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse
  • 应用场景
#!/bin/sh
#定义sparksql提交脚本的头信息
SUBMITINFO="spark-sql --master spark://node01:7077 --executor-memory 1g --total-executor-cores 4 --conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse" 
#定义一个sql语句
SQL="select * from default.hive_source;" 
#执行sql语句   类似于 hive -e sql语句
echo "$SUBMITINFO" 
echo "$SQL"
$SUBMITINFO -e "$SQL"