Spark(一)
-
为什么使用Spark
- Map Reduce编程模型的局限性
- 繁杂
- 只有Map和Reduce两个操作,复杂的逻辑需要大量的样板代码
- 处理效率低
- Map中间结果写磁盘,Reduce写HDFS,多个Map通过HDFS交换数据
- 任务调度和启动开销大
- 不适合迭代处理,交互式处理和流式处理
- 繁杂
- Spark是类Hadoop MapReduce的通用的并行框架
- Job中间输出结果可以保存在内存,不再需要读写HDFS
- 比MapReduce平均快10倍以上
- Map Reduce编程模型的局限性
-
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 Core
-
Spark架构设计
-
运行架构
- 在驱动程序中,通过SparkContext主导应用的执行
- SparkContext可以连接不同类型的Cluster Manager (Standalone,YARN,Mesos),连接后,获得集群节点上的Executor
- 一个Worker节点默认一个Executor,可通过SPARK_WORKER_INSTANCES调整
- 每个应用获取自己的Executor
- 每个Task处理一个RDD分区
-
Spark API
-
SparkContext
-
连接Driver与Spark Cluster(Workers)
-
Spark的主入口
-
每个JVM仅能有一个活跃的SparkContext
-
SparkContext.getOrCreate
import org.apache.spark.{SparkConf,SparkContext} val conf = new SparkConf().setMaster("local[1]").setAppName("hellospark") val sc = SparkContext.getOrCreate(conf)
-
-
SparkSession
-
Spark2.0+应用的主入口:包含了SparkContext,SQLContext,HiveContext以及StreamingContext
-
SparkSession.getOrCreate
import org.apache.spark.sql.SparkSession val spark = SparkSession.bulider.master("local[2]").appName("name").getOrCreate()
-
-
RDD
- Spark核心,主要数据抽象
-
DataSet
- 从Spark1.6开始引入的新的抽象,特定领域对象中的强类型集合,它可以使用函数或者相关操作并行地进行转换等操作
-
DataFrame
- DataFrame是特殊的DataSet
-
-
RDD概念
-
解释
- RDD是用于数据转换的接口
- RDD是指向了存储在HDFS,Cassandra,Hbase等,或缓存(内存,内存+磁盘,仅磁盘),或在故障或缓存收回时重新计算其他RDD分区中的数据
-
RDD的五大特性
- 1.一系列的分区信息,每个任务处理一个分区
- 每个分区上都有compute函数,计算该分区中的数据
- RDD之间有一系列的依赖
- 分区器决定数据分配到哪个区
- 优先位置列表,将计算任务分派到其所在处理数据块的存储位置
-
RDD是弹性分布式数据集
- 分布式数据集
- RDD是只读的,分区记录的集合,每个分区分布在集群的不同节点上
- RDD并不是存储真正的数据,只是对数据和操作的描述
- 弹性
- RDD默认存放在内存中,当内存不足,Spark自动将RDD写入磁盘
- 容错性
- 根据数据血统,可以自动从节点失败中恢复分区
-
RDD与DAG
- 两者都是Spark提供的核心抽象
- DAG(有向无环图)反应了RDD之间的依赖关系
-
RDD的特性
- 一系列的分区(分片)信息,每个任务处理一个分区
-
每个分区上都有compute函数,计算该分区中的数据
- RDD之间有一系列的依赖
-
分区器决定数据分配至哪个分区
-
有限位置列表,将计算任务分派到其所在处理数据块的存储位置
-
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 rdd=sc.makeRDD(List(1,2,3,4,5,6))//底层调用parallelize,并给定默认分区
- 通过加载文件产生RDD
val distFile=sc.textFile("file:///home/hadoop/data/hello.txt") distFile.count val distHDFSFile=sc.textFile("hdfs://hadoop000:8020/hello.txt")
-
-
RDD分区
-
分区是RDD被拆分并发送到节点的不同块之一
-
拥有的分区越多,得到的并行性就越强
-
每个分区都是分发到不同Worker Node的候选者
-
每个分区对应一个Task
-
-
RDD的操作
- 分为lazy与non-lazy两种
-
Transformation(lazy):也称转换操作,转换算子
-
-
Actions(non-lazy):立即执行,也称动作操作,动作算子
-
RDD转换算子
-
对于转换操作,RDD的所有转换都不会直接计算结果
-
仅记录作用于RDD上的操作
-
当遇到动作算子时才会进行真正的计算
-
-
-
RDD持久化
-
RDD缓存机制:缓存数据至内存/磁盘,可大幅度提升Spark应用性能
- cache=persist(MEMORY)
- persist
-
缓存策略StorageLevel
- MEMORY_ONLY
- MEMORY_AND_DISK
- DISK_ONLY
- …
-
缓存应用场景
- 从文件加载数据之后,因为重新获取文件成本较高
- 经过较多的算子变换之后,重新计算成本较高
- 单个非常消耗资源的算子之后
-
使用注意事项
- cache()或persist()后不能再有其他算子
- cache()或persist()遇到Action算子完成后才生效
-
检查点:类似于快照
sc.setCheckpointDir("hdfs:/checkpoint") val rdd=sc.parallelize(List(('a',1), ('a',2), ('b',3), ('c',4))) rdd.checkpoint rdd.collect //生成快照 rdd.isCheckpointed rdd.getCheckpointFile
-
检查点与缓存的区别
- 检查点会删除RDD lineage,而缓存不会
- SparkContext被销毁后,检查点数据不会被删除
-
RDD共享变量
-
广播变量:允许开发者将一个只读变量(Driver端)缓存到每一个节点(Executor)上,而不是每一个人物传递一个副本
val broadcastVar=sc.broadcast(Array(1,2,3)) //定义广播变量 broadcastVar.value
- Driver端变量在每个Executor每个Task保存一个变量副本
- Driver端广播变量在每个Executor只保存一个变量副本
-
累加器:只允许added操作,常用于实现计数
val accum = sc.accumulator(0,"My Accumulator") sc.parallelize(Array(1,2,3,4)).foreach(x=>accum+=x) accum.value
-
-
RDD分区计算
- 分区大小限制为2GB
- 分区太少
- 不利于并发
- 更容易受数据倾斜影响
- groupBy,reduceByKey,sortByKey等内存压力大
- 分区过多
- Shuffle开销大
- 创建任务开销大
- 一般每个分区大小为128MB
- 如果每个分区小于但接近2000,则设置为大于2000
-
数据倾斜
- 指分区中的数据分配不均匀,数据集中在少数分区中
- 严重影响性能
- 通常发生在groupBy,join等之后
- 解决方案
- 使用新的Hash值(如对key加盐)重新分区
- 指分区中的数据分配不均匀,数据集中在少数分区中
-