为什么使用Spark
MapReduce编程模型的局限性
- 繁杂
- 只有Map和Reduce两个操作,复杂的逻辑需要大量的样板代码
- 处理效率低:
- Map中间结果写磁盘,Reduce写HDFS,多个Map通过HDFS交换数据
- 任务调度与启动开销大
- 不适合迭代处理、交互式处理和流式处理
Spark是类Hadoop MapReduce的通用并行框架
- Job中间输出结果可以保存在内存,不在需要读写HDFS
- 比MapReduce平均快10倍以上
Spark优势
速度快
- 基于内存数据处理,比MR快100个数量级以上(逻辑回归算法测试)
- 基于硬盘数据处理,比MR快10个数量级以上
易用性
- 支持Java、Scala、Python、R语言
- 交互式shell方便开发测试
通用性
- 一栈式解决方案:批处理、交互式查询、实时流处理、图计算及机器学习
多种运行模式
- YARN、Mesos、EC2、Kubernetes、Standalone、Local
Spark技术栈
Spark Core
- 核心组件,分布式计算引擎
Spark SQL
- 高性能的基于Hadoop的SQL解决方案
Spark Streaming
- 可以实现高吞吐量、具备容错机制的准实时流处理系统
Spark GraphX
- 分布式图处理框架
Spark MLlib
- 构建在Spark上的分布式机器学习库
Spark初体验
spark-shell:Spark自带的交互式工具
本机:spark-shell --master local[*]
Standalone:spark-shell --master spark://MASTERHOST:7077
YARN:spark-shell --master yarn-client
安装Spark
***************** 配置spark **********************
tar -zxf spark-2.3.4-bin-hadoop2.6.tgz
mv spark-2.3.4-bin-hadoop2.6 /opt/soft/spark234
cd /opt/soft/spark234/conf
ls
cp spark-env.sh.template spark-env.sh
cp slaves.template slaves
vi slaves
vi spark-env.sh
export SPARK_MASTER_HOST=192.168.56.100
export SPARK_MASTER_PORT=7077
export SPARK_WORKER_CORES=2
export SPARK_WORKER_MEMORY=3g
export SPARK_MASTER_WEBUI_PORT=8888
cd ../sbin/
vi spark-config.sh
export JAVA_HOME=/opt/soft/jdk180
cd /opt/soft/spark234/sbin/
./start-all.sh
jps worker、master、Jps
cd ../bin/
./spark-shell
Spark架构设计
运行架构
在驱动程序中,通过SparkContext主导应用的执行
SparkContext可以连接不同类型的Cluster Manager(Standalone、YARN、Mesos),连接后,获得集群节点上的Executor
一个Worker节点默认一个Executor,可通过SPARK_WORKER_INSTANCES调整
每个应用获取自己的Executor
每个Task处理一个RDD分区
Spark架构核心组件
Spark API SparkContext
SparkContext
连接Driver与Spark Cluster(Workers)
Spark的主入口
每个JVM仅能有一个活跃的SparkContext
SparkContext.getOrCreate
pom:
Spark API SparkSession
Spark 2.0+应用程序的主入口:包含了SparkContext、SQLContext、HiveContext以及StreamingContext
SparkSession.getOrCreate
import org.apache.spark.sql.SparkSession
val spark = SparkSession.builder.master("local[2]").appName("appName").getOrCreate()
使用IDEA初始化Spark运行环境
需求说明
创建Maven项目,添加依赖
- scala-library
- spark-core
- spark-sql
创建SparkContext
创建SparkSession
使用Spark实现WordCount
Spark API SparkSession
RDD:Spark核心,主要数据抽象,数据集
Dataset:从Spark1.6开始引入的新的抽象,特定领域对象中的强类型集合,它可以使用函数或者相关操作并行地进行转换等操作
DataFrame:DataFrame是特殊的Dataset
Spark RDD概念
简单的解释
- RDD是将数据项拆分为多个分区的集合,存储在集群的工作节点上的内存和磁盘中,并执行正确的操作
复杂的解释
- RDD是用于数据转换的接口
- RDD指向了存储在HDFS、Cassandra、HBase等、或缓存(内存、内存+磁盘、仅磁盘等),或在故障或缓存收回时重新计算其他RDD分区中的数据
RDD是弹性分布式数据集
**分布式数据集
- RDD是只读的、分区记录的集合,每个分区分布在集群的不同节点上
- RDD并不存储真正的数据,只是对数据和操作的描述
**弹性
- RDD默认存放在内存中,当内存不足,Spark自动将RDD写入磁盘
**容错性
- 根据数据血统,可以自动从节点失败中回复分区
RDD与DAG
两者是Spark提供的核心抽象
DAG(有向无环图)反映了RDD之间的依赖关系
RDD的特性
一系列的分区(分片)信息,每个任务处理一个分区
每个分区上都有compute函数,计算该分区中的数据
RDD之间有一系列的依赖
分区器决定数据(key-value)分配至哪个分区
优先位置列表,将计算任务分派到其所在处理数据块的存储位置
RDD编程流程
RDD创建–> RDD转换–> RDD持久化–> RDD执行
RDD的创建
使用集合创建RDD
val rdd = sc.parallelize(List(1,2,3,4,5,6))
rdd.count
rdd.partitions.size
val rdd = sc.parallelize(List(1,2,3,4,5,6),5)
rdd.partitions.size
val rd = sc.makeRDD(List(1,2,3,4,5,6))
1.Spark默认会根据集群的情况来设置分区的数量,也可以通过parallelize()第二参数来指定
2.Spark会为每一个分区运行一个任务进行处理
scala> val rdd = sc.parallelize(List(1,2,3,4,5,6))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[17] at parallelize at <console>:24
scala> rdd.count
res4: Long = 6
scala> rdd.partitions.size
res5: Int = 4 虚拟机分了4核,默认4个区
scala> val rdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[18] at parallelize at <console>:24
scala> rdd.partitions.size
res6: Int = 4
scala> val rdd = sc.parallelize(List(1,2,3))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[19] at parallelize at <console>:24
scala> rdd.partitions.size
res7: Int = 4
scala> val rdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3) 分3个片
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[20] at parallelize at <console>:24
scala> rdd.partitions.size
res8: Int = 3
scala> val rdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),60)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[21] at parallelize at <console>:24
scala> rdd.partitions.size
res9: Int = 60
scala> val rdd = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10),3)
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[22] at parallelize at <console>:24
scala> rdd.partitions.foreach(println)
org.apache.spark.rdd.ParallelCollectionPartition@a17
org.apache.spark.rdd.ParallelCollectionPartition@a18
org.apache.spark.rdd.ParallelCollectionPartition@a19
scala> rdd.partitions.foreach(x => x.index)
scala> rdd.partitions.foreach(x => println(x.index))
0
1
通过加载文件产生RDD
// 文件中的一行文本作为RDD的一个元素
val distFile = sc.textFile("file:///home/hadoop/data/hello.txt")
disFile.count
val distHDFSFile = sc.textFile("hdfs://hadoop000:8020/hello.txt")
1.加载"file://…"时,以local运行仅需一份本地文件,以Spark集群方式运行,应保证每个节点均有该文件的本地副本
2.支持目录、压缩文件以及通配符
sc.textFile("/my/directory")
sc.textFile("/my/directory/*.txt")
// 1、Spark默认访问HDFS
sc.textFile("/my/directory/*.gz")
// 2、Spark默认为HDFS文件的每一个数据块创建一个分区,也可以通过textFile()第二个参数指定,但只能比数据块数量多
其他创建RDD的方法
SparkContext.wholeTextFiles():可以针对一个目录中的大量小文件返回<filename,fileContent>作为PairRDD
普通RDD:org.apache.spark.rdd.RDD[data_type]
PairRDD:org.apache.spark.rdd.RDD[(key_type,value_type)]
SparkContext.sequenceFileK,V:Hadoop SequenceFile的读写支持
SparkContext.hadoopRDD()、newAPIHadoopRDD():从Hadoop接口API创建
SparkContext.objectFile():RDD.saveAsObjectFile()的你操作
RDD创建方式的最佳实践
测试环境:
- 使用内存集合创建RDD
- 使用本地文件创建RDD
生产环境:
- 使用HDFS文件创建RDD
RDD分区
分区是RDD被拆分并发送到节点的不同块之一
我们拥有的分区越多,得到的并行性就越强
每个分区都是被分发到不同Worker Node的候选者
每个分区对应一个Task
RDD操作
分为lazy和non-lazy两种
Transformation(lazy):也称转换操作、转换算子
Actions(non-lazy):立即执行,也称为动作操作、动作算子
RDD转换算子
对于转换操作,RDD的所有转换都不会直接计算结果
仅记录作用于RDD上的操作
当遇到动作算子(Action)时才会进行真正计算
RDD常用的转换算子
map算子
对RDD中的每个元素都执行一个指定的函数来产生一个新的RDD
任何原RDD中的元素在新RDD中都有且只有一个元素与之对应
输入分区与输出分区一一对应
// 将原RDD中每个元素都乘以2来产生一个新的RDD
val a = sc.parallelize(1 to 9)
val b = a.map(x => x * 2)
a.collect
b.collect
// map把普通RDD编程PairRDD
val a = sc.parallelize(List("dog","tiger","lion","cat","panther","eagle"))
val b = a.map(x => (x,1))
b.collect
filter算子
对元素进行过滤,对每个元素应用指定函数,返回值为true的元素保留在新的RDD中
val a = sc.parallelize(1 to 10)
a.filter(_%2 == 0).collect
a.filter(_<4).collect
// map & filter
val rdd = sc.parallelize(List(1 to 6))
val mapRdd = rdd.map(_*2)
mapRdd.collect
val filterRdd = mapRdd.filter(_>5)
filterRdd.collect
mapValues算子
原RDD中的Key保持不变,与新的Value一起组成新的RDD中的元素,仅适用于PairRDD
val a = sc.parallelize(List("dog","tiger","lion","cat","panther","eagle"))
val b = a.map(x => (x.length,x))
b.mapValues("x"+_+"x").collect
// 输出结果:
Array((3,xdogx),(5,xtigerx),(4,xlionx),(3,xcatx),(7,xpantherx),(5,xeaglex))