Spark

434 阅读10分钟

spark项目

gitee.com/elaiza/spar…

rdd算子

blog.csdn.net/u013998466/…

rdd概述

1. RDD
	-弹性分布式数据集
	-弹性
		*存储
		*计算与容错
		*分区  一个分区对应一个Executor
	-分布式
		不同分区中的数据会分配给集群中的不同服务器节点进行计算

	-数据集
		和集合不一样,没有存放数据,存的是计算逻辑

2.RDD的五个特性
	-一组分区 getPartitions

	-分区计算函数 compute

	-RDD之间的依赖 getDependence

	-分区器Partitioner(对 KV 类型的RDD)

	-数据存储的优先位置  getreferedLocation

3.RDD创建的方式
	-通过内存集合创建
	-读取外部文件创建
	-通过RDD的转换算子

4.创建RDD分区方式
	-通过内存集合创建
		*默认的分区方式
			取决于分配给当期应用的CUP核数
		*指定分区
			def posttions(length:Long,nunmSlices:Int):Ierator[(Int,Int)] = {
				...
			}

	-读取外部文件创建
		*默认的分区方式
			取决于分配给当前应用的CUP核数和2取最小值

		*指定分区
			>FileInputFormat getSplits 切片
			>LineRecorReader next      读取

	-通过RDD的转换算子

5.RDD的常用算子
	-转换算子
		转换算子执行完成后,会创建新的RDD,不会马上执行计算
		shuffle 就是 把分区里的数据 写入到磁盘,又下个RDD再读磁盘的数据
	
	-行动算子
		行动算子执后,才会触发计算
		reduce
			对RDD中的元素进行聚合

		*collect.foreach和foreach
			>collect.foreach
				将每一个Excutor中的数据收集到Driver,形成一个新的数组
				.foreach 不是一个算子,是集合的方法,是对数组中的元素进行遍历

			>foreach
				对RDD中的元素进行遍历

		*count
			获取RDD中元素的个数

		*countByKey
			获取RDD中每个key对应的元素个数

		*first
			获取RDD中第一个个元素

		*take
			获取RDD中前几个元素

		*takeOrdered
			获取排序后的RDD中的前几个元素

		*aggreate&fold
			>aggreateByKey 处理kv类型的RDD,并且在进行分区间聚合的时候,初始值不参与计算
			>fold 是 aggreate的简化版

		*save相关的算子
			>saveASsTextFile
			>saveAsObjectFile
			>saveAsSequenceFile(只针对kv类型的RDD)

6.序列化以及闭包检查
	-为什么要序列化
		因为在spark程序中,算子相关的操作在Excutor上执行,算子之外的代码在Dricer端执行
		在执行有些算子的时候,需要使用到Driver里面定义的数据,这就涉及到了跨进程或者跨节点之间的通信
		所以要求传递给Exctor中的数组所属的类型必须实现Serializable接口

	-如何判断是否实现了序列化的接口
		在作业job提交之前,其中有一行代码 val cheanF = sc.clean(f),用于进行闭包检查
		之所以叫闭包检查,是因为在当前函数的内部访问了外部函数的变量,属于闭包的形式。
		如果算子的参数是函数的形式就会存在这种情况


7.查看RDD的血缘关系以及依赖关系
	-血缘关系
		toDebugString

	-依赖关系
		>dependencies
		>窄依赖
			父RDD一个分区中的数据,还是交给子RDD的一个分区处理
		>宽依赖
			父RDD一个分区中的数据,交给子RDD的多个分区处理

8.Spark的Job调度
	-集群(Standalone|Yarn)
		*一个Spark集群可以同时运行多个Spark应用

	-应用
		*我们所编写的完成某些功能的程序
		*一个应用可以并发的运行多个Job

	-Job
		*Job对应着我们应用中的行动算子,每次执行一个行动算子,都会提交一个Job
		*一个Job由多个Stage组成

	-Stage
		*一个宽依赖做一次阶段的划分
		*阶段的个数 =  宽依赖个数  + 1
		*一个Stage由多个Task组成

	-Task
		*每一个阶段最后一个RDD的分区数,就是当前阶段的Task个数
		

9.RDD的持久化
	-cache
		底层调用的就是persist,默认存储在内存中

	-persist
		可以通过参数指定存储级别

	-checkpoint
		*可以当做缓存理解,存储在HDFS上,更稳定
		*为了避免容错执行时间过长

	-缓存不会切断血缘,但是检查点会切断血缘

10.数据的读取和保存
	-textFile
	-sequenceFile
	-objectFile
	-Json	
		本质还是通过textFile读取文本,对读到的内容进行处理
	-HDFS
	-MySQL
		map-------mapPartition
		foreach---foreachPartition

11.在Spark里面,有三大结构
	-RDD
		弹性分布式数据集

	-累加器
		分布式共享只写变量

	-广播变量
		分布式共享只读变量
------------------------------------------------------------------------------------------------
1.SparkSQL和Hive对比
	-hive---引擎---MR
	-SparkSQL---引擎---spark

2.SparkSQL对数据集的抽象
    -RDD
	-DataFrame
		*spark.read.不同类型的文件
		*通过SQL风格操作df,需要创建临时视图
		*通过DSL风格操作df,不需要创建临时视图
		*DF->RDD 	rdd
		*RDD->DF 	toDF

		*在编程的时候,需要导入   import SparkSession对象名.implicits._

	-DataSet(强类型)
		*RDD->DS 	toDS
		*DS->RDD 	rdd
		*DF->DS 	as[类型]
		*DS->DF 	toDF

3.用户自定义函数
	-UDF 
		一进一出
	-UDAF
		多进一出
		*弱类型 	继承UserDefinedAggregateFunction
		*强类型 	Aggregator[输入,缓存,输出]

	-UDTF
		一进多出

4.文件的加载与数据保存
	-spark.read.format("类型").load("路径")

	-df.writer.format("类型").save("路径")

------------------------------------------------------------------------------------------------
1.SparkStreaming
	-是Spark的内置模块,主要用于实时计算
	-微批次流式实时计算框架

	-离线和实时
		数据处理的延迟

	-批和流式

2.架构图

3.背压机制
	
4.创建方式
	-DStream 是SparkStreaming对处理的数据集的一个抽象
	-读取指定端口数据创建DS
	-通过RDD队列创建DS(了解)
	-通过定义Receiver读取指定数据源数据创建DS
	-通过读取kafka数据源创建DS
		*spark-streaming-kafka-0-8(Kafka 0.8 support is deprecated as of Spark 2.3.0)
			>Receiver DStream
				&默认情况下,offset维护在zk中
			>Direct DStream
				&默认情况下,offset维护在checkpoint检查点,需要改变SparkStreamingContext的创建方式
				&可以手动指定offset维护位置,为了保证数据的精准一致性,一般维护在有事务的存储上
			
		*spark-streaming-kafka-0-10
			>Direct DStream

5.DStream算子
	-转换算子
		*无状态的转换操作
			transform
			map\flatmap...
		*有状态的转换操作
			第一个参数Seq,相同的key对应的value的集合
			第二个参数Option,相同的key对应的状态(上一个采集周期计算的结果)
			&updateStateByKey(Seq,Option)

			&window	
	-输出算子

6.Spark几个重要的关系对比
								SparkCore 				SparkSQL 	  				SparkStreaming

	程序执行的入口点			SparkContext(sc) 		SparkSession(sparkt)		StreamingContext(ssc)
	对数据的抽象 				RDD 					DF|DS(DataSet)  			DS(DStream)	

7.内核源码分析
	-第一部分:将App部署到Yarn服务器
	-第二部分:Job以及任务调度过程
		App->Job->Stage->Task
	-第三部分:提交Task到Executor
	-第四部分:Shuffle过程分析



==============提交Task到Executor=============
App->Job->Stage->Task

1.Driver端任务提交 org.apache.spark.scheduler.DAGScheduler
	-submitMissingTasks
		//每一个Task对应处理一个分区的数据,将多个Task放到TaskSet中进行提交
		*taskScheduler.submitTasks(new TaskSet)
			//在Task之前,创建了TaskSetManager对TaskSet进行封装
			>val manager = createTaskSetManager(taskSet, maxTaskFailures)

			//将封装好的TaskSetManager放到资源调度器中
			//先进先出: org.apache.spark.scheduler.FIFOSchedulableBuilder(默认)	
			//公平:	org.apache.spark.scheduler.FairSchedulableBuilder
			>schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties)
			//触发底层资源调度	 底层调用的是CoarseGrainedSchedulerBackend.reviveOffers
			>backend.reviveOffers() 
				//给Driver终端发送一条标记位ReviveOffers的消息,
				&driverEndpoint.send(ReviveOffers)

				&Driver终端会通过receive的方法,接收消息并对其进行处理
					 #case ReviveOffers =>makeOffers()	
					 	//获取可用的Executor
					 	~val activeExecutors = executorDataMap.filterKeys(executorIsAlive)
					 	~val workOffers = activeExecutors.map	
					 	//	scheduler.resourceOffers(workOffers) 决定Task应该交给哪个Executor处理
					 	~运行Task launchTasks(scheduler.resourceOffers(workOffers))
					 		//因为要将Task提交到Executor运行,所以需要进行序列化
					 		+val serializedTask = ser.serialize(task)
					 		//给Executor终端发送Task
					 		+executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))



2.Executor端接收Task并运行
	-receive
		//对接收到的消息类型进行匹配,匹配LaunchTask
		*case LaunchTask(data)
			//对接收到的Task进行反序列化
			>val taskDesc = ser.deserialize[TaskDescription](data.value)
			//运行Task
			>executor.launchTask
				//从线程池中获取线程,执行task
				&threadPool.execute(tr)
					#TaskRunner.run
						~task.run
							+runTask(context)
								++ShuffleMapTask
								++ResultTask


开启shell

# 本地 shell 
./bin/spark-shell

# 集群 shell(前提是开启spark集群:./sbin/start-all.sh)
./bin/spark-shell --master spark://master:7077

# yarn sparkshell
./bin/spark-shell --master yarn

提交spark任务

# 在本地跑
./run-example SparkPi 1 --master local[2]

# 在spark集群(前提是开启spark集群:./sbin/start-all.sh)
./bin/spark-submit \
--master spark://master:7077 \
--class org.apache.spark.examples.SparkPi \
./examples/jars/spark-examples_2.11-2.1.1.jar \
100

# yarn -client模式 方式部署(用于测试)
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode client \
./examples/jars/spark-examples_2.11-2.1.1.jar \
100

# yarn -cluster模式 方式部署(用于生产)
./bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
./examples/jars/spark-examples_2.11-2.1.1.jar \
100

Rdd

Application->job->stage->task(partition) 每一层都是 1 对 n 的关系

job: 一个行动算子就是一个job
stage: 是一个TaskSet
task(partition): 每个分区就是一个任务(每个stage里的最后一个的分区数量),发送到不同的Executor执行

k-v算子
1. partitionBy 
val rdd = sc.parallelize(Array((1,'a'),(2,'b'),(3,'c'),(4,'d')), 4)
// 根据key来取模分区的分区器
val rdd2 = rdd.partitionBy(new org.apache.spark.HashPartitioner(2))
rdd2.glom.collect()

2.groupByKey
val rdd = sc.parallelize(Array("a","a","b","v","b","b")).map(x=>(x,1))
val rdd2 = rdd.groupByKey()
rdd2.map(t => (t._1,t._2.sum)).collect()

3.reduceByKey
val rdd = sc.parallelize(Array("a","a","b","v","b","b")).map(x=>(x,1))
rdd.reduceByKey(_+_).collect // 相同键的值两两相加

DataFrame(结构)

val df = spark.read.json("file:////usr/local/src/data/spark_about/demo.json")
// 临时视图表,session退出就失效
df.createTempView("demo")
spark.sql("select * from demo").show

// 全局视图表
df.createGlobalTempView("global")
spark.sql("select * from global_temp.global").show

// DSl
1.df.printSchema
2.df.select("name").show()
3.df.select($"name", $"age"+1).show()
4.df.filter($"age" <= 20).show()
5.df.groupBy("age").count().show()

// RDD => DataFrame
import spark.implicits._
val rdd = sc.parallelize(Array("a","a","b","v","b","b"))
rdd.toDF("id")

// RDD => DataFrame
创建样例类,根据样例类来将RDD转换为DataFrame
case class People(name:String, age:Int)

val rdd = sc.parallelize(List(("a",1),("a",3),("b",4),("b",19),("a",30),("c",1)))

val peopleRDD = rdd.map( t => { People(t._1, t._2) })

val df = peopleRDD.toDF

df.show()

// DataFrame => RDD
val rdd = df.rdd

DataSet(结构+类型)

样例类是有结构和类型的,所以可以直接把rdd 转成 DataSet
case class People(name:String, age:Int)
val caseClassDS = Seq(People("Andy",32)).toDS()

sparksql 两大类: DataFrame和DataSet

RDD+结构 = DataFrame+类型 = DataSet

rdd -> toDF
rdd.toDF(字段名称)

rdd.toFS

DataFrame.rdd
DataSet.rdd

UDF 用户自定义函数

spark.udf.register("addName",(x:String) => "name:" + x)
val df = spark.read.json("file:////usr/local/src/data/spark_about/demo.json")
df.createTempView("info")
spark.sql("select addName(name) from info").show

UDAF 用户自定义聚合函数(弱类型)


/**
* 第一步:声明用户自定义聚合函数(弱类型)
* 第二步:注册函数
*/
import org.apache.spark.SparkConf
import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types.{DataType, DoubleType, LongType, StructType}

object SparkSql_UDAF {
    def main(args: Array[String]): Unit = {
        val sparkConf = new SparkConf()
            .setMaster("local[*]")
            .setAppName("SparkSql_UDAF")
        val spark = SparkSession
            .builder()
            .config(sparkConf)
            .getOrCreate()

        import spark.implicits._

        // 创建聚合函数对象 + 注册聚合函数对象
        val udaf = new MyAgeAvgFunction
        spark.udf.register("avgAge", udaf)

        // 创建DF
        val rdd = spark.sparkContext.parallelize(List((1,"elaiza",24),(2,"Yw",25),(3,"ee",25)))
        val frame = rdd.toDF("id","name","age")
        frame.createOrReplaceTempView("info")

        spark.sql("select avgAge(age) from info").show()

        spark.stop()
    }

}


// 创建一个算年龄平均数的用户自定义聚合函数
class MyAgeAvgFunction extends UserDefinedAggregateFunction {

    // 函数输入的数据结构
    override def inputSchema: StructType = {
        new StructType().add("age", LongType)
    }

    // 计算时的数据结构
    override def bufferSchema: StructType = {
        new StructType().add("sum", LongType).add("count", LongType)
    }

    // 函数返回的数据类型
    override def dataType: DataType = DoubleType

    // 函数是否稳定
    override def deterministic: Boolean = true

    // 计算之前的缓冲区的初始化
    override def initialize(buffer: MutableAggregationBuffer): Unit = {
        buffer(0) = 0L
        buffer(1) = 0L
    }

    // 根据查询的结果更新缓冲区数据
    override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
        buffer(0) = buffer.getLong(0) + input.getLong(0)
        buffer(1) = buffer.getLong(1) + 1
    }

    // 将多个节点的缓冲区合并
    override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
        // sum
        buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)

        // count
        buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
    }

    // 计算
    override def evaluate(buffer: Row): Any = {
        buffer.getLong(0).toDouble / buffer.getLong(1)
    }
}

UDAF 用户自定义聚合函数(强类型)

package sql

import org.apache.spark.SparkConf
import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.{Encoder, Encoders, Row, SparkSession}

object SparkSql_UDAF_Class {

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

        val sparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkSql_UDAF")

        val spark = SparkSession
            .builder()
            .config(sparkConf)
            .getOrCreate()

        import spark.implicits._

        // 创建聚合函数对象
        val udaf = new MyAgeAvgClassFunction
        // 将聚合函数转化为查询列
        val avgCol = udaf.toColumn.name("agvAge")

        // 创建DataSet
        // {"name":"M", "age":21}
        val frame = spark.read.json("file:///Users/elaiza/Desktop/spark-demo/in/demo.json")
        
        val ds = frame.as[UserBean]

        // 应用函数
        ds.select(avgCol).show()

        spark.stop()
    }

}
case class UserBean (name:String, age:BigInt)
case class AvgBuffer (var sum:BigInt, var count:Int)

// 声明用户自定义聚合和函数(强类型)
class MyAgeAvgClassFunction extends Aggregator[UserBean, AvgBuffer, Double] {
    override def zero: AvgBuffer = {
        AvgBuffer(0, 0)
    }

    /**
     * 聚合数据
     * @param b
     * @param a
     * @return
     */
    override def reduce(b: AvgBuffer, a: UserBean): AvgBuffer = {
        b.sum = b.sum + a.age
        b.count = b.count + 1

        b
    }

    /**
     * 缓冲区的合并操作
     * @param b1
     * @param b2
     * @return
     */
    override def merge(b1: AvgBuffer, b2: AvgBuffer): AvgBuffer = {
        b1.sum = b1.sum + b2.sum
        b1.count = b1.count + b2.count

        b1
    }

    /**
     * 完成计算
     * @param reduction
     * @return
     */
    override def finish(reduction: AvgBuffer): Double = {
        reduction.sum.toDouble / reduction.count
    }

    override def bufferEncoder: Encoder[AvgBuffer] = Encoders.product

    override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}
神秘代码:22126-CC8C-C991-A1DA-7378