基于内存的,用于大规模数据处理(离线计算、实时计算、快速查询(交互式查询))的统一分析引擎。
快:
Spark计算速度是MapReduce计算速度的10-100倍
易用:(算法多)
MR支持1种计算模型,Spsark支持更多的计算模型。
通用:
Spark 能够进行离线计算、交互式查询(快速查询)、实时计算、机器学习、图计算等
兼容性:
Spark支持大数据中的Yarn调度,支持mesos。可以处理hadoop计算的数据。
1、2009年诞生于美国加州大学伯克利分校AMP 实验室
2、2014年,Spark 成为 Apache 的顶级项目
原因1:优秀的数据模型和计算抽象
Spark将多个RDD之间的中间数据写入内存,MR将中间数据写入硬盘,内存比硬盘速度快
原因2:完善的生态圈
Spark 能够进行离线计算、交互式查询(快速查询)、实时计算、机器学习、图计算等
Spark Core:Spark基本功能
Spark SQL : 操作结构化数据
Spark Streaming: 对实时数据进行流式计算
Spark MLlib: 机器学习
GraphX:用于图计算
|
Hadoop |
Spark |
类型 |
基础平台, 包含计算, 存储, 调度 |
分布式计算工具 |
场景 |
大规模数据集上的批处理 |
迭代计算, 交互式计算, 流计算 |
价格 |
对机器要求低, 便宜 |
对内存有要求, 相对较贵 |
编程范式 |
Map+Reduce, API 较为底层, 算法适应性差 |
RDD组成DAG有向无环图, API 较为顶层, 方便使用 |
数据存储结构 |
MapReduce中间计算结果存在HDFS磁盘上, 延迟大 |
RDD中间运算结果存在内存中 , 延迟小 |
运行方式 |
Task以进程方式维护, 任务启动慢 |
Task以线程方式维护, 任务启动快 |
1.local本地模式(单机)--开发测试使用
2.standalone独立集群模式--开发测试使用
3.standalone-HA高可用模式--生产环境使用
4.on yarn集群模式--生产环境使用
5.on mesos集群模式--国内使用较少
6.on cloud集群模式--中小公司未来会更多的使用云服务
1、local本地模式安装部署
第一步:上传并解压
第二步:开箱即用(不需要修改配置)
启动spark
./spark-shell
没有参数表示 等于 local[*]
./spark-shell --master local[*]
Local表示在本机运行。[*]表示可用的所有资源
./spark-shell --master local[n]
Local表示在本机运行。[n]表示使用n个线程
1、读取本地数据计算WordCount
val textFile = sc.textFile("file:///opt/tt.txt")
val counts = textFile.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
2、读取hdfs集群数据计算WordCount
val textFile = sc.textFile("hdfs://node01:8020/tt.txt")
val counts = textFile.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
counts.saveAsTextFile("hdfs://node01:8020/ttt")
第一步:软件上传并解压
第二步:
修改配置文件
export JAVA_HOME=${JAVA_HOME}
export SPARK_MASTER_HOST=node01
export SPARK_MASTER_PORT=7077
修改slaves
node02
node03
第三步:软件包的分发
scp -r spark-2.2.0-bin-2.6.0-cdh5.14.0 node02:$PWD
scp -r spark-2.2.0-bin-2.6.0-cdh5.14.0 node03:$PWD
第四步:启动集群
./start-all.sh –master spark://node01:7077
第五步:验证
sc.textFile("hdfs://node01:8020/tt.txt")
.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
.saveAsTextFile("hdfs://node01:8020/tttt")
HA解决master的单点问题
第一步:上传解压
第二步:修改配置文件
export JAVA_HOME=${JAVA_HOME}
export SPARK_MASTER_PORT=7077
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zooke
eper.url=node01:2181,node02:2181,node03:2181 -Dspark.deploy.zookeeper.dir=/spark"
"../conf/spark-env.sh" 67L, 3966C written
第三步:同步更新的配置到其他节点
scp spark-env.sh node02:/$PWD
scp spark-env.sh node03:/$PWD
第四步:启动zookeeper
第五步:启动spark
启动集群 ./start-all.sh
启动第二个Master(node02/node03)
第六步:验证HA
http://node01:8080/
http://node02:8080/
前期准备
2、安装部署单机版本的spark
第一步:安装包的上传并解压
第二步:修改配置文件
export JAVA_HOME=${JAVA_HOME}
export SPARK_MASTER_HOST=node01
export SPARK_MASTER_PORT=7077
export HADOOP_CONF_DIR=/export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop
第三步测试
使用spark-submits
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 2 \
--queue default \
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/jars/spark-examples_2.11-2.2.0.jar \
10
参数的说明
--master local [n]/[*]/spark://HOST:PORT/YARN 指定 Master 的地址
--name "appName" 指定程序运行的名称
--class 程序的main方法所在的类
--jars xx.jar 程序额外使用的 jar 包
--driver-memory 512m Driver运行所需要的内存, 默认1g
--executor-memory 2g 指定每个 executor 可用内存为 2g, 默认1g
--executor-cores 1 指定每一个 executor 可用的核数
--total-executor-cores 2 指定整个集群运行任务使用的 cup 核数为 2 个
--queue default 指定任务的对列
--deploy-mode 指定运行模式(client/cluster)
将代码导出成为jar文件,将jar文件添加到集群,使用sparkSubmit提交任务到集群。
弹性分布式数据集(数据存储在内存)
弹性的,RDD中的数据可以保存在内存中或者磁盘里面
分布式存储的,可以用于分布式计算
集合,可以存放很多元素
一个不可变、可分区、里面的元素可并行计算的集合
1、数据集的基本组成单位是一组分片(Partition)或一个分区(Partition)列表
每个分片都会被一个计算任务处理,分片数决定并行度。
用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值(默认值2)。
2、一个函数会被作用在每一个分区。
Spark中RDD的计算是以分区为单位的,函数会被作用到每个分区上
3、一个RDD会依赖于其他多个RDD。
RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。(Spark的容错机制)
4.KV类型的RDD会有一个Partitioner函数
非key-value的RDD的Parititioner的值是None,Partitioner函数决定了RDD本身的分区数量,也决定了parent RDD Shuffle输出时的分区数量。
5、每个RDD维护一个列表,每个Partition的位置(preferred location)存储在一个列表中。
创建RDD
1、由外部存储系统的数据集创建
val rdd1 = sc.textFile("hdfs://node01:8020/wordcount/input/words.txt")
2、通过已有的RDD经过算子转换生成新的RDD
val rdd2=rdd1.flatMap(_.split(" "))
3、由一个已经存在的Scala集合创建
方法1 sc.parallelize(Array(1,2,3,4,5,6,7,8))
方法2 sc.makeRDD(List(1,2,3,4,5,6,7,8))
makeRDD方法底层调用了parallelize方法
RDD的算子分为两类:
1.Transformation转换操作:返回一个新的RDD
2.Action动作操作:返回值不是RDD(无返回值或返回其他的)
问答题:判断一个算子是Transformation??还是Action??
若返回值的类型是一个RDD那么这个算子就是Transformation,反之就是Action
如何理解spark的惰性计算?
RDD中的所有转换都是惰性求值/延迟执行的,也就是说并不会直接计算。
遇到Action动作时,这些转换才会真正运行
之所以使用惰性求值/延迟执行,是因为这样可以在Action时对RDD操作形成DAG有向无环图进行Stage的划分和并行优化,这种设计让Spark更加有效率地运行。
转换 |
含义 |
map(func) |
返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成 |
filter(func) |
返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) |
类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素) |
mapPartitions(func) |
类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U] |
mapPartitionsWithIndex(func) |
类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是 (Int, Interator[T]) => Iterator[U] |
sample(withReplacement, fraction, seed) |
根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子 |
union(otherDataset) |
对源RDD和参数RDD求并集后返回一个新的RDD |
intersection(otherDataset) |
对源RDD和参数RDD求交集后返回一个新的RDD |
distinct([numTasks])) |
对源RDD进行去重后返回一个新的RDD |
groupByKey([numTasks]) |
在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD |
reduceByKey(func, [numTasks]) |
在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置 |
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) |
|
sortByKey([ascending], [numTasks]) |
在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
sortBy(func,[ascending], [numTasks]) |
与sortByKey类似,但是更灵活 |
join(otherDataset, [numTasks]) |
在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup(otherDataset, [numTasks]) |
在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable |
cartesian(otherDataset) |
笛卡尔积 |
pipe(command, [envVars]) |
对rdd进行管道操作 |
coalesce(numPartitions) |
减少 RDD 的分区数到指定值。在过滤大量数据之后,可以执行此操作 |
repartition(numPartitions) |
重新给 RDD 分区 |
动作 |
含义 |
reduce(func) |
通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的 |
collect() |
在驱动程序中,以数组的形式返回数据集的所有元素 |
count() |
返回RDD的元素个数 |
first() |
返回RDD的第一个元素(类似于take(1)) |
take(n) |
返回一个由数据集的前n个元素组成的数组 |
takeSample(withReplacement,num, [seed]) |
返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子 |
takeOrdered(n, [ordering]) |
返回自然顺序或者自定义顺序的前 n 个元素 |
saveAsTextFile(path) |
将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 |
saveAsSequenceFile(path) |
将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。 |
saveAsObjectFile(path) |
将数据集的元素,以 Java 序列化的方式保存到指定的目录下 |
countByKey() |
针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。 |
foreach(func) |
在数据集的每一个元素上,运行函数func进行更新。 |
foreachPartition(func) |
在数据集的每一个分区上,运行函数func |
统计操作
算子 |
含义 |
count |
个数 |
mean |
均值 |
sum |
求和 |
max |
最大值 |
min |
最小值 |
variance |
方差 |
sampleVariance |
从采样中计算方差 |
stdev |
标准差:衡量数据的离散程度 |
sampleStdev |
采样的标准差 |
stats |
查看统计结果 |
repartition可以增加和减少rdd中的分区数,
coalesce默认减少rdd分区数,增加rdd分区数不会生效。
不管增加还是减少分区数原rdd分区数不变,变的是新生成的rdd的分区数
1.启动的时候指定的CPU核数确定了一个参数值:
spark.default.parallelism=指定的CPU核数(集群模式最小2)
2.对于Scala集合调用parallelize(集合,分区数)方法,
如果没有指定分区数,就使用spark.default.parallelism,
如果指定了就使用指定的分区数(不要指定大于spark.default.parallelism)
3.对于textFile(文件,分区数) defaultMinPartitions
如果没有指定分区数sc.defaultMinPartitions=min(defaultParallelism,2)
如果指定了就使用指定的分区数sc.defaultMinPartitions=指定的分区数
rdd的分区数
对于本地文件:
rdd的分区数 = max(本地file的分片数, sc.defaultMinPartitions)
对于HDFS文件:
rdd的分区数 = max(hdfs文件的block数目, sc.defaultMinPartitions)
所以如果分配的核数为多个,且从文件中读取数据创建RDD,即使hdfs文件只有1个切片,最后的Spark的RDD的partition数也有可能是2
redis数据持久化什么作用?(将内存中的数据写入到硬盘中,进行永久保存)
防止数据丢失!
Rdd数据持久化什么作用?
1、对多次使用的rdd进行缓存,缓存到内存,当后续频繁使用时直接在内存中读取缓存的数据,不需要重新计算。
2、将RDD结果写入硬盘(容错机制),当RDD丢失数据时,或依赖的RDD丢失数据时,可以使用持久化到硬盘的数据恢复。
缓存方法
Persist
Cache
这两个方法被调用时立即缓存,而是触发后面的action时
代码
val rdd1 = sc.textFile("hdfs://node01:8020/wordcount/input/words.txt")
val rdd2 = rdd1.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_)
rdd2.cache
rdd2.sortBy(_._2,false).collect//触发action,会去读取HDFS的文件,rdd2会真正执行持久化
rdd2.sortBy(_._2,false).collect
缓存级别
掌握的级别
MEMORY_ONLY(默认):数据写入内存,内存不足,部分分区数据不缓存。
MEMORY_AND_DISK:数据写入内存,内存不足写入磁盘。
DISK_ONLY:数据写入硬盘。
总结:
1.RDD持久化/缓存的目的是为了提高后续操作的速度
2.缓存的级别有很多,默认只存在内存中,开发中使用memory_and_disk
3.只有执行action操作的时候才会真正将RDD数据进行持久化/缓存
4.实际开发中如果某一个RDD后续会被频繁的使用,可以将该RDD进行持久化/缓存
将数据写入HDFS,利用HDFS永久存储。
操作过程
SparkContext.setCheckpointDir("目录") //HDFS的目录
RDD.checkpoint()
代码:
sc.setCheckpointDir("hdfs://node01:8020/ckpdir")
val rdd1 = sc.textFile("hdfs://node01:8020/wordcount/input/words.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
rdd1.checkpoint()
rdd1.collect
持久化结果
cache和Checkpoint的区别
位置
Persist 和 Cache将数据保存在内存
Checkpoint将数据保存在HDFS
生命周期
Persist 和 Cache 程序结束后会被清除或手动调用unpersist方法。
Checkpoint永久存储不会被删除。
RDD依赖关系(血统Lineage)
Persist和Cache,不会丢掉RDD间的依赖链/依赖关系
Checkpoint会斩断依赖链
RDD的依赖关系
窄依赖:父RDD的一个分区只会被子RDD的一个分区依赖(图一)
宽依赖:父RDD的一个分区会被子RDD的多个分区依赖(涉及到shuffle)(图二)
为什么划分宽窄依赖?
1.对于宽依赖
是划分Stage的依据(目的是实现并行化计算)
2.对于窄依赖
Spark Stage可以并行计算(并行计算速度快)
DAG:指的是数据转换执行的过程,有方向,无闭环(其实就是RDD执行的流程)
DAG边界
开始:通过SparkContext创建的RDD
结束:触发Action,一旦触发Action就形成了一个完整的DAG
说明:
一个Spark应用中可以有一到多个DAG,取决于触发了多少次Action
一个DAG中会有不同的阶段/stage,划分阶段/stage的依据就是宽依赖
一个阶段/stage中可以有多个Task,一个分区对应一个Task
为什么要划分Stage??
为了实现并行化计算,提高计算效率。
总结
Spark会根据shuffle/宽依赖使用回溯算法来对DAG进行Stage划分,从后往前,遇到宽依赖就断开,遇到窄依赖就把当前的RDD加入到当前的stage/阶段中
哪些算法是宽依赖??
??????
Spark概念介绍
1.Application:指的是用户编写的Spark应用程序/代码,包含了Driver功能代码和分布在集群中多个节点上运行的Executor代码。
2.Driver:Spark中的Driver即运行上述Application的Main()函数并且创建SparkContext,SparkContext负责和ClusterManager通信,进行资源的申请、任务的分配和监控等
3.Cluster Manager:指的是在集群上获取资源的外部服务,Standalone模式下由Master负责,Yarn模式下ResourceManager负责;
4.Executor:是运行在工作节点Worker上的进程,负责运行任务,并为应用程序存储数据,是执行分区计算任务的进程;
5.RDD:Resilient Distributed Dataset弹性分布式数据集,是分布式内存的一个抽象概念;
6.DAG:Directed Acyclic Graph有向无环图,反映RDD之间的依赖关系和执行流程;
7.Job:作业,按照DAG执行就是一个作业;Job==DAG
8.Stage:阶段,是作业的基本调度单位,同一个Stage中的Task可以并行执行,多个Task组成TaskSet任务集
9.Task:任务,运行在Executor上的工作单元,一个Task计算一个分区,包括pipline上的一系列操作
Spark执行任务的基本流程(简答-Yarn 缺)
1.当一个Spark应用被提交时,首先需要为这个Spark Application构建基本的运行环境,即由任务控制节点(Driver)创建一个SparkContext,
2.SparkContext向资源管理器注册并申请运行Executor资源;
3.资源管理器为Executor分配资源并启动Executor进程,Executor运行情况将随着心跳发送到资源管理器上;
4.SparkContext根据RDD的依赖关系构建成DAG图,并提交给DAGScheduler进行解析划分成Stage,并把该Stage中的Task组成Taskset发送给TaskScheduler。
5.TaskScheduler将Task发放给Executor运行,同时SparkContext将应用程序代码发放给Executor。
6.Executor将Task丢入到线程池中执行,把执行结果反馈给任务调度器,然后反馈给DAG调度器,运行完毕后写入数据并释放所有资源。
总结(master,缺Yarn)
1.Spark应用被提交-->SparkContext向资源管理器注册并申请资源 (??) -->启动Executor
2.RDD-->构建DAG-->DAGScheduler划分Stage形成TaskSet-->TaskScheduler提交Task-->Worker上的Executor执行Task
1.累加器accumulators:累加器支持在所有不同节点之间进行累加计算
package cn.itcast.core
|
2.广播变量broadcast variables:广播变量用来把变量在所有节点的内存之间进行共享,在每个机器上缓存一个只读的变量,而不是为机器上的每个任务都生成一个副本。
import org.apache.spark.broadcast.Broadcast
//先将数据添加到广播变量 //在广播变量中获取数据 |
通常在两个表(一个大表一个小表,将小表广播出去)的join时会用到广播变量。
txt文本用sc将数据写入到msql和从msql中读取
|
txt文本用sc将数据写入到hbase和从hbase中读取
|