spark

目录

 

Spark是什么

spark core当中的RDD

以spark当中单词计数统计为例解释RDD的五大属性 

rdd的创建三种方式

rdd的算子的介绍以及算子使用

算子分两大类

常用算子

DAG的划分以及shuffle过程

spark的任务调度

SparkSQL

在spark-shell中使用RDD配合样例类,转换成为DataFrame 

dataFrame的常用的操作

通过JavaAPI编写SparkSQL程序,读取文本文件创建DataFrame

sparkSQL支持spark on hive,使用sparkSession直接操作hql语句

Spark Streaming

kafka数据源


Spark是什么

Spark是一个开源的类似于Hadoop MapReduce的通用的并行计算框架,Spark基于map reduce算法实现的分布式计算,拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是Spark是基于内存计算的大数据并行计算框架,它的Job中间输出和结果可以保存在内存中,从而不再需要读写HDFS。因为Spark基于内存计算,所以比Hadoop中MapReduce计算框架具有更高的实时性,同时保证了高效容错性和可伸缩性,能更好地适用于数据挖掘与机器学习等需要迭代的map reduce的算法。

Spark是MapReduce的替代方案,而且兼容HDFS、Hive,可融入Hadoop的生态系统,以弥补MapReduce的不足。

spark core当中的RDD

spark core实现了spark的基本功能,包括任务调度、内存管理、错误恢复与存储系统交互等模块。spark core中还包含了对RDD的定义

RDD(Resilient Distributed Dataset)弹性分布式数据集,是Spark的基石,是实现Spark数据处理的核心抽象。

spark core的操作对象是SparkContext 。

以spark当中单词计数统计为例解释RDD的五大属性 

sc.textFile("file:///export/servers/sparkdatas/wordcount.txt").flatMap(x => x.split(" ").map(x => (x,1)). reduceByKey( _ +_ ).saveAsTextFile("file:///export/servers/sparkdatas/out_wordcount")

1. 分区列表

一个RDD就是一个分布式对象集合,本质上是一个只读的分区记录集合,每个RDD可以分成多个分区,每个分区就是一个数据集片段(HDFS上的数据块),并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算。

图中三个block数据块的数据分别属于RDD的三个分区。

2. 作用函数

Spark中RDD的计算是以分区为单位的,每个RDD都会实现算子以达到这个目的。算子会对迭代器进行复合,不需要保存每次计算的结果。

图中textFile、flatMap、map、reduceByKey以及saveAsTextFile都是RDD的算子,也叫作用函数。

3. 依赖关系

不同RDD通过转换操作形成依赖关系,可以实现管道化,中间结果持久化到内存而非落地存储,避免了不必要的读写磁盘开销。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算,实现了Spark的容错性。

图中的RDD依赖关系,也称为血统关系,由父到子依次为  rdd1 ==> rdd2 ==> rdd3 ==> rdd4 ,假设rdd3数据丢失,可以利用rdd2.map来恢复rdd3的数据,体现容错性。

4. 分区函数

当前Spark中实现了两种类型的分区函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分区数量,也决定了parent RDD Shuffle输出时的分区数量。

图中rdd3有键值对,在进行saveAsTextFile作用函数的时候进行了shuffle,把所有分区的数据进行汇总并重新输出分区。比如rdd4的0号分区的数据实际上是rdd3的所有三个分区的数据汇总后切分而来。

5. 位置优先性

按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理的数据块的存储位置。文件在哪一台服务器上面,就在哪一台服务器上面启动task进行运算,尽量避免数据的拷贝。

图中:数据块block1,2都存储在node01服务器上,就在node01服务器上启动任务task对block1,2数据块进行处理;block3在node02服务器上,就在node02服务器上启动任务task对block3数据块进行处理。

 

rdd的创建三种方式

1. 由外部存储文件创建,包括本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、Cassandra、HBase等。

2.​ 由集合创建

3. 由已经存在的rdd转化而来

rdd的算子的介绍以及算子使用

算子分两大类

​transfermation:转换算子,所有的转换算子都是懒执行的,不会马上去执行,只是记录了血统关系 。如果返回值是RDD就一定是转换算子

action:动作算子,触发整个任务真正的去执行

常用算子

转换算子

map:返回一个新的RDD,该RDD由每一个输入元素经过参数函数转换后组成。

mapPartitions:类似于map,区别是以分区为单位处理数据,效率更高 。

filter:返回一个新的RDD,该RDD由经过参数函数计算后返回值为true的输入元素组成。

flatMap:类似于map,但是每一个输入元素可以被映射为0或多个输出元素,所以每个元素经参数函数计算后应该返回一个序列,而不是单一元素。

union:对源RDD和参数RDD求并集后返回一个新的RDD。

intersection:对源RDD和参数RDD求交集后返回一个新的RDD。

distinct:对源RDD进行去重后返回一个新的RDD。默认情况下是8个并行任务来操作,可以传入一个可选的numTasks参数设置并行任务数量。

partitionBy:对RDD进行分区操作,手动指定分区数,如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区,否则会生成ShuffleRDD。

reduceByKey:在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用第一个参数函数将相同key的值聚合到一起,使用第二个参数设置reduce任务的个数。效率更高,先部分聚合,再shuffle。

groupByKey:类似于reduceByKey,区别是直接shuffle后再聚合。效率低下,尽量不要用。

sortByKey:在一个(K,V)的RDD上调用,返回一个按照key进行排序的(K,V)的RDD。

sortBy:类似于sortByKey,但是更灵活,可以用参数函数先对数据进行处理,按照处理后的数据比较结果排序。

join:在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD。

coalesce:缩减分区数,用于大数据集过滤后,提高小数据集的执行效率,不会产生shuffle过程。

mapValues:针对于(K,V)形式的类型只对V进行操作。

动作算子

saveAsTextFile:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本。

saveAsSequenceFile:将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,该目录可以是HDFS或者其他Hadoop支持的文件系统。

DAG的划分以及shuffle过程

DAG(Directed Acyclic Graph)有向无环图,其实就是根据rdd之间的依赖关系来生成的。DAG的生成主要是为了划分stage(调度阶段)。

rdd1 ==> rdd2 ==> rdd3 ==> rdd4

窄依赖是父RDD的分区只被一个子RDD依赖,宽依赖是父RDD的分区被多个子RDD依赖。宽依赖存在shuffle过程。

Spark查询为懒执行,当执行到action算子时开始反向推算。stage根据宽窄依赖来划分,从后往前回溯,遇到窄依赖,加入当前stage,遇到宽依赖,重新开始一个stage。stage的划分最终依据就是shuffle过程,与MapReduce的shuffle类似,都是做数据的分发。

spark当中shuffle过程

负责shuffle过程的执行、计算和处理的组件主要就是ShuffleManager,即shuffle管理器。

spark1.2版本之前,使用的shuffle类叫做HashShuffleManager 。会产生大量的中间结果小文件,进而由大量的磁盘IO操作影响了性能。

spark1.2版本之后,使用SortShuffleManager替代HashShuffleManager。SortShuffleManager将大量临时磁盘文件合并(merge)成一个磁盘文件,因此每个分区Task就只有一个磁盘文件。同时,SortShuffleManager创建一个单独的索引文件,在下一个stage的shuffle read task拉取自己的数据时,只要根据索引读取每个磁盘文件中的部分数据即可。

SortShuffleManager又分为两种机制:普通机制和bypass机制。

bypass运行机制的条件:

   1)分区数量小于spark.shuffle.sort.bypassMergeThreshold参数的值,默认是200。

   2)非聚合类的shuffle算子。

分区的个数小于等于200,启用bypass机制,默认不会对数据进行排序的操作,直接将数据写入到内存缓冲中。写机制和HashShuffle一样,上游task会为每个下游task都创建一个临时磁盘文件,并将数据按key进行hash然后根据key的hash值,将key写入对应的磁盘文件之中。当然,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后,以batch的形式(每批一万条数据)溢写到磁盘文件的。不同的是,最后会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件,等待下游task拉取数据。

在这里插入图片描述

其他情况则启用普通机制,与bypass机制的区别在于,输出到内存缓冲之前多了两个步骤:

   1)在写入默认5M大小的内存结构,如果是reduceByKey这种聚合类的shuffle算子,那么会选用Map数据结构,一边通过Map进行聚合,一边写入内存;如果是join这种普通的shuffle算子,那么会选用Array数据结构,直接写入内存。

   2)内存数据溢写到磁盘之前要先排序。

在这里插入图片描述

bypass不需要进行数据的排序操作,也就节省掉了这部分的性能开销,所以性能比普通机制好。

spark的任务调度

在使用spark-shell等交互式运行或者使用官方提供的run-example实例时,Driver运行在Master节点中;若是客户端使用spark-submit工具进行任务的提交,或者使用IDEA等工具开发运行任务时,Driver是运行在本地客户端的。Driver负责运行Application的main函数并创建SparkContext,SparkContext的目的是为了准备Spark应用程序的运行环境。SparkContext负责资源的申请、任务分配和监控等。当Executor运行结束后,Driver负责关闭SparkContext。

第一步:客户端提交Application 

第二步:启动Driver,运行Application 并创建SparkContext

第三步:SparkContext负责计算RDD之间的依赖关系,构建DAG

第四步:当遇到一个Action操作后就会触发一个Job(作业)的计算,SparkContext调用DAGScheduler把DAG划分多个stage,每个stage对应一个taskSet

第五步:DAGScheduler将Task信息(分区信息以及方法等)序列化并打包成taskSet发给taskScheduler(任务调度器),

第六步:taskScheduler将接收到的taskset分解成为一个个的task,将task分配给各个工作节点(Worker Node)上的executor去执行

第七步:Executor运行结束后,Driver关闭SparkContext

SparkSQL

sparkSQL是spark的一个处理结构化数据的模块,与hive功能类似,通过sql语句的方式可以实现结构化数据分析 。它是将Spark SQL转换成RDD,然后提交到集群中去运行,执行效率非常快!

SparkStreaming的数据抽象是DataFrame和DataSet。与RDD类似,DataFrame也是一个分布式数据容器,然而DataFrame更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即schema。DataSet是Dataframe API的一个扩展,是Spark最新的数据抽象。DataSet是强类型的,知道每个字段的类型。而Dataframe只知道字段不知道类型,就相当于DataSet[Row]。

SparkStreaming的操作对象是SparkSession。

在spark-shell中使用RDD配合样例类,转换成为DataFrame 

读取文本文件成为一个RDD

将RDD给切开,然后将数据转换成为RDD[CASE Class]

将RDD[case class] 转换成为DataFrame

// 读取文本文件成为一个RDD,并将RDD切开
val lineRDD = sc.textFile("file:///export/servers/person.txt").map(x => x.split(" "))

// 定义 case class样例类
case class Person(id:Int,name:String,age:Int)

// 关联RDD 与 case  class,将数据转换成为RDD[CASE Class]
val personRDD = lineRDD.map(x => Person(x(0).toInt,x(1),x(2).toInt))

// 将RDD[CASE Class]转换成为DataFrame
val personDF  = personRDD.toDF

dataFrame的常用的操作

DSL风格语法

// 1 查看DataFrame当中的数据
personDF.show

// 2 查看DataFram当中部分字段的数据
personDF.select(personDF.col("name")).show
personDF.select("name").show
personDF.select(col("name"), col("age")).show
personDF.select($"name", $"age").show

// 3 打印DataFrame的Schema信息
personDF.printSchema

//4 查询所有的name和age,并将age+1
personDF.select(col("id"), col("name"), col("age") + 1).show
personDF.select(personDF("id"), personDF("name"), personDF("age") + 1).show
personDF.select($"name",$"age",$"age" + 1).show

// 5 过滤age大于等于25
personDF.filter(col("age") >= 25).show
personDF.filter($"age" > 25).show

// 6 统计年龄大于30岁的人数
personDF.filter(col("age")>30).count()

// 7 按年龄进行分组并统计相同年龄的人数
personDF.groupBy("age").count().show

SQL风格语法

// 1 将DataFrame注册成表
personDF.registerTempTable("person")

// 2 使用sql语句实现数据的查询
// 2.1 查询年龄最大的前两名
spark.sql("select * from person order by age desc limit 2").show
// 2.2 显示表的Schema信息
spark.sql("desc person").show
// 2.3 查询年龄大于30的人的信息
spark.sql("select * from person where age > 30").show

通过JavaAPI编写SparkSQL程序,读取文本文件创建DataFrame

第一种方式:使用RDD配合样例类

利用反射机制,推导包含某种类型的RDD,通过反射将其转换为指定类型的DataFrame,适用于提前知道RDD的schema。

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

case class Person(id:Int,name:String,age:Int)

object SparkDF {
  /**
    * 需求:读取文件,生成RDD,配合样例类,将RDD转换成为DF
    */
  def main(args: Array[String]): Unit = {
    //读取文件成为RDD,对RDD进行切割
    //定义样例类,将RDD转换成为 RDD[case class]
    //将rdd 转换成为  df

    //获取sparkSession对象
    val sparkSession: SparkSession = SparkSession.builder().appName("sparkSQL").master("local[2]").getOrCreate()

    //通过sparkContext来读取文件
    val sparkContext: SparkContext = sparkSession.sparkContext
    sparkContext.setLogLevel("WARN")

    //读取文件
    val fileRDD: RDD[String] = sparkContext.textFile("file:///F:\\person.txt")

    //对rdd进行切割
    val map: RDD[Array[String]] = fileRDD.map(x => x.split(" "))

    val personRDD: RDD[Person] = map.map(x => Person(x(0).toInt,x(1),x(2).toInt))

    //需要导入隐式转换的包
    import sparkSession.implicits._
    //将RDD转换成为了DF 得到personDF
    val personDF: DataFrame = personRDD.toDF()
    
    sparkContext.stop()
    sparkSession.close()

  }
}

第二种方式:使用Row对象配合StructType

通过编程接口StructType与RDD进行交互获取schema,并动态创建DataFrame,在运行时决定列及其类型。

    // 获取sparkSesssion对象,通过sparkContext读取文件的过程略去

    // 对rdd进行切割
    val map: RDD[Array[String]] = fileRDD.map(x => x.split(" "))

    val rowRDD: RDD[Row] = map.map(x => Row(x(0).toInt,x(1),x(2).toInt))

    val structType: StructType = new StructType().add("id",IntegerType).add("name",StringType).add("age",IntegerType)

    // 给两个参数  RDD[Row]  StructType  将RDD转换成DF
    val dataFrame: DataFrame = sparkSession.createDataFrame(rowRDD,structType)

sparkSQL支持spark on hive,使用sparkSession直接操作hql语句

HiveContext对应spark-hive项目,与hive有部分耦合,支持hql,是SqlContext的子类。在Spark2.0之后,HiveContext和SqlContext在SparkSession进行了统一,可以通过操作SparkSession来操作HiveContext和SqlContext。

举例:创建hive表,加载student.csv,进行数据分析。

import org.apache.spark.SparkContext
import org.apache.spark.sql.SparkSession

object SparkHive {

  def main(args: Array[String]): Unit = {
    //通过spark使用hive的语法,直接创建hive表,然后加载数据即可

    //获取sparkSession
    val sparkSession: SparkSession = SparkSession.builder().master("local[2]").appName("sparkHive").enableHiveSupport() //开启对hive语法的支持
      .getOrCreate()

    val context: SparkContext = sparkSession.sparkContext

    context.setLogLevel("WARN")

    //创建表
    sparkSession.sql("create table if not exists  student (id int,name string,age int) row format delimited fields terminated by ',' ")
    //加载数据
    sparkSession.sql("load data local inpath './datas/student.csv' overwrite into table student")
    //查看表
    sparkSession.sql("select * from student").show()

    context.stop()
    sparkSession.close()

  }

}

Spark Streaming

sparkStreaming是spark当中用于实时处理的一个模块,是流式批处理引擎,主要是用于做一些实时处理的功能,类似于storm,其基本原理是把输入数据以某一时间间隔批量的处理,当批处理间隔缩短到秒级时,便可以用于处理实时数据流。sparkStreaming支持各种各样的数据源,例如kakfa,flume,tcp套接字。

SparkStreaming的数据抽象是DStream,代表持续性的数据流和经过各种Spark算子操作后的结果数据流。在内部实现上,DStream是一系列连续的RDD来表示。每个RDD含有一段时间间隔内的数据。

SparkStreaming的操作对象是StreamingContext 。

kafka数据源

​ 0.8版本整合,有两种数据接收方式

ReceiverDStream:使用使用kafka的高阶API进行消费,offset保存在zk当中,使用at least once消费模式,会造成数据的重复消费。数据重复消费的原因是 消费者​每隔一段时间自动提交一次offset到zk当中去保存,如果一批数据刚消费完,消费者就宕机了,offset没来得及提交到zk中,那么等到消费者恢复消费的时候,会重复消费zk中没来得及记录的offset对应的数据。

DirectDStream:使用kafka的低阶API进行消费,offset自动保存在kafka的topic里面,使用at most once消费模式,会造成数据丢失。数据丢失的原因是 消费者消费到了这个消息,先自动提交offset给kafka的topic,再处理这个消息,如果这时候消费者没来得及处理这个消息就宕机了,那么等到消费者恢复消费的时候按照kafka中topic的最新offset进行消费,这条消息就会丢失。0.8版本也可以设置手动提交offset,但是很麻烦。

​ 0.10版本整合,有一种数据接收方式

​DirectDStream:使用lowLevelAPI进行消费,offset保存在topic里面,配合手动提交offset,使用exactly once消费模式,实现数据消费且仅消费一次。可以关闭自动提交,在数据消费完后手动提交offset给Kafka的topic,可以避免数据丢失。如果数据刚消费完,没来得及提交offset,消费者就宕机了,那么等到消费者恢复消费的时候也会重复这条消息,但是重复消费的概率和数据量都比0.8版本的高阶API小。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值