Spark Core
spark 的核心计算 ,用于通用分布式数据处理的引擎。不依赖于任何其他组件,可以运行在任何商用服务器集群上。实现饿了 Spark 的基本功能,包含任务调度、内存管理、错误恢复,与存储系统交互等模块。还包含了对弹性分布式数据集(Resilient Distributed Dataset,简称RDD)的API 定义。
Spark SQL
是Spark用来操作结构化数据的程序包,可以使用SQL或者HQL来对历史数据做交互式查询(即席查询:用户根据自己的需求 自定义查询)。Spark SQL 支持多种数据源,比如Hive表,Parquet 以及 JSON 等。
Spark Streaming
是Spark提供的对实时数据进行流式计算的组件,基于Spark的微批处理引擎,支持各种各样数据源的导入。提供了用来操作数据流的API,并且与Spark Core 中的RDD API 高度对应,唯一依赖的是 Spark Core引擎。
Spark MLib
构建在 Spark 之上的提供常见的机器学习(ML)功能的程序库,支持一系列数据挖掘算法。包括分类、回归、聚类、协同过滤,还提供了模型评估、数据导入等额外的支持功能。
Spark GraphX
图计算(关注事物本身而且关注事物之间的联系)
共同点:
① 都是开源的分布式系统,具有低延迟、可扩展和容错性诸多优点。
② 允许在运行数据流代码时,将任务分配到一列具有容错能力的计算机并行运行。
③ 都提供了简单的API来简化底层实现的复杂程度。
不同点:
① Apache Storm中,先要设计一个用于实时计算的图状结构(topology,拓扑)。这个拓扑将会被提交到集群,由集群中的主控节点(master node)分发代码,将任务分配给工作节点(worker node)执行。一个拓扑中包括 spout 和 bolt 两种角色,其中 spout 发送消息,负责将数据流以 tuple(元组)的形式发送出去;而 bolt 则负责转换这些数据流,在 bolt 中可以完成计算、过滤等操作,bolt 自身也可以随机将数据发送给其他 bolt。由 spout 发射出的 tuple 是不可变数组,对应着固定的键值对。
② Spark Streaming 是核心Spark API 的扩展,不会像 Storm那样一次一个地处理数据流,而是在处理前按时间间隔预先将其切分为一段一段的批处理作业。Spark 针对持续性数据流的抽象(DStream,DiscretizedStream),一个 DStream 是一个微批处理(micro-batching)的RDD(弹性分布式数据集);而 RDD 则是一种分布式数据集,能够以两种方式并行运作,分别是任意函数和滑动窗口数据的转换。相对于 Flink,Spark将内存完全交给应用层,更容易出现 OOM(Out Of Memory)内存溢出。
③ Apache Flink 是一个针对流数据和批数据的分布式处理引擎。主要是由 Java 代码实现。对 Flink 而言,其所要处理的主要场景就是流数据,批数据知识流数据的一个极限特例而已。再换句话说,Flink 会把所有任务当成流来处理,这也是其最大的特点。Flink 可以支持本地的快速迭代,以及一些环形的迭代任务。并且 Flink 可以定制化内存管理。相对于 Spark ,Flink 并没有将内存完全交给应用层。就框架本身与应用场景来说,Flink 更相似于 Storm。
Local(本地模式):运行在一台计算机上的模式,通常就是用于在本机上练手和测试。
Standalone模式:构建一个由Master+Slave 构成的 Spark 集群,Spark 运行在集群中。
yarn 模式:Spark客户端直接连接Yarn,不需要额外构建Spark集群。
mesos 模式:Spark 客户端直接连接 Mesos;不需要额外构建 Spark 集群。 国内应用比较少,更多的是运用 yarn 调度。
运行快:基于内存,Spark 实现了高效的DAG执行引擎,可以通过基于内存来高效处理数据流,计算的中间结果是存在于内存中的。
易于使用:Spark支持Java、Python和Scala的API,还支持超过80种高级算法,使用户可以快速构建不同的应用。而Spark支持交互式的Python和Scala的Shell,可以非常方便地在这些Shell中使用Spark集群来验证解决问题的方法。
通用:Spark提供了统一的解决方案,可用于批处理,交互式查询,实时流处理,机器学习和图计算,都可以在同一个应用中无缝使用
兼容性:Spark可以很方便的与其他的开源产品进行融合。
不能,仅仅是MapReduce的替代方案,不能进行分布式数据的存储,即不能替代Hadoop中的HDFS。
Spark 只是分布式计算平台,而Hadoop已经是分布式计算、存储、管理的生态系统。
在Hadoop架构中:HDFS(存储系统)、MapReduce(计算框架)、Hive(查询框架)、HBase(实时、准实时)、YARN(资源调度)
Spark(是一种新型大数据计算框架,可基于Hadoop上存储的大数据进行计算。)
Spark只是一个专门用来对那些分布式存储的大数据进行处理的工具,它并不会进行分布式数据的存储。
cache和persist 内存中缓存 ,内部的优化机制。当Rdd 重复被使用了,不需要在重新计算,直接从内存中获取使用
RDD通过 persist或cache 方法可以将前面的计算结果缓存,默认情况下 persist( ) 会把数据以序列化的形式缓存在 JVM 的堆(Heap)空间中。
缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于 RDD 的一系列转换,丢失的数据会被重算,由于 RDD 的各个 Partition 是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部 Partition。
client:
Driver 程序运行在客户端,适用于交互、调试,希望立即看到 app 的输出
cluster:
Driver程序运行在由 RM(ResourceManager)启动的 AP(APPMaster)适用于生产环境。
Lineage:血缘关系,根据血缘关系重新计算进行容错。
RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建 RDD 的一系列 Lineage(血缘)记录下来,以便恢复丢失的分区。RDD 的 Lineage 会记录 RDD 的元数据信息和转换行为,当该 RDD 的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
CheckPoint:设置检查点,一般都是文件系统,磁盘IO
- key本身分布不均衡
- 计算方式有误
- 过多的数据在一个 task 里面
- shuffle 并行度不够
20%执行编写的代码,默认占Executor总内存的20%
20%接收上一个stage的结果
60%进行数据持久化所占用内存大小
能快速处理多种场景下的大数据问题,能高效挖掘大数据中的价值,从而为业务发展提供决策支持
RDD(Reslilent Distributed Dataset,弹性分布式数据集),是Spark中最基本的数据抽象。代码中是一个抽象类,它代表一个不可变、可分区、里面的元素可并行计算的集合。
特点: RDD 表示只读的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必须的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。
① 分区
RDD逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个 compute 函数得到每个分区的数据。如果 RDD 是通过已有的文件系统构建,则 compute 函数是读取指定文件系统中的数据,如果RDD是通过其他 RDD 转换而来,则 compute 函数是执行转换逻辑将其他 RDD 的数据进行转换。
② 只读
RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD。
③ 依赖
④ 缓存
⑤ CheckPoint
① 一组分区(Partition),即数据集的基本组成单位。
② 一个计算每个分区的函数。
③ RDD之间的依赖关系。
④ 一个Partitioner,即RDD的分片函数。
⑤ 一个列表,存储存取每个Partition的优先位置。
spark on hive:hive作为数据源,spark计算
hive on spark:spark 作为hive 底层的计算引擎
① 是执行开发程序中的main方法的进程。
② 负责开发人员编写的用来创建sc,创建rdd,以及进行rdd的转化操作和行动操作代码的执行。
③ 把用户程序转为作业
④ 跟踪Executor的运行状况
⑤ 为执行器节点调度任务
⑥ UI展示应用运行状况
① 是一个工作进程,负责在 Spark 作业中运行任务,任务间相互独立。
② Spark 应用启动时,Executor 节点被同时启动,并且始终伴随着整个 Spark 应用的生命周期而存在。如果Executor节点发生了故障或崩溃,Spark应用也可以继续执行,会将出错节点上的任务调度到其他 Executor 节点上继续运行。
③ 负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程。
④ 通过自身的块管理器(Block Manager)为用户程序中要求缓存的RDD提供内存式存储。RDD是直接存在 Executor 进程内的,因此任务可以在运行时充分利用缓存数据加速运算。
一个RDD可以有多个分片,一个分片对应一个task,分片的个数决定并行度
并行度并不是越高越好,还要考虑资源的情况
转换算子(transformation):
用来将RDD进行转换,构建RDD的血缘关系,由一个 RDD 生成一个新的RDD 不会被立即执行,记录的都是一系列的操作
动作算子(action):
用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中。(立即执行,返回都是结果。)
宽依赖:
(父RDD与子RDD,Partition之间是一对多,多对多的关系)
ReduceByKey、Join
宽依赖指的是 多个子RDD的Partition 会依赖同一个 父RDD的Partition,会引起Shuffle。
窄依赖:
(父RDD与子Rdd,Partition之间是一对一的关系,或者多对一的关系)
Map、Filter、Union
窄依赖指的是每一个 父RDD 的Partition最多被子RDD的一个Partition使用。
① 从集合中创建
val rdd = sc.parallelize(Array(1,2,3,5,3,4))
,val rdd1 = sc.makeRDD(Array(1,2,3,5,3,4))
② 从外部存储系统的数据集创建RDD:val rdd2=sc.textFile("hdfs://jh/RELEASE")
包括本地的文件系统,还有所有 Hadoop 支持的数据集,比如:HDFS、Cassandra、HBase等
③ 从其他 RDD 创建
根据原RDD创建新的RDD
value类型(13)
map、mapPartitions、mapPartitionsWithIndex、flatMap、glom、groupBy、filter
sample、distinct、coalesce、repartition、sortBy、pipe、
双Value类型交互(5)
union、subtract、intersection、cartesian、zip、
key-value类型(10)
partitionBy、groupByKey、reduceByKey、aggregateByKey、foldByKey、combineByKey、
sortByKey、mapValues、join、cogroup、
action类型(13)
reduce、collect、count、first、take、takeOrdered、aggregate、fold、saveAsTextFile、saveAsSequenceFile、saveAsObjectFile、countByKey、foreach、
map( ):每次处理一条数据。
mapPartition( ):每次处理一个分区的数据,这个分区的数据处理完后,原 RDD中分区的数据才能释放,可能导致OOM(Out Of Memory,内存溢出)当内存空间较大的时候建议使用 mapPartition( ),以提高处理效率。
coalesce 重新分区,可以选择是否进行 Shuffle 过程。由参数 shuffle:Boolean=false/true 决定。
repartition 实际上是调用的 coalesce,默认是进行 shuffle 的。
reduceByKey:按照 key 进行聚合,在 Shuffle 之前有 combine(预聚合)操作,返回结果是 RDD[k,v]。
groupByKey:按照 key 进行分组,直接进行 shuffle。
union算子是将两个rdd的元素求并集
intersection 算子是将求两个 rdd 的元素的交集。
宽依赖算子:
① 所有byKey算子: partitionBy、groupByKey、reduceByKey、aggregateByKey、foldByKey、combineByKey、 sortByKey
② repartition、cartesian算子
③ 部分 join 算子
窄依赖算子:
map、flatMap、filter
广播变量 + map + filter
当遇到一个 Shuffle类的算子,就会划分一个stage
遇到一个 action 算子,就会划分一个job
尽量打散(默认的资源调度算法):
> 尽量让需要的资源平均的在不同机器上启动。
尽量集中:
> 尽量在某一台或者某几台机器上启动。
Spark通过两种方式来创建序列化器。
Java序列化
> 在默认情况下,Spark采用Java的ObjectOutputStream序列化一个对象。该方式适用于所有实现了java.io.Serializable的类。通过继承 java.io.Externalizable,你能进一步控制序列化的性能。Java序列化非常灵活,但是速度较慢,在某些情况下序列化的结果也比较大。
>
> 使用:
>
> 实现 Serializable 接口:`implements Serializable`
Kryo序列化
> Spark 也能使用 Kryo(版本2)序列化对象。Kryo不但速度极快,而且产生的结果更加紧凑(通常能提高10倍)。Kryo的缺点是不支持所有类型,为了更好的性能,你需要提前注册程序中所使用的类。
>
> 使用:
>
> 在创建 SparkContext 之前,通过调用 `System.setProperty("spark.serializer","org.apache.spark.serializer.KryoSerializer")`,将序列化方式切换成 Kryo。
>
> 但是 Kryo 需要用户进行注册。`conf.registerKryoClasses(Array(classof[MyClass],classof[MyClass2]))`
>
> 如果对象非常大,你还需要增加属性 spark.kryoserializer.buffer.mb 的值。该属性的默认值是32,但是该属性需要足够大以便能够容纳需要序列化的最大对象。
累加器是一种共享变量,提供了将工作节点中的值聚合到驱动器程序中的简单语法。累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数。全局唯一只增不减
累加器用来对信息进行聚合,通常在向 Spark 传递函数时,比如使用 map( ) 函数或者用filter( ) 传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。如果我们想实现所有分片处理时更新共享变量的功能,那么累加器可以实现我们想要的效果。
> 只有在 运行动作操作(action算子)之后累加器中才会有计数值,因为行动操作之前的转换操作(transformation算子)是惰性的。
广播变量用来高效分发较大的对象。向所有工作节点(worker node)发送一个较大的只读值,以供一个或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,甚至是机器学习算法中的一个很大的特征向量,广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark 会为每个任务分别发送。
使用广播变量的过程如下:
① 通过对一个类型T的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T] 对象。任何可序列化的类型都可以这么实现。
② 通过 value 属性访问该对象的值(在 Java 中为 value( ) 方法)。
③ 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。
① 每个节点可以起一个或多个 Executor。
② 每个Executor由若干 core组成,每个 Executor 的每个 core 一次只能执行一个 Task。
③ 每个Task执行的结果就是生成了目标RDD的一个partition。
注意:这里的core是虚拟的core而不是机器的物理CPU核,可以理解为就是 Executor的一个工作线程。而 Task被执行的并发度 = Executor数目 * 每个Executor 核数。
- --supervise 在集群模式下,driver失败了自动重启
- 对 driver 的元数据做 CK(CheckPoint)
① 使用 聚合日志方式(推荐,比较常用)将散落在集群中各个机器上的日志,最后都给聚合起来。
打开日志聚合的选项:`yarn.log-aggregation-enable` Container的日志会拷贝到HDFS上,并从机器上删除。对于这种情况,可以使用 `yarn logs -applicationId ` 命令,来查看日志。
yarn logs 命令,会打印出 application 对应的所有 Container 的日志出来,当然,因为日志是在 HDFS 上的,我们自然也可以通过 HDFS 的命令行来直接从 HDFS 中查看日志。
日志在HDFS中的目录,可以通过查看 yarn.nodemanager.remote-app-log-dir 和 yarn.nodemanager.remote-app-log-dir-suffix 属性来获知。
② WebUI
日志也可以通过 Spark web ui 来查看 executor 的输出日志
但是此时需要启动 History Server,需要让 spark History server 和 MapReduce History server 运行着,并且在 yarn-site.xml 文件中,配置 yarn.log.server.url 属性
spark History server web ui 中的 log url,会将你重定向到 mapreduce history server 上,去查看日志。
③ 分散查看(通常不推荐)
如果没有打开聚合日志选项,那么日志默认就是散落在各个机器上的本地磁盘目录中的,在YARN_APP_LOGS_DIR 目录下,根据hadoop版本的不同,通常在 /tmp/logs 目录下,或者 $HADOOP_HOME/logs/userlogs 目录下,如果你要查看某个 Container 的日志,那么就得登录到那台机器上去,然后到指定的目录下去,找到那个日志文件,然后查看。
开发优化
① 避免创建重复的RDD
`.persist()`和 `cache()`
② 尽可能使用同一个 RDD
③ 对多次使用的RDD进行持久化
`new StorageLevel()`
④ 尽量避免使用 shuffle 类算子
减少分区:broadcast + map + filter 代替 join
大表join小表,将小表的数据广播到 executor中,使用 map+filter 完成join的功能
⑤ 使用map-side 预聚合的shuffle操作
⑥ 使用高性能的算子
使用 reduceByKey/aggregateByKey代替groupByKey
⑦ 广播大变量
⑧ 使用 Kryo 优化序列化性能
⑨ 优化数据结构
资源调优
① num-executors
② executor-memory,1/3-1/2
③ executor-cores,建议设置为2~4个,不要超过总cores的1/3~1/2
④ task的总数一般是cpu core 的2-3倍。
39.RDD Partition 的个数由什么决定的?
① 默认的,两个
② 指定的,numPartitions
③ 从HDFS读取数据,由块的个数决定
④ 从 Kafka 读取数据,由 topic 的 Partition 个数决定的
> new StorageLevel(false, false, false, false)
>
> 第一个参数,是否持久化到磁盘
>
> 第二个参数,是否持久化到内存
>
> 第三个参数,是否持久化到JVM的heap中
>
> 第四个参数,是否不使用序列化
>
> ① 不持久化RDD数据
>
> val NONE = new StorageLevel(false, false, false, false)
>
> ② 将RDD数据持久化到磁盘
>
> val DISK_ONLY = new StorageLevel(true, false, false, false)
>
> ③ 在磁盘上保存两个副本
>
> val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
>
> ④ 将RDD的数据持久化到内存
>
> val MEMORY_ONLY = new StorageLevel(false, true, false, true)
>
> ⑤ 在内存中持久化两个副本
>
> val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
>
> ⑥ 将序列化后的RDD数据持久化到内存
>
> val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
>
> ⑦ 将序列化后的RDD数据在内存中保存两个副本
>
> val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
>
> ⑧ 将RDD的数据持久化到内存中,如果内存放不下,就溢写到磁盘上
>
> val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
>
> ⑨ 将RDD的数据在内存中持久化两个副本,如果放不下,就溢写到磁盘上
>
> val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
>
> ⑩ 将序列化后的RDD数据持久化到内存中,如果放不下,就溢写到磁盘上
>
> val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
>
> ⑪ 将序列化后的RDD数据在内存中持久化两个副本,如果存不下,就溢写到磁盘上
>
> val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
>
> ⑫ 把序列化后的RDD数据存储在内存中,放不下,就存放咋
>
> val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
–master:master的地址,提交任务到哪里执行
–deploy-mode:在本地(Client)启动driver或在 cluster 上启动,默认是 client
–class:应用程序的主类,仅针对 java 或 scala 应用
–name:应用程序的名称
–jars:用逗号分隔的本地 jar 包,设置后,这些 jar 将包含在 driver 和 executor 的classpath下。
–packages:包含在driver和 executor 的 classpath 中的jar的maven 坐标。
–execlude-packages:为了避免冲突而指定不包含的 package
–repositories:远程 repository
–conf PROP=VALUE:指定 Spark 配置属性的值。
–properties-file:加载的配置文件
–driver-memory:driver内存,默认 1G
–driver-java-options:传给 driver 的额外的 Java 选项
–driver-library-path:传给 driver 的额外的库路径
–driver-class-path:传给 driver 的额外的类路径
–driver-cores:driver 的核数,默认是1.在yarn或者 standalone 下使用
–executor-memory:每个 executor 的内存,默认是1G
–total-executor-cores:所有 executor 总共的核数,仅仅在 mesos 或者 stangalone 下使用。
–num-executors:启动的 executor 数量。默认为2。在 yarn 下使用
–executor-core:每个executor 的核数。在 yarn 或者 standalone 下使用。
代码
脚本
配置文件 , 优先级依次降低的
out of memory 内存溢出
在驱动程序中,以数组的形式返回数据集的所有元素。
Directed Acyclic Graph,有向无环图
原始的 RDD 通过一系列的转换就形成了 DAG,根据 RDD 之间的依赖关系的不同将DAG划分成不同的 Stage,对于窄依赖,Partition 的转换处理在 Stage 中完成计算。
拓展:
在 图论 中,如果一个有向图无法从某个定点出发经过若干条边回到该点,则这个图是一个 有向无环图(DAG图)。DAG与数组、排列、区块链一样,是一种数据结构。
对于窄依赖,partition的转换处理在stage中完成计算,不划分(将窄依赖尽量放在在同一个stage中,可以实现流水线计算)。
对于宽依赖,由于有shuffle的存在,只能在父RDD处理完成后,才能开始接下来的计算,也就是说需要要划分stage(出现宽依赖即拆分)。
stage的切割规则:从后向前,遇到宽依赖就切割stage。