目录
前言
一、Spark 基本定义
Spark 相对于 MapReduce 的优势
二、Spark 的组成
三、Spark 运作时架构
四、任务层定义
五、RDD间依赖关系:宽窄依赖(shuffle)
pom 文件
总结
前言
学会用一个技术只是第一步,最重要的是要追问自己:
- 这个技术解决了哪些痛点?
- 别的技术为什么不能解决?
- 这个技术用怎样的方法解决问题?
- 采用这个技术真的是最好的方法吗?
- 如果不用这个技术,你会怎样独立解决这类问题?
一、Spark 基本定义
spark解决了什么问题? 处理大量的数据和计算时候。需要一台超级电脑,来做计算和保存。spark/map reduce ,就是提供了这样的一套系统,可以在多台机器上并行做计算,(每台机子做的工作都相同,相当于工作量都减轻为原来的1/n)多部电脑同时读写。单机处理问题,首先容量不够,同时计算能力不足。所以想的方法是将任务拆解给不同的机子,让他们同时做相同的事情,之后再将每个机子的结果进行整和。所以map reduce/spark 主要解决的就是数据计算问题
spark 是什么 |
|
spark 解决了什么问题 | 当数据量太大时,spark解决了单机无法处理大量数据的计算问题 |
spark 如何解决 | 分而治之 将数据和计算任务分解,将计算逻辑分发到各个数据模块所在机子上,每个机子的任务相同,计算数据中的一部分,最后将各个机子的结果汇总,得到最终的结果 |
如果没有spark, 那么应该如何解决 | 场景:统计日志中出现的所有url及其出现的次数 单机版本解决方案:建立map ,key: url, value: 该URL出现的次数
问题:日志太大,单机内存不足 解决方案:将数据存在hdfs上
问题:完整日志被切分为多个子模块,存在hdfs中不同的机器上 解决方案:
map reduce 是把以上的问题的解决方案全部封装好,并提供各种API, 程序员只需要会写业务逻辑就好。 |
| |
| |
Spark 相对于 MapReduce 的优势
高效性 |
|
易用性 | MapReduce 只支持Map 和Reduce 两种编程算子,Spark提供了超过80种不同的transformation和action算子。实现相同的功能,spark的代码量极大缩小 |
通用性 | Spark提供了统一的解决方案。Spark可以用于批处理、交互式查询(Spark SQL)、实时流处理(Spark Streaming)、机器学习(Spark MLlib)和图计算(GraphX)。 这些不同类型的处理都可以在同一个应用中无缝使用。这对于企业应用来说,就可使用一个平台来进行不同的工程实现,减少了人力开发和平台部署成本。 |
兼容性 | Spark能够跟很多开源工程兼容使用。如Spark可以使用Hadoop的YARN和Apache Mesos作为它的资源管理和调度器,并且Spark可以读取多种数据源,如HDFS、HBase、MySQL等。 |
二、Spark 的组成
spark 组成部分 | 作用 |
GraphX 图计算 | 是用来操作图(比如社交网络的朋友关系图)的程序库,可以进行并行的图计算。 与Spark Streaming 和Spark SQL 类似,GraphX 也扩展了Spark 的RDD API,能用来创建一个顶点和边都包含任意属性的有向图。GraphX 还支持针对图的各种操作 |
MLlib 机器学习 | 提供了很多种机器学习算法,包括分类、回归、聚类、协同过滤等,还提供了模型评估、数据导入等额外的支持功能。如果我们要调用一个randomforest函数,就需要调用MLlib接口 |
Spark Core | 实现了Spark 的基本功能,包含任务调度、内存管理、错误恢复、与存储系统交互等模块。 Spark Core 提供了创建和操作这些集合的多个API (比如filter, map, reduce等) |
Spark SQL 结构化数据 | 是Spark 用来操作结构化数据的程序包。通过Spark SQL, 我们可以使用SQL或者Apache Hive 版本的SQL 方言(HQL)来查询数据。Spark SQL 支持多种数据源,比如Hive 表、Parquet 以及JSON 等。 也就是说多亏了Spark SQL, 我们才可以定义sql =" xxx" sc.sql(sql) 关联到hive表中并且从中读取数据 |
Spark Streaming 实时计算 | Spark 提供的对实时数据进行流式计算的组件。比如生产环境中的网页 服务器日志,或是网络服务中用户提交的状态更新组成的消息队列,都是数据流。Spark Streaming 提供了用来操作数据流的API,并且与Spark Core 中的RDD API 高度对应。 |
集群管理器 | Spark 设计为可以高效地在一个计算节点到数千个计算节点之间伸缩计算。为了实现这样的要求,同时获得最大灵活性,Spark 支持在各种集群管理器(cluster manager)上运行,包括Hadoop YARN、Apache Mesos,以及Spark 自带的一个简易调度器,叫作独立调度器。 如果已经有了一个装有Hadoop YARN 或Mesos的集群,通过Spark 对这些集群管理器的支持,你的应用也同样能运行在这些集群上 |
三、Spark 运作时架构
每个spark 应用都由一个驱动器(driver program)来发起集群上的各种并行操作
核心概念 | 作用 |
worker 集群中的机器 |
|
RDD弹性分布式数据集 | 弹性分布式数据集,它是记录的只读分区集合,是Spark的基本数据结构。 RDD代表一个不可变、可分区、里面的元素可并行计算的集合。 RDD的操作有两种类型,即Transformation操作和Action操作。转换操作是从已经存在的RDD创建一个新的RDD,而行动操作是在RDD上进行计算后返回结果到 Driver。 Transformation操作都具有 Lazy 特性,即 Spark 不会立刻进行实际的计算,只会记录执行的轨迹,只有触发Action操作的时候,它才会根据 DAG 图真正执行。 对RDD操作,其实是对RDD的每个分区上data进行操作,task调度executor上data执行相关的计算逻辑,进而对数据进行操作。 |
master (spark 自带的集群管理器)cluster manager (Yarn/Mesos 开源集群管理器) |
|
executor执行器 |
|
driver program驱动器程序 |
|
DAG有向无环图 | 反映RDD之间的依赖关系, 由driver创建 一个action对应一个job, 对应一个DAG |
- 集群准备 先起master再起worker,worker和master进行通讯,向master注册自己,把自己的内存和核数(机器的默认配置)都汇报给maste,这样master就知道哪些worker可用,而且知道每个worker的内存和核数,方便之后的资源调度。master给worker返回信息以后,worker就会定期给master发心跳,为了证明自己还活着。
- Spark-submit/Driver是客户端。客户端就是一个应用程序,支持和服务器的协议,实现和相应服务器间的交互比如微信,我们要使用微信,接受微信上的数据,那就需要先登陆客户端(微信的客户端可以是手机APP,电脑软件,还有电脑的网页)只有登陆上客户端以后,才能访问腾讯微信的服务器,接受聊天数据啊发聊天信息啊啥的。然后各个服务程序的客户端和服务器是一一匹配的,比如淘宝APP(淘宝客户端)就不能和微信的服务器相互交互。总而言之,要使用某种服务,要使用相应的服务器,必须要先登陆相应的客户端。
用户
- 编写程序,定义一个class/object对象,其中编写main 函数,main() 中创建SparkContext代表对Spark集群的链接,并定义对RDD的各种创建转化和修改等操作。
- 打包代码和依赖:
- 如果只有一两个库的依赖且这些库本身不依赖于其他库时,通过spark-submit 的--jars 标记提交独立的JAR 包依赖
- 若依赖很多库,使用构建工具Maven,在pom.xml中添加所有需要的库(必不可少的是引入spark.core) 然后将整个程序成单个大JAR 包,包含应用的所有的传递依赖。
- 编写spark-submit 脚本,其中定义要连接的集群管理器Yarn,上传Jar包所在路径,并控制应用所使用的资源数量。
- 然后mater看集群里面是否有符合要求的woker,和worker通信,申请资源(申请可用的worker),在之前worker向master注册的时候,matser已经掌握了worker的全部计算资源信息,此时就可以知道哪些worker的内存和核数满足该driver的请求,并且任务启动后,所有worker的资源的使用情况,master都了如指掌。收到driver的请求后,master就可决定在哪些worker上起executor. 分配的准则:让任务运行在尽量多的机器上,
- master将分配的参数传给worker,worker启动executor进程 (注意起的资源的参数),woker起executor,
- executor主动链接driver,driver的位置信息是通过master-worker-executor传给executor,然后实现真正的计算逻辑(比如那个RDD操作逻辑图)然后driver端生成具体的task,然后把task通过网络发给executor执行,一个executor可以执行多个task。executor 执行完毕后,将数据写入hdfs或者返回dirver
- driver 收到executor任务完成信号后,向cluster manager发送注销信号
- cluster manager向worker发送释放资源信号
- worker上对应的executor停止运行
四、任务层定义
按照任务层面划分:application -> job -> stage -> task
概念 | 定义 | 划分依据 |
Application | 一个spark session 对应一个application | |
Job | 一个Job包含多个RDD及作用于相应RDD上的各种操作 | 一个action 对应一个job |
Stage | 是Job的基本调度单位,一个Job会分为多组任务,每组任务被称为“Stage”。 | job中含有需要shuffle的操作,就会把job分解成两个stage。 job遇到宽依赖就切割stage |
Task | 运行在Executor上的工作单元,是Executor中的一个线程。 | task 个数由rdd的分区数决定。同一个stage中的所有task功能相同,只是运行在不同的分区上。 并行化的具体实现 |
阶段 | 具体动作 | 执行位置 |
构建DAG | 描述了RDD的转换过程,定义了如何对数据进行操作 一个action对应一个job,对应一个DAG DAG的开始:通过Spark Context 创建RDD DAG的结束:一旦触发一个action,就形成一个完整的DAG | driver端 |
切分Stage | DAGScheduler遇到shuffle就切分stage, stage中的所有的task的业务逻辑都相同,都装到一个set中:TaskSet中 将stage中生成的task以TaskSet的形式传给TaskScheduler | driver端 |
调度Task | 将TaskSet中的task通过网络调度到executor中 | driver端 |
执行Task | executor接受Task并将task反序列化,丢到线程池中执行 | executor端 |
五、RDD间依赖关系:宽窄依赖(shuffle)
宽窄依赖算子: transforamtion.
宽依赖:需要shuffle操作的transformation算子
窄依赖:不需要shuffle操作的transformation算子
划分stage的依据就是RDD之间的宽窄依赖。遇到宽依赖就划分stage
为啥要切分stage?
因为业务逻辑中,需要将多台机器上具有相同属性的数据,都聚合到一台机器上(通过shuffle实现)
如果有shuffle,意味着后面阶段的数据依赖前面前一阶段的结果。
shuffle的含义:将数据打散,父RDD中一个分区的数据可能会分给子RDD中的多个分区
shuffle会有网络传输,但是网络传输不代表就是shuffle
依赖关系 | 概念 | 算子示例(只限transforamtaion) |
窄依赖(独生子女) | 父RDD的一个分区去到了子RDD的一个分区,不会有shuffle产生 | map,filter,union |
宽依赖 | 父RDD的一个分区的数据去到了子RDD的不同分区里面。会有shuffle产生 | groupByKey,reduceByKey,sortByKey |
六、代码实现
定义Scala启动类
Driver program包含应用的main函数,并且定义了集群上的分布式数据集RDD, 还对这些分布式数据集RDD应用了相关操作(transform 和 action)。
Driver program 通过一个 SparkContext 对象来访问Spark, SparkContext代表对计算集群的一个连接。是使用spark功能的入口点。主要用于创建和操作RDD,进行数据计算。一旦有了SparkContext,就可以用它来创建RDD,并执行操作。
// 定义scala的object, 也是咱们的启动类,会作为参数传到spark submit中
Object Test{
//创建 main 函数
Def main(args:Array[String]){
/*----------------------------创建一个SparkSession对象来访问Spark,通过对于spark-core工件的maven依赖(参考pom 文件),连接到计算集群----------------------------*/
//这个SparkSession 对象就代表着对集群的依赖,之后对于数据的任何操作,都是通过spark这个变量来操作
val spark = SparkSession.builder()
//集群URL,告诉spark如何连接到集群上,此处我们使用的是local,这个特殊值可以让Spark运行在单机单线程上而无需连接到集群,也就是所有的运算都是在本地跑的,不调动集市资源
.master("local[3]")
//当连接到一个集群时,这个值可以帮助我们在集群管理器的用户界面上找到我的应用
.appName("potential_label_set_score_2")
.getOrCreate()
/*----------------------------定义RDD的操作----------------------------*/
//通过spark变量(SparkSession 对象,代表着对集群的依赖)来创建RDD
val tagValue=spark.read.option("header","true").option("inferSchema","true").format("csv").load("D:\\GaoQian2\\经海路高潜重构\\测试\\tagValue.csv")
//对RDD执行各种操作,ex:count()
tagValue.count()
//对RDD执行筛选操作,筛选出含有"python"的句子,并定义内联函数
Val pythonLines = tagValue.filter(line => line.contains("Python"))
pom 文件
为了实现连接Spark,要给整个Scala应用添加一个对于saprk-core工件的Maven 依赖。(就是pom.xml中定义的各种东西,通过maven这个包管理工具,可以连接到公共仓库中的程序库,比如在pom.xml中加入下列的依赖,再在main脚本中初始化SparkContext对象,就可以实现对spark集群的连接,
Spark Core 实现了Spark 的基本功能,包含任务调度、内存管理、错误恢复、与存储系统交互等模块。Spark Core 提供了创建和操作这些集合的多个API (比如filter, map, reduce等)
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>${spark.version}</version>
<scope>provided</scope>
</dependency>
由于Spark Core 包已经在各个工作节点的classpath 中了,所以我们把对Spark Core 的依赖标记为provided,这样当我们稍后使用assembly 方式打包应用时,就不会把spark-core包也打包到assembly包中。
如果是在本地运行,那么就可以把provide注释掉,因为本地运行不需要打包,而之前provide是为了避免把spark-core打包进去
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>${spark.version}</version>
<!--<scope>provided</scope>-->
</dependency>
spark submit 脚本
#!/usr/bin/env bash
#submit表示提交sparkapplication, 它会起一个程序(就是java/python/scala中的main函数)红色的参数都是传给spark-submit的,告诉它master在哪里,要运行啥程序
spark-submit \
# 要执行的类名
--class PotentialLabelSetScore2 \
# 集群资源调度器为yarn
--master yarn \
# 如果是client模式,那么任务执行的打印日志都会收回到driver端,比如我们在堡垒机上起任务,那么在堡垒机上就能看到日志输出,cluster模式,就要去saprk UI上看输出
--deploy-mode client \
# 每个executor使用的12g内存
--executor-memory 12g \
# 每个executor用多少核
--executor-cores 3 \
# 使用的executor个数
--conf spark.dynamicAllocation.maxExecutors=1000 \
--conf spark.sql.shuffle.partitions=3000 \
--conf spark.network.timeout=1000s \
--conf spark.dynamicAllocation.enabled=true \
--conf spark.hadoop.hive.lzo.use.index=true \
--conf spark.sql.orc.impl=hive \
--conf spark.sql.orc.enableVectorizedReader=true \
--conf spark.sql.hive.convertMetastoreOrc=true \
--conf spark.yarn.queue=root.bdp_jmart_scr_union.bdp_jmart_dwm_union.bdp_jmart_dwm_formal \
--jars AutoFeatureEngineering-1.0-SNAPSHOT.jar\
FashionUserSegmentation-1.0-SNAPSHOT.jar 加在jar包后面的参数才是给程序的 > predict2.log #程序中打印出来的部分都写到predict2.log这个中
总结
本文对spark的基本原理进行了整理。内容较多,框架性还是不足够。