bingmous

欢迎交流,不吝赐教~

导航

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之间的解耦合
  • 核心概念
    • 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模式

 

 

 

 

 

posted on 2020-11-20 16:19  Bingmous  阅读(203)  评论(0编辑  收藏  举报