目录
一、Spark介绍
1、概述
2、来源
二、Spark的生态系统模块
三、Spark的使用模式
1、Spark单机模式安装
2、Spark集群模式安装
四、RDD介绍
1、概述
2、创建RDD两种方式
3、分区概念
五、RDD的操作
1、Transformation变化
2、Action执行
3、Controller控制
Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的,后贡献给Apache。是一种快速、通用、可扩展的大数据分析引擎。它是不断壮大的大数据分析解决方案家族中备受关注的明星成员,为分布式数据集的处理提供了一个有效框架,并以高效的方式处理分布式数据集。Spark集批处理、实时流处理、交互式查询、机器学习与图计算于一体,避免了多种运算场景下需要部署不同集群带来的资源浪费。目前,Spark社区也成为大数据领域和Apache软件基金会最活跃的项目之一,其活跃度甚至远超曾经只能望其项背的Hadoop。
Spark是一种分布式的,快速的,通用的,可靠的,免费的计算框架。
目前市面上比较常见和流行的计算框架:
①、Hadoop MapReduce->离线批处理
②、Spark->离线批处理+实时流处理
③、Storm->实时流处理
④、Flink->实时流处理
从上图我们观察,MapReduce在执行任务的时候,底层会发生Shuffle过程,则会产生大量和多次的磁盘I/O,拉低性能。并且在Shuffle过程会针对每个分区内的数据做排序,耗费大量的CPU,拉低性能。此外,在做一个迭代性质算法的场景,比如梯度下降法,逻辑回归等的时候,需要重复用到某些中间步结果集。由于不能做到中间结果集的复用,会带来大量的重新计算代价。
Spark团队吸取了以上MapReduce的一些经验教训,做出了相关优化,比如尽量减少shuffer过程产生,减少不必要的排序以及支持缓存机制,达到中间结果集的复用
上图是Spark的整个生态系统。可以看出Spark涵盖了大数据处理的各种应用场景:
1、离线批处理
2、交互式查询和数仓
3、实时流处理
4、算法建模和数据挖掘
即Spark可以一站式处理大数据的所有应用场景,避免部署多种不同集群带来的麻烦。
常见的模式有三种:
Local 本地单机模式:一般用于测试或练习
Standalone Spark集群模式:Spark集群的资源管理由Spark自己的Master来管理的
On Yarn Saprk集群模式:Spark集群的资源管理由Yarn的ResourceManager来管理的
①、步骤
Ⅰ、安装和配置好JDK
Ⅱ、上传和解压Spark安装包 spark-2.0.1-bin-hadoop2.7.tgz
tar -xvf spark-2.0.1-bin-hadoop2.7.tgz
Ⅲ、进入Spark安装目录下的conf目录
复制spark-env.sh.template 文件为 spark-env.sh
在其中修改,增加下面内容:
SPARK_LOCAL_IP=服务器IP地址
②、启动
在bin目录下执行: sh spark-shell --master=local
启动后发现打印消息
①、步骤
1)上传解压spark安装包
2)进入spark安装目录的conf目录
3)配置spark-env.sh文件
#本机ip地址 SPARK_LOCAL_IP=hadoop01 #spark的shuffle中间过程会产生一些临时文件,此项指定的是其存放目录,不配置默认是在 /tmp目录下 SPARK_LOCAL_DIRS=SPARK_LOCAL_DIRS=/home/software/spark-2.0.1-bin-hadoop2.7/tmp export JAVA_HOME=/home/software/jdk1.8.0_131
4)在conf目录下,编辑slaves文件
mv slaves.template slaves 重命名一下
hadoop01 hadoop02 hadoop03
5)配置完后,将spark目录发送至其他节点,并更改对应的 SPARK_LOCAL_IP 配置
scp -r spark-2.0.1-bin-hadoop2.7 hadoop02:/home/software/
scp -r spark-2.0.1-bin-hadoop2.7 hadoop03:/home/software/6)进入hadoop02与hadoop03的spark-env.sh修改
②、启动
1)如果你想让 01 虚拟机变为master节点,则进入01 的spark安装目录的sbin目录
执行: sh start-all.sh
2)通过jps查看各机器进程,
01:Master +Worker
02:Worker
03:Worker
3)通过浏览器访问管理界面
hadoop01:8080
4)通过spark shell 连接spark集群
进入spark的bin目录
执行:sh spark-shell.sh --master spark://hadoop01:7077
6)在集群中读取文件:
sc.textFile("/root/work/words.txt")
默认读取本机数据 这种方式需要在集群的每台机器上的对应位置上都一份该文件 浪费磁盘
7)所以应该通过hdfs存储数据
sc.textFile("hdfs://hadoop01:9000/mydata/words.txt");
8)本地打jar包在spark的bin目录下运行指令:
sh spark-submit --class cn.yang.wordcount.Driver wordcount.jar
注:可以在spark-env.sh 中配置选项 HADOOP_CONF_DIR 配置为hadoop的etc/hadoop的地址 使默认访问的是hdfs的路径
注:如果修改默认地址是hdfs地址 则如果想要访问文件系统中的文件 需要指明协议为file 例如 sc.text("file:///xxx/xx")
RDD弹性分布式数据集,它是Spark最核心的数据结构,特点是可以并行操作,并且是容错的。我们刚接触RDD的话,可以把它看做是一个集合类型(类似于Array或List),用于存储数据和操作数据。
RDD和普通集合的区别:
①、RDD有分区机制,可以分布式,并行的处理同一个RDD数据集,从而极大提高处理效率。分区数量由程序员自己定
②、RDD有容错机制。即数据丢失后,可以进行恢复。
①、执行Transform操作(变换操作),将一个集合(Array或List)转变成为一个RDD
转变为RDD:val r1=sc.parallelize(List(1,2,3,4),2)
查看分区数量:r1.partitions.size
查看分区数据:r1.glom.collect
查看RDD整体数据:r1.collect 收集rdd中的数据组成Array返回,此方法将会把分布式存储的rdd中的数据集中到一台机器中组建Array。
在生产环境下一定要慎用这个方法,容易内存溢出。
②、读取外部存储系统的数据集,如HDFS,HBase,或任何与Hadoop有关的数据源。读取外部存储文件系统的文件,把文件读取成为一个RDD
读取Linux本地文件:val r4=sc.textFile("file:///home/1.txt",2)
读取HDFS文件:val r5=sc.textFile("hdfs://hadoop01:9000/1.txt",2)
在上图中, 一个RDD有item1~item25个数据,共5个分区,分别在3台机器上进行处理。、
此外,spark并没有原生的提供rdd的分区查看工具 我们可以自己来写一个
import org.apache.spark.rdd.RDD
import scala.reflect.ClassTag
object su {
def debug[T: ClassTag](rdd: RDD[T]) = {
rdd.mapPartitionsWithIndex((i: Int, iter: Iterator[T]) => {
val m = scala.collection.mutable.Map[Int, List[T]]()
var list = List[T]()
while (iter.hasNext) {
list = list :+ iter.next
}
m(i) = list
m.iterator
}).collect().foreach((x: Tuple2[Int, List[T]]) => {
val i = x._1
println(s"partition:[$i]")
x._2.foreach { println }
})
}
}
对于RDD的操作,总的来说分为三种:
特点:都是懒操作(算子),调用后并不是马上执行,不会真正触发RDD的处理运算。比如典型的textFile方法。此外,每当调用一次变化操作(懒方法),就会产生一个新的RDD
常用的Transformation变化操作:
只有我们调用才执行:
Transformation |
Meaning |
map(func) | 参数是函数,函数应用于RDD每一个元素,返回值是新的RDD 案例展示: map 将函数应用到rdd的每个元素中 |
flatMap(func) | 扁平化map,对RDD每个元素转换,然后再扁平化处理 案例展示: 注:map和flatMap有何不同? map: 对RDD每个元素转换 flatMap: 对RDD每个元素转换, 然后再扁平化(即去除集合) 所以,一般我们在读取数据源后,第一步执行的操作是flatMap |
filter(func) | 参数是函数,函数会过滤掉不符合条件的元素,返回值是新的RDD 案例展示: filter 用来从rdd中过滤掉不符合条件的数据 |
mapPartitions(func) | 该函数和map函数类似,只不过映射函数的参数由RDD中的每一个元素变成了RDD中每一个分区的迭代器。 案例展示: 补充:此方法可以用于某些场景的调优,比如将数据存储数据库, 如果用map方法来存,有一条数据就会建立和销毁一次连接,性能较低 |
mapPartitionsWithIndex (func) |
函数作用同mapPartitions,不过提供了两个参数,第一个参数为分区的索引。 案例展示: |
union(other Dataset) | 返回一个新数据集,该数据集包含源数据集中的元素和参数的并集。 union 并集 -- 也可以用++实现 案例展示: |
intersection (other Dataset) |
返回一个新的RDD,该RDD包含源数据集中的元素与参数的交集。 案例展示: |
subtract | 返回差集 案例展示: |
distinct([numTasks]) | 返回包含源数据集的不同元素的新数据集。 没有参数,将RDD里的元素进行去重操作 案例展示: |
groupByKey([numTasks]) | 当对(K, V)对的数据集调用时,返回(K, V)对的数据集。 注意:如果你分组是为了对每个键执行聚合(比如求和或平均),那么使用reduceByKey或aggregateByKey将产生更好的性能。 注意:默认情况下,输出的并行级别取决于父RDD的分区数量。你可以传递一个可选的numTasks参数来设置不同数量的任务。 案例展示: 注:groupByKey对于数据格式是有要求的,即操作的元素必须是一个二元tuple, |
reduceByKey(func,[numTasks]) |
这个方法就是在groupByKey的基础上传入一个函数来处理数据 案例展示: var rdd = sc.makeRDD( List( ("hello",1),("spark",1),("hello",1),("world",1) ) ) rdd.reduceByKey(_+_); 注:reduceByKey操作的数据格式必须是一个二元tuple |
aggregateByKey (zeroValue)(seqOP,comOP,[numTasks]) |
当对(K, V)对的数据集调用时,返回一个(K, U)对的数据集,其中每个键的值使用给定的组合函数和中性的“零”值进行聚合。允许与输入值类型不同的聚合值类型,同时避免不必要的分配。与groupByKey类似,reduce任务的数量可以通过第二个可选参数进行配置。 案例展示: aggregateByKey(zeroValue)(func1,func2)
|
sortByKey ([ascending],[numTasks]) |
当在K实现了Ordered的(K, V)对数据集上调用时,返回一个(K, V)对数据集,按键的升序或降序排序。 案例展示: |
join (otherDataset,[numTasks]) |
当调用类型为(K, V)和(K, W)的数据集时,返回一个(K, (V, W))对的数据集,每个键有所有对的元素。通过leftOuterJoin、rightOuterjoin和fullOuterJoin支持外部连接。 案例展示: |
cartesian(otherDataset) |
参数是RDD,求两个RDD的笛卡儿积 案例展示: |
coalesce(numPartitions) | coalesce(n,true/false) 扩大或缩小分区 案例展示: val rdd = sc.makeRDD(List(1,2,3,4,5),2) rdd.coalesce(3,true);//如果是扩大分区 需要传入一个true 表示要重新shuffle rdd.coalesce(2);//如果是缩小分区 默认就是false 不需要明确的传入 |
repartition(numPartitions) | repartition(n) 等价于上面的coalesce |
特点:操作会真正触发执行
Action |
Meaning |
reduce(func) | 使用函数func(它接受两个参数并返回一个)聚合数据集的元素。这个函数应该是交换的和结合的,这样才能正确地并行计算。 并行整合所有RDD数据,例如求和操作 |
collect() |
返回RDD所有元素,将rdd分布式存储在集群中不同分区的数据 获取到一起组成一个数组返回 要注意 这个方法将会把所有数据收集到一个机器内,容易造成内存的溢出 在生产环境下千万慎用 |
count() |
统计RDD里元素个数 案例展示: val rdd = sc.makeRDD(List(1,2,3,4,5),2) rdd.count |
first() |
返回数据集的第一个元素(类似于take(1))。 |
take(n) |
返回包含数据集的前n个元素的数组。 案例展示: take 获取前几个数据 val rdd = sc.makeRDD(List(52,31,22,43,14,35)) rdd.take(2) |
takeOrdered(n, [ordering]) |
使用自然顺序或自定义比较器返回RDD的前n个元素。 案例展示: takeOrdered(n) 先将rdd中的数据进行升序排序 然后取前n个 val rdd = sc.makeRDD(List(52,31,22,43,14,35)) rdd.takeOrdered(3) |
top(n) | top(n) 先将rdd中的数据进行降序排序 然后取前n个 val rdd = sc.makeRDD(List(52,31,22,43,14,35)) rdd.top(3) |
saveAsTextFile(path) |
将数据集的元素作为文本文件(或一组文本文件)写入本地文件系统、HDFS或任何其他hadoop支持的文件系统中的给定目录中。Spark会对每个元素调用toString,将其转换为文件中的一行文本。 案例展示: saveAsTextFile 按照文本方式保存分区数据 val rdd = sc.makeRDD(List(1,2,3,4,5),2); rdd.saveAsTextFile("/root/work/aaa") |
countByKey() |
仅在(K, V)类型的rdd上可用。返回(K, Int)对的哈希图,包含每个键的计数。 |
foreach(func) |
对数据集的每个元素运行函数func。这通常用于一些副作用,如更新Accumulator或与外部存储系统交互。 注意:在foreach()之外修改Accumulators以外的变量可能会导致未定义的行为。 |
对性能效率和容错方面的支持。persist , cache, checkpoint
后面在RDD的持久化会详细讲解
import org.apache.spark.storage._
val rdd1=sc.makeRDD(1 to 5)
rdd1.cache //cache只有一种默认的缓存级别,即MEMORY_ONLY
rdd1.persist(StorageLevel.MEMORY_ONLY)