Spark学习笔记(详细)
Spark Core
第1章 Spark 概述
- Spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎
- Spark和Hadoop 的根本差异是多个作业之间的数据通信问题: Spark多个作业之间数据通信基于内存,而Hadoop基于磁盘
- Spark是基于内存的,所以在实际的生产环境中,由于内存的限制,可能会由于内存资源不够导致Job 执行失败,此时MapReduce其实是一个更好的选择,所以Spark并不能完全替代MR
- Spark核心模块
- Spark Core:提供Spark最基础与最核心的功能
- Spark SQL:是Spark用来操作结构化数据的组件。通过Spark SQL,用户可以使用SQL或者Apache Hive 版本的SQL 方言(HQL)来查询数据
- Spark Streaming:是Spark平台上针对实时数据进行流式计算的组件,提供了丰富的处理数据流的API
第2章 Spark 快速上手
- IDEA增加Scala插件
- 修改Maven的POM文件
- WordCount
//local单核单线程,local[2]使用两个核,local[*]使用所有的核
val sparkConf = new SparkConf().setMaster("local").setAppName("wordcount")
val sc = new SparkContext(sparkConf)
sc.makeRDD(List(1,2,3,4)) //使用默认并行度,会查找配置文件参数和环境核数
sc.makeRDD(List(1,2,3,4), 4) //使用4个核,实际取决于环境
sc.textFile("input") //可以是目录
sc.textFile("input/word.txt") //可以是文件
sc.textFile("input/word*.txt") //可以使用通配符
rdd.saveAsTextFile("目录") //按分区方式保存数据
sc.stop()
第3章 Spark运行环境
Local模式,当前spark shell运行任务情况查看端口4040
不需要其他任何节点资源就可以在本地执行Spark代码的环境,只用一个节点
## 修改spark-local/conf/spark-env.sh文件,增加如下内容
SPARK_DIST_CLASSPATH=$(/opt/module/hadoop-2.7.2/bin/hadoop classpath)
## 启动
bin/spark-shell --master local[*]
## 测试命令行工具
sc.textFile("file:///data/word.txt").flatMap(_.split("")).map((_,1)).reduceByKey(_+_).collect
## 提交应用
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master local[2] \
./examples/jars/spark-examples_2.12-2.4.5.jar \
10
Standalone模式,master端口8080
只使用Spark自身节点运行的集群模式
## 修改spark-env.sh文件,增加如下内容
SPARK_DIST_CLASSPATH=$(/opt/module/hadoop-2.7.2/bin/hadoop classpath)
export JAVA_HOME=/opt/module/jdk1.8.0_144
SPARK_MASTER_HOST=localhost102
SPARK_MASTER_PORT=7077
## 修改slaves文件,添加work节点
localhost02
localhost03
localhost04
## 分发
## 提交
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://localhost102:7077 \
./examples/jars/spark-examples_2.12-2.4.5.jar \
10
- 配置历史服务 端口18080,spark-shell停止掉后,集群监控linux1:4040页面就看不到历史任务的运行情况,所以开发时都配置历史服务器记录任务运行情况
## 配置历史服务 hdfs目录要提前存在
# 修改spark-default.conf文件,配置日志存储路径:
spark.eventLog.enabled true
spark.eventLog.dir hdfs://localhost102:9000/directory
# 修改spark-env.sh文件, 添加日志配置
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080
-Dspark.history.fs.logDirectory=hdfs://localhost102:9000/directory
-Dspark.history.retainedApplications=30"
# 重启集群和历史服务
sbin/start-all.sh
sbin/start-history-server.sh
-
配置高可用,需要使用zookeeper
Yarn模式
## 修改spark-env.sh文件,增加如下内容
SPARK_DIST_CLASSPATH=$(/opt/module/hadoop-2.7.2/bin/hadoop classpath)
export JAVA_HOME=/opt/module/jdk1.8.0_144
YARN_CONF_DIR=/opt/module/hadoop/etc/hadoop
## 修改hadoop/etc/hadoop/yarn-site.xml
<!--是否启动一个线程检查每个任务正使用的物理内存量,如果任务超出分配值,则直接将其杀掉,默认
是true -->
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
</property>
<!--是否启动一个线程检查每个任务正使用的虚拟内存量,如果任务超出分配值,则直接将其杀掉,默认
是true -->
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>
## 提交
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
./examples/jars/spark-examples_2.11-2.1.0.jar \
10
- 1.部署Application和服务更加方便,只需要yarn服务,包括Spark,Storm在内的多种应用程序不要要自带服务,它们经由客户端提交后,由yarn提供的分布式缓存机制分发到各个计算节点上。
- 2.资源隔离机制,yarn只负责资源的管理和调度,完全由用户和自己决定在yarn集群上运行哪种服务和Applicatioin,所以在yarn上有可能同时运行多个同类的服务和Application。Yarn利用Cgroups实现资源的隔离,用户在开发新的服务或者Application时,不用担心资源隔离方面的问题。
- 3.资源弹性管理,Yarn可以通过队列的方式,管理同时运行在yarn集群种的多个服务,可根据不同类型的应用程序压力情况,调整对应的资源使用量,实现资源弹性管理。
第4章 Spark运行架构
- 标准master-slave 的结构
- 核心组件:
- Driver(用于执行Spark 任务中的main 方法,负责实际代码的执行工作)
- 将用户程序转话为job
- 在Executor之间调度任务task
- 跟踪Executor执行情况
- 通过UI展示运行情况
- Executor
- 负责运行组成Spark 应用的任务,并将结果返回给驱动器进程
- 通过自身的块管理器(Block Manager)为用户程序中要求缓存的RDD 提供内存式存储
- Master & Worker
- Master主要负责资源的调度和分配,类似于RM
- Worker 运行在集群中的一台服务器上,由Master 分配资源对数据进行并行的处理和计算,类似与NM
- ApplicationMaster
- 用于向资源调度器申请执行任务的资源容器Container,运行用户自己的程序任务job,监控整个任务的执行,跟踪整个任务的状态,处理任务失败等异常情况。RM与Driver之间的解耦合
- Driver(用于执行Spark 任务中的main 方法,负责实际代码的执行工作)
- 核心概念
- Executor(JVM进程),Core(虚拟CPU核心数)
- 并行度
- 有向无环图,spark作业内部(RDD)的有向无环,Tez是作业之间的有向无环
- 提交流程
- Yarn Client,Driver在客户端执行,申请运行ApplicationMaster
- Yarn Cluster,ApplicationMaster就是Driver(一个线程)
- 主要区别:Driver 程序的运行节点位置不同
- 1,任务提交以后,向ResourceManager申请运行ApplicationMaster
- 2,RM在合适的NodeManager节点上分配Container,运行ApplicationMaster
- 3,ApplicationMaster申请向RM申请Executor运行资源
- 4,RM分配Container,运行Executor
- 5,Executor进程启动后向Driver反向注册,全部注册后Driver开始执行main函数
- 6,之后执行到行动算子时,触发一个job,并根据宽依赖划分Stage,每个Stage生成对应的TaskSet,之后将Task发到各个Executor上执行
第5章 Spark核心编程
RDD
- 弹性分布式数据集,spark最基本的数据处理模型,代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。
- 弹性:
- 存储的弹性:内存与磁盘的自动切换
- 容错的弹性:数据丢失可以自动恢复
- 计算的弹性:计算出错重试机制
- 分片的弹性:可根据需要重新分片
- 分布式:数据存储在大数据集群不同节点上
- 数据集:RDD封装计算逻辑,不保存数据
- 不可变:RDD封装计算逻辑,是不可以改变的,想要改变只能产生新的RDD,在新的RDD里面封装计算逻辑
- 可分区,并行计算
- 弹性:
- 核心属性:
- 分区列表:RDD 数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性
- 分区计算函数(对每个分区进行计算),Spark 在计算时,是使用分区函数对每一个分区进行计算
- 依赖关系(RDD之间的依赖),RDD 是计算模型的封装,当需要将多个计算模型进行组合时,就需要将多个RDD 建立依赖关系,(宽依赖:一个父RDD,窄依赖)
- 分区器(可选,决定分区如何分区),当数据为KV 类型数据时,可以通过设定分区器自定义数据的分区
- 首选位置(可选,优先位置,计算位置的选择),计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
- 执行原理:类似IO,体现了装饰者模式,延迟加载(用的时候才执行)
- Driver负责调度任务,Executor负责执行任务,Driver将任务封装成Task(包含计算逻辑和数据,hdfs上的地址)发送给Executor
- 1,启动yarn集群环境
- 2,spark申请资源
- 3,spark将计算逻辑根据分区进行划分不同的任务,将每个分区和计算函数封装成一个Task,放在TaskPool
- 4,调度节点(Driver)根据计算节点(Executor)的状态发送到对应的节点进行计算
- RDD的创建
- 从内存(集合)创建:sc.parallelize(list),sc.makeRDD(list),底层是调用前者,第二个参数决定分区数量和并行度
- 从磁盘(文件)创建:sc.textFile("文件或目录"),默认是HadoopRDD,一行一行读数据
- 从其他RDD创建
- new RDD(一般框架自己使用)
- RDD并行度与分区
- RDD分区数量就是并行度,一个分区会封装成一个Task,但是资源不够的时候分区不等于并行度;
- 默认值:从conf获取,获取不到使用当前机器的核数(取决于环境)
- makeRDD()分区存储,对集合中的数据基本上是平均分(一个算法)
- textFile()分区存储,参数是minPartitions = min(defaultParallelism, 2)表示预计分几片,用总的字节数除以minPartition,如果剩余的大于每个单个的1.1倍,则再分一个,实际结果比minPartition大
- 单个文件:按照字节大小分区,如果剩余字节大于一片的1.1倍则再分一个区,读取时按照一行读取,存储时是按照字节的偏移量进行存储的,偏移量范围后闭
- 多个文件的话会对所有文件字节数进行划分,每个文件剩余部分字节数单独分区
- RDD转换算子:产生新的RDD算子,不会触发作业的执行,只是功能的扩展和包装
- 单值类型
- map,对每一条数据计算,默认分区数不变
- mapPartitions(),以分区为单位进行计算,一次性获取分区的所有数据,参数为迭代器,返回也是迭代器
- map与mapPartitions的区别:map每次处理一条数据,mapPartitions每次将一个分区当成一个整体进行处理,如果一个分区没有处理完,那么所有的数据都不会释放,容易出现内存溢出
- mapPartitionsWithIndex(),参数为分区号和迭代器,返回值是迭代器
- glom,将分区内数据转为数组Array
- groupBy(函数),返回值为元组,第一个元素为分组的key,第二个元素为相同key的可迭代集合,将数据进行分组操作,但是分区是不会改变的,也可以传入参数改变分区数
- filter(函数),过滤,参数为返回值类型为Boolean的函数,将数据根据指定的规则进行筛选
- sample(withReplacement: Boolean, fraction: Double),抽取样本,也可以给定种子
- distinct(),去重,也可以传入参数num,改变分区数
- coalesce(num),缩减分区
- repartition(num),扩大分区,底层是coalesce(num, true)
- orderBy(函数),排序之前可以使用函数进行处理,默认升序,可以传入false降序
- union(rdd),并集(分区合并)
- intersection(rdd),交集(保留最大分区,shuffle)
- substring(rdd),差集(分区数为前面的,shuffle)
- zip(rdd),拉链(返回2元组,分区数必须相同,每个分区的数据量必须相同,类型不同可以拉链)
- 双值类型
- partitionBy(),自定义分区
- reduceByKey(),按k分区聚合
- groupByKey(),按k分组,返回元组,第一个元素为分组的key,第二个为相同key的可迭代对象
- reduceByKey与groupByKey的区别:reduceByKey会在shuffle之前进行分区内的聚合操作,减少shuffle落盘的数据量,提高了shuffle的性能
- aggregateByKey(初始值)(),根据k进行聚合,可以分区内和分区间的规则不一样,对于分区内计算,k进行第一次计算时使用初始值参与计算
- foldByKey()(),如果aggregateByKey中分区内和分区间规则相同,可简化为foldByKey()()
- combineByKey(),接收三个参数,第一个参数对相同k的第一个v进行处理,第二和第三个参数是分区内和分区间的计算规则
- reduceByKey,aggregateByKey,foldByKey,combineByKey底层都是combineByKeyWithClassTag
- piple("脚本"),对每个分区调用一次shell脚本
- sortByKey(),按照k排序,参数为true、false
- join(),两个rdd连接,返回值为元组,第一个元素是k,第二个元素是元组为连接的v,多个相同的k会笛卡尔乘积
- leftOuterJoin(),rightOuterJoin()
- cogroup(),先内部连,再外部连
- RDD行动算子,不会产生新的RDD,而是触发作业的执行,得到执行结果
- reduce,聚合数据
- collect,采集数据,以Array形式返回数据,会将所有分区的计算结果拉取到当前节点Driver的内存中,可能OOM
- count,返回处理数据集的个数
- first,返回RDD中第一个元素
- take(num),取num个元素
- takeOrdered,取已经排序后的num个
- aggregate()(),三个参数,初始值,分区内计算规则,分区间计算规则,初始值都参与
- fold,aggregate的简化版本,分区内和分区间计算规则一样
- countByKey
- countByValue
- saveAsTextFile()
- saveAsObjectFile()
- saveAsSequenceFile(),必须是kv类型
- foreach,这个是算子,.collect().foreach()这个是方法,rdd的方法称为算子,算子的逻辑代码是在分布式节点Executor执行的,算子之外的代码是在Driver执行的,想到RDD就要先到Driver和Executor,集合的方法是在当前节点执行的
- RDD序列化:
- 如果算子中使用了算子外的对象,那么在执行时,要保证这个对象能序列化
- 算子操作其实都是闭包,所以闭包有可能包含外部变量,如果包含外部变量,一定要保证外部变量可序列化,spark在提交之前会对闭包变量进行检测,称为闭包检测
- Kryo序列化框架,需要注册,spark已经对一些类型使用了Kryo序列化,transient关键字对Kryo不管用
- RDD依赖关系
- toDebugString,打印RDD依赖关系
- dependencies,打印当前依赖
- OneToOneDependency,下游数据分区依赖不变
- ShuffleDenpendency
- 窄依赖:表示每一个父RDD的Partition 最多被子RDD 的一个Partition 使用,OneToOneDependency extends NarrowDependency
- 宽依赖:表示同一个父RDD 的Partition 被多个子RDD 的Partition 依赖,会引起Shuffle,ShuffleDenpendency
- 阶段的划分(作业分成几段,落盘完才能进行下一阶段):取决于Shuffle的个数,阶段的个数 = Shuffle的个数 + 1,Shuffle将阶段一分为二
- RDD持久化:
- RDD是不保存数据的,所以如果多个RDD需要共享其中一个RDD的数据,必须重头执行,效率非常低,所以需要将一些重复性比较高,比较耗时的操作的结果缓存起来,提高效率
- cache(),缓存RDD数据,在执行行动算子之后,会在血缘关系中添加缓存相关的依赖,一旦发生错误,可以重新执行
- persist(),可以设置不同的级别,存储在磁盘内存,cache默认只在内存中缓存
- checkpoint(),执行前需要设置检查点目录;为了保证数据的准确性,执行时会启动新的job,所以一般与cache结合使用,这样checkpoint的job就可以从cache缓存中读数据;checkpoint()会切断血缘关系,因为它将数据保存在分布式文件系统中,当成了一个数据源,数据相对安全
- RDD分区器:HashPartitioner,RangePattitioner,自定义分区器
- RDD文件读取与保存:saveAsTextFile,saveAsObjectFile,saveAsSequenceFile都有对应的读取方法
累加器
- 累加器用来把Executor 端变量信息聚合到Driver 端。在Driver程序中定义的变量,在Executor 端的每个Task 都会得到这个变量的一份新的副本,每个task 更新这些副本的值后,传回Driver 端进行merge。
- 分布式共享只写变量,累加器变量在Driver端,共享到Executor,每个task会有一个新副本
- sc.longAccumulator(),声明累加器
- sc.doubleAccumulator()
- sc.collectionAccumulator()
- 自定义累加器:创建累加器(重写6个方法),注册,使用,获取累加器的值
广播变量
- 广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个Spark 操作使用
- 先向所在的Executor的缓存中找广播变量,找不到再去Driver端拉取(也可以去附近的Executor上拉取),避免在一个Executor中多个相同的变量占用过多内存
- 分布式共享只读变量
- sc.broadcast(list),声明,value,使用
- 为了解决join出现的性能问题,可以将数据独立出来,防止shuffle操作,在一个Executor内的多个task中,只有一份
Spark SQL
第1章 Spark SQL概述
- Spark SQL 是Spark 用于结构化数据(structured data)处理的Spark 模块,兼容Hive,还可以从RDD、parquet 文件、JSON 文件中获取数据
- 简化RDD的开发,提高开发效率,提供两个编程抽象,DataFrame,DataSet
- DataFrame 是一种以RDD为基础的分布式数据集,带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型;反观RDD,由于无从得知所存数据元素的具体内部结构,Spark Core 只能在stage 层面进行简单、通用的流水线优化。
- DataFrame 是DataSet 的特列
第2章 Spark SQL核心编程
- SparkSession是Spark最新的SQL 查询起始点,实质上是SQLContext 和HiveContext的组合,SparkSession 内部封装了SparkContext,所以计算实际上是由sparkContext 完成的
- 数据模型:DataFrame,DataSet
- RDD是数据,DF是结构,DS是类型,RDD只包含数据,没有结构,DataFrame添加了结构,DataSet添加了类型
- 转换
- RDD转DataFrame:rdd.toDF("id"),给一个结构就行了,这一列或多列都是什么结构
- RDD转DataSet:rdd.toDS,先将rdd通过一个样例类给定类型就可以了
- DF转DS:因为有了结构,给定一个类型就可以了df.as[类型]
- DS转DF:ds.toDF,把DS的类型去掉就可以了
DataFrame
- 创建DataFrame:三种方式,spark的数据源,存在的RDD,Hive Table进行查询返回
- 从Spark数据源创建:
- spark.read.,查看支持的数据格式,val df = spark.read.json("data/user.json")
- SQL语法:先创建临时视图,在使用spark.sql()进行查询
- df.createOrReplaceTempView("people"),创建一个临时表
- val sqlDF = spark.sql("SELECT * FROM people"),查询
- sqlDF.show,显示结果
- df.createGlobalTempView("people"),创建一个全局表
- spark.sql("SELECT * FROM global_temp.people").show(),查询全局表必须加global_temp
- spark.newSession().sql("SELECT * FROM global_temp.people").show(),在新的Session里可以查询全局表
- DSL语法:DataFrame 提供一个特定领域语言(domain-specific language, DSL)去管理结构化的数据。不必再创建临时视图
- df.printSchema,查看Schema信息
- df.select("username").show(),查看某一列数据
- df.select($"username",$"age" + 1).show,列数据进行运算
- df.select('username, 'age + 1).show()
- df.select('username, 'age + 1 as "newage").show(),起别名
- df.filter($"age">30).show,过滤数据
- df.groupBy("age").count.show,分组
- RDD转为DF
- RDD没有结构,指定结构:idRDD.toDF("id").show,使用toDF("列名")
- 通过样例类,将数据转换成样例类的对象,使用idRDD.toDF,使用样例类中的列名,也可以重命名.toDF("id","name")
- DF转为RDD
- df.rdd,返回的是Row对象
DataSet
- 是具有强类型的数据集合,需要提供对应的类型信息(样例类),查询之后使用方便
- 创建DataSet
- val caseClassDS = Seq(Person("zhangsan",2)).toDS(),使用样例类创建
- val ds = Seq(1,2,3,4,5).toDS,使用基本类型的序列创建DataSet
- RDD 转换为DataSet
- sc.makeRDD(List(("zhangsan",30), ("lisi",49))).map(t=>User(t._1, t._2)).toDS SparkSQL 能够自动将包含有case 类的RDD 转换成DataSet,case 类定义了table 的结构,case 类属性通过反射变成了表的列名。Case 类可以包含诸如Seq 或者Array 等复杂的结构。
- DataSet 转换为RDD
- ds.rdd,DataSet 其实也是对RDD 的封装,所以可以直接获取内部的RDD
- DataFrame与DataSet的转换
- df.as[类型],告诉df一个样例类的类型就可以自动转换为DS了
- ds.toDF,直接返回DF
IDEA 开发SparkSQL
- 添加依赖
- 代码实现
- 用户自定义函数
- UDF
- UDAF,弱类型,强类型
数据的加载和保存
- spark.read.format("…")[.option("…")].load("…"),加载数据的通用方法
- df.write.format("…")[.option("…")].save("…"),保存数据的通用方法
- Spark SQL 的默认数据源为Parquet 格式
- Spark SQL 能够自动推测JSON 数据集的结构,并将它加载为一个Dataset[Row],注意:Spark 读取的JSON 文件不是传统的JSON 文件,每一行都应该是一个JSON 串
- Spark SQL 可以配置CSV 文件的列表信息,读取CSV 文件,CSV 文件的第一行设置为数据列
- Spark SQL 可以通过JDBC 从关系型数据库中读取数据的方式创建DataFrame
- Hive,Spark 要接管Hive 需要把hive-site.xml 拷贝到conf/目录下
Spark Streaming
第1章 SparkStreaming概述
- 实时:数据处理的时延在毫秒内响应
- 离线:数据处理的时延在小时、天
- 数据处理的方式:批处理(多条处理),流式(一条一条处理)
- 微批次,准实时的数据处理引擎
第2章 DStream入门
- socketTextStream(),采集一个端口,进行wordcount
第3章 DStream创建
- 队列采集:queueStream()
- 目录采集:textFileStream()
- 自定义数据采集器:继承Receiver,参考socketTextStream
- Kafka数据源
第4章 DStream转换
- 无状态转化操作
- transform(),对转换后的RDD进行操作,是周期性的
- join(),流的join
- 有状态转化操作
- UpdateStateByKey(),从开始运行就一直保持状态,使用updateStateByKey 需要对检查点目录进行配置,会使用检查点来保存状态
- window(),可以设置窗口的大小和滑动窗口的间隔来动态的获取当前Steaming 的允许状态,窗口时长和滑动间隔必须是采集时长的整数倍
- reduceByKeyAndWindow(),一般用于重复数据的范围比较大的场合,可以优化效率
第5章 DStream输出
- 如果StreamingContext 中没有设定输出操作,整个context 就都不会启动
- 输出操作
- print()
- foreachRDD(),与transform类似,只是不需要返回值,transform乣返回值
第6章 优雅关闭
- stop方法一般不放在main线程中,需要将stop方法放在一个新的线程中完成调用,一般需要周期性的判断时机(一般采用第三方程序或存储判断),Spark streaming停止后当前线程也需要停止
- 从检查点恢复:StreamingContext.getActiveOrCreate(cp, f),不是直接new StreamingContext,而是从一个检查点获取,如果没有获取到再执行函数f中的逻辑
Spark 内核
第1章 Spark内核概述
- 核心组件:Driver,Executor
- 通用运行流程
- 任务提交后向资源管理器(比如Yarn的RM)申请运行ApplicationMaster,RM会在指定节点上分配Container运行ApplicationMaster,然后ApplicationMaster会根据提交的配置信息向RM申请运行任务的资源,RM分配相应的Container后,ApplicationMaster运行Executor,然后Executor向Driver进行反向注册,注册后Driver程序就开始执行main函数,执行到行动算子会触发作业,然后根据宽依赖划分阶段,每个阶段生成对应的TaskSet,然后将Task发送给Executor进行执行
第2章 Spark部署模式
- Yarn Cluster
- Yarn Client,运行的Driver程序的节点不一样,Cluster由资源管理器决定的,Client是运行在提交作业的节点进行的
- Standalone Cluster,Master会找一个Worker运行驱动程序,然后向Master注册,Master分配其他Worker启动Executor
- Standalone Client,在提交的节点运行驱动程序,向Master注册程序,Master分配其他Worker启动Executor,反向注册
第3章 Spark通信框架
- Spark 基于Netty 新的RPC 框架,基于Actor 模型,每个通信的组件都有1个收件箱,多个发件箱
- spark中的通信终端:Driver和Executor
- RpcEndpoint:RPC 通信终端
- RpcEnv:RPC 上下文环境
- Dispatcher:消息调度(分发)器:发给自己放入Inbox,发给别人放入OutBox
- Inbox:指令消息收件箱
- RpcEndpointRef:RpcEndpointRef 是对远程RpcEndpoint 的一个引用,要发给某个终端需要有这个终端的引用
- OutBox:指令消息发件箱
- RpcAddress:表示远程的RpcEndpointRef 的地址,Host + Port
- TransportClient:Netty 通信客户端,一个OutBox 对应一个TransportClient,TransportClient不断轮询OutBox,根据OutBox 消息的receiver 信息,请求对应的远程TransportServer
- TransportServer:Netty 通信服务端,一个RpcEndpoint 对应一个TransportServer,接受远程消息后调用Dispatcher 分发消息至对应收发件箱;
第4章 Spark任务调度机制
- Spark任务调度概述,总体来说分两类,一类是stage级调度(DAGScheduler),一类是task调度(TaskScheduler)
- Job:一个行动算子会触发一个Job
- Stage:是job的子集,以宽依赖为界
- Task:是stage的子集,以并行度来衡量,分区数是多少,就有多少task
- spark RDD通过转换算子,形成RDD血缘关系,即DAG,最后行动算子的调用触发job执行,执行过程会创建两个调度器
- Driver初始化SparkContext过程中会初始化
- DAGScheduler,将job切分成不同的stage,将每个stage打包成TaskSet
- TaskScheduler,将DAGScheduler 给过来的TaskSet 按照指定的调度策略分发到Executor 执行,SchedulerBackend提供资源
- SchedulerBackend ,通过ApplicationMaster申请资源,并不断从TaskScheduler中拿到合适的Task分发到Executor 执行
- HeartbeatReceiver,负责接收Executor 的心跳信息,监控Executor的存活状况,并通知到TaskScheduler
Spark Stage级调度
- 从行动算子开始回溯,遇到宽依赖则划分一个stage,直到没有依赖的RDD,使用深度优先遍历,前面的为ShuffleMapStage,后面的为ResultStage,前面的Stage执行完了才能提交后面的
Spark Task级调度
- 调度策略:FIFO(默认),FAIR(三个属性)
- 本地化调度:5种,优先级从高到低,每次先从最高的尝试运行,不行的话等待一段时间,还是不行的话就降级运行,可以通过调大容忍时间,在一定程度上提升性能
失败重试与黑名单机制
- 任务执行失败之后,会记录失败次数,如果没超过最大失败次数则放回调度的任务池里,TaskSetManager会记录它失败的Executor ID和主机及对应的拉黑时间,在这段时间不在这个节点上调度这个任务了
第5章 Spark Shuffle解析
第6章 Spark 内存管理
Spark 优化
第1章 常规性能调优
常规性能调优
- 常规性能调优
- 增加executor个数
- 增加每个executor的cpu个数
- 增加每个executor的内存,1缓存更多的数据,减少磁盘IO,2为shuffle提供更多内存,减少写入磁盘数据,3内存较小会频繁GC,增加内存可以避免频繁GC,提升整体性能
- RDD优化
- 避免在相同的算子和计算逻辑下对RDD进行重复的计算
- 对多次使用的RDD进行持久化(可以序列化的,对于可靠性要求高的数据可以使用副本)
- RDD尽可能早的过滤操作,减少内存占用
- 并行度调节
- 在资源允许的情况下,应尽可能让并行度与资源匹配,官方推荐task数量应该是总cpu核心数的2-3倍(因为有的任务执行的快,执行完毕后可以立马执行下一个task)
- 广播大的变量
- 如果task算子中使用了外部的变量,每个task会有一个变量的副本,这就造成了内存的极大消耗,如果使用广播变量的话每个executor保存一个副本,可以很大程度上减少内存的使用
- 序列化:使用Kryo序列化,java的序列化太重(慢,字节多),效率不高
- 调整本地化等待时长
算子调优
- 使用mapPartitions,map对分区中每一个元素操作,mapPartitions对真个分区操作,如果使用jdbc写入数据,那么每条数据都要建立一个连接,资源消耗大,如果使用mapPartitions,只需要建立一个连接
- 缺点:如果数据量很大的话,使用mapPartitions容易OOM,如果资源允许可以考虑使用mapPartitions代替map
- 使用foreachPartition优化数据库操作,与mapPartitions类似,foreachPartition将RDD的每个分区作为遍历对象
- filter与coalesce的配合使用,过滤后数据量变小,再用原来的去执行浪费资源,有可能导致数据倾斜,可以进行合并分区或者扩大分区(宽依赖,需要设置为true,即开启shuffle,否则不起作用)
- 使用repartition提高SparkSQL的并行度,默认是hive表的文件的切片个数,spark SQL的并行度没法手动更改,可以读取到数据后立马进行扩大分区数,提高并行度
- 使用reduceByKey预聚合,groupByKey 不会进行map 端的聚合,减少磁盘IO,减少对磁盘空间的占用,reduce端的数据也变少
Shuffle 调优
- 增加map端缓冲区的大小,避免频繁溢写到磁盘
- 增加reduce端拉取数据缓冲区大小,减少拉取次数,减少网络传输
- 增加reduce端拉取数据的重试次数,避免因为JVM的full GC或者网络原因导致数据拉取失败进而导致作业执行失败
- 增加reduce端拉取数据等待间隔,拉取失败会等一段时间后重试,以增加shuffle 操作的稳定性
- 增加SortShuffle 排序操作阈值,
JVM调优
- 降低cache 操作的内存占比,1静态内存管理机制,需要调节,2统一内存管理机制,堆内存被划分为了两块,Storage 和Execution
- 增大Executor 堆外内存,有时去拉取Executor数据时,Executor可能已经由于内存溢出挂掉了
- 增大连接等待时长,Executor优先从本地关联的BlockManager获取数据,获取不到会去其他节点上获取数据,反复拉取不到会导致DAGScheduler反复提交几次stage,TaskScheduler反复提交几次Task,大大延长作业运行时间
第2章 Spark数据倾斜
主要指shuffle 过程中出现的数据倾斜问题,是由于不同的key对应的数据量不同导致的不同task 所处理的数据量不同的问题。
- 表现:大部分task 都执行迅速,只有有限的几个task 执行的非常慢或者OOM
- 定位问题:
- 查看代码中的shuffle算子(行动算子),根据代码逻辑判断此处是否会发生数据倾斜
- 查看log文件,定位是在哪一个stage发生的,具体的是哪一个算子
- 解决方案一,避免shuffle过程,如果是按key分组的话,可以把相同的key所对应的value提前进行聚合,这样一个key就对应一条数据了,对一个key的所有value进行处理只需要使用map就可以了
- key颗粒度小,增大数据倾斜的可能性,
- key颗粒度大,降低数据倾斜的可能性,如果没有办法对每一个key进行聚合,可以考虑扩大key的颗粒度
- 解决方案二,过滤导致倾斜的key,单独处理
- 解决方案三:提高shuffle操作中的reduce并行度,并行度增加了每个task的数量就减小了,但是没有从本质上解决数据倾斜的问题,只是尽可能的缓解reduce端的数据压力,如果某个key的数据很多,并行度再大还是在一个task里面
- 解决方案四:使用随机key 实现双重聚合,当使用了类似于groupByKey、reduceByKey 这样的算子时,可以考虑使用随机key 实现双重聚合,先在通过map算子在key上加一个随机数,将key打散,然后进行一次聚合,这样将原本一个task处理的数据分散到其他task上去做局部聚合,然后去掉随机数,再次进行聚合,仅仅适用于聚合类的shuffle操作,不能是join操作
- 解决方案五:将reduce join 转换为map join,将一个小的RDD转换成广播变量,然后使用map算子,避免shuffle,如果两个RDD都比较大,由于广播变量每个Executor都有一份,有可能造成OOM
- 解决方案六:sample采样对倾斜key单独进行join,然后最终的结果进行合并union,如果只有一个key导致数据倾斜,可以单独把这个key的数据拿出来进行join,因为在spark中,如果RDD只有一个key,那么会在shuffle过程中将此key对应的数据打散,由不同的reduce端进行task处理,如果数据量比较大,可以进行采样查看key,不适合导致数据倾斜的key较多的情况
- 解决方案七:使用随机数扩容进行join,如果join操作时,有大量的key导致数据倾斜,那么拆分key也没什么意义,可以将一个RDD的key加1-N的数值,然后另一个RDD的key加1-N的随机值,这样join的时候就会被分到不同的task进行处理了,这样就不会有数据倾斜的问题了,但是比较耗资源,根据资源扩容10倍,100倍
- 如果导致数据倾斜的key比较多,在方案六中加入方案七的思想,先采样,找出导致数据倾斜的key,然后对这些key进行随机数扩容,然后再和普通的key的join结果合并
第3章 Spark 故障排除
- 故障排除一:控制reduce 端缓冲大小以避免OOM
- reduce端缓冲区大小默认48M,可能会OOM,这时需要减小缓冲区大小,典型的以性能换执行的原理,要保证任务能够运行,再考虑性能的优化
- 故障排除二:JVM GC 导致的shuffle 文件拉取失败
- reduce端去拉取数据的时候失败,有时候是由于map端在进行GC,可以通过调整重试次数和拉取间隔时间来对shuffle的性能进行调整增大参数值,使得reduce 端拉取数据的重试次数增加,并且每次失败后等待的时间间隔加长
- 故障排除三:解决各种序列化导致的报错
- RDD的元素的自定义类型,必须是可序列化的
- 算子里面使用的外部自定义变量必须是可序列化的
- 故障排除四:解决算子函数返回NULL 导致的问题
- 不希望有返回值可以返回特殊值,如-1
- 故障排除五:解决YARN-CLIENT 模式导致的本地网卡流量激增问题
- 由于Driver程序需要和Executor频繁的通信,如果使用yarn-client模式,会导致本地流量激增,如果
- 注意,YARN-client 模式只会在测试环境中使用,而之所以使用YARN-client 模式,是由于可以看到详细全面的log 信息,通过查看log,可以锁定程序中存在的问题,避免在生产环境下发生故障
- 故障排除六:解决YARN-CLUSTER 模式的JVM 栈内存溢出无法执行问题
- 调用方法层级过多,or关键字太多,对于or的处理时递归,一条sql语句控制在100个以内通常就不会发生栈溢出
- 故障排除八:持久化与checkpoint 的使用
- 使用checkpoint 的优点在于提高了Spark 作业的可靠性,一旦缓存出现问题,不需要重新计算数据,缺点在于,checkpoint 时需要将数据写入HDFS 等文件系统,对性能的消耗较大
Yarn-client模式
Yarn-Cluster模式
---
本文来自博客园,作者:Bingmous,转载请注明原文链接:https://www.cnblogs.com/bingmous/p/15643698.html