Spark
RDD(弹性分布式数据集)
特点:逻辑概念。可分区、可依赖,可缓存。
执行流程
执行过程:提交任务后,会首先执行main()函数,来启动SparkSubmit(),客户端向RM申请启动AppM,RM创建AppM读取配置信息,启动Driver进程,初始化SparkContext,并向RM申请启动Executor的资源,RM返回可用资源列表。启动Executor节点后,向Driver端反向注册,注册成功后,启动任务。DAGSchedule根据action算子生成job任务,然后根据shuffle依赖关系,将job划分成多个stage,之后生成多个task,由taskSchedule将task传到Executor执行。
每个action算子触发一个job,每个job可以根据shuffle划分成多个stage,然后每个stage根据分解成和分区数量相同的task数量。
map task个数:通过输入数据大小/每个分片大小。
reduce task个数:一般通过设置partition数量简介设置。
逻辑处理流程(输入输出、中间数据、依赖关系)
分区方法:水平划分、HashPartitioner、RangePartitioner。
数据操作:
transformation操作:
map(func)、
flatmap(func)、
sample(withReplacement,fraction,seed)、
groupByKey([numPartitions])、
reduceByKey(func,[numPartitions])、
foldByKey(zeroValue)(func)、
aggregateByKey(zeroValue)(seqOp,combOp,[numPartitions])、
combineByKey(createCombiner,mergeValue,mergeCombiners,[numPartitions])、
join(otherDataset,[numPartitions])、
coalesce(numPartitions)
join的三种实现方式:sort merge join、broadcast join、hash join。
buildlter小的时候,可以用broadcast join;
通常情况下可以用:sort merge join;
hash join使用条件:总大小大于(不适合使用)broadcast join,各分区大小小于broadcast大小,streamlter是buildlter的三倍。
在spark sql查询优化阶段,spark会自动将大表设为左表,即streamIter,将小表设为右表,即buildIter。
一般让大表作为streamlter表,即left outer join 让左表为大表。
full outer join仅采用sort merge join实现,左边和右表既要作为streamIter,又要作为buildIter。
left semi join是以左表为准,在右表中查找匹配的记录,如果查找成功,则仅返回左边的记录,否则返回null。
left anti join与left semi join相反,是以左表为准,在右表中查找匹配的记录,如果查找成功,则返回null,否则仅返回左边的记录。
action操作:
count()、
collect()、
foreach(func)、
fold(zeroValue)(func)、
reduce(func)、
aggregate(zeroValue)(seqOp,combOp,[numPartitions])、
treeAggregate(zeroValue)(seqOp,combOp,depth)、
treeReduce(func,depth)、
take(num)、
saveAsTextFile()
MapReduce和Spark的优缺点:
- Spark更有通用性:将输入输出、中间数据抽象为一个RDD,更加灵活。可定义数据依赖关系连接中间数据,使用DAG图来组合输出处理操作。
- Spark更有易用性:数据操作很多,更容易实现复杂流程,可并行化。
Spark缺点:
- 单项操作,中间数据不可修改;
- 粗粒度,面向分区。
一个基于进程,一个基于线程。
物理执行计划(划分stask执行)
- 根据action()操作划分job。
- 根据shuffleDependency依赖关系,将job划分为执行阶段(stage)。
- 根据最后生成RDD的分区个数生成多个计算任务(task)。
同一个stage中的每个task可并行执行。
shuffle机制
shuffle Write:聚合----排序----分区。
- 不需要聚合和排序:spark根据partitionId,将record依次输出到不同的buffer中。每当buffer填满就将record溢写到磁盘上的分区文件中。BypassMergeSortShuffleWrite。适合分区个数小于200的情况。
- 不需要聚合,需要排序:采用Array来存放record,存不下,先扩容,还存不下,排序后spill到磁盘上,等map输出完后,再全局排序。适用于分区个数很大的情况。增加了排序的计算时间。
- 需要聚合:采用类似HashMap的结构来对record聚合,存不下,先扩容两倍,还存不下,排序后spill到磁盘上,等map输出完后,再全局聚合。使用聚合的情况。
shuffle Read:聚合----排序。
- 不需要聚合和排序:从map task获取数据,将记录输出到buffer中,下一个操作直接从buffer中获取。
- 不需要聚合,需要排序: 从map task获取数据,将数据存放在类Array中,存放不下,spill到磁盘进行,最后进行全局排序。
- 需要聚合:采用类似HashMap的结构来对record聚合,存不下,先扩容两倍,还存不下,排序后spill到磁盘上。如果需要排序,建立一个Array结构,排序。
PartitionedPairBuffer:用于map和reduce端的排序。
PartitionedAppendOnlyMap:用于map端聚合排序。
ExternalAppendOnlyMap:用于reduce端聚合排序。
shuffle类型
hashShuffle:每个MT会根据每个RT生成对应个数的磁盘文件,个数多,效率低
优化之后的hashShuffle:每个Executor根据每个RT生成对应个数的磁盘文件,个数相对少。
sortHashShuffle:每个Executor生成一个磁盘文件和对应的索引,个数少,效率高。
bypass:需要满足两个条件:1.不是聚合类的算子;2.MT个数小于默认的bypass个数(200)。
MapReduce和Spark的shuffle比较
MapReduce的shuffle:map stage和reduce stage。
- map stage:读取record,并输出新的record,输出到一个环形缓冲区100M,超过80M,将缓冲区中的record按照key排序后输出到磁盘上,之后如果需要聚合,需要等所有recordspill到磁盘后,使用combine进行全局聚合。
- reduce stage:通过网络获取map stage数据,然后放在内存中,放不下就先聚合排序,spill到磁盘,获取所有文件后,再启动一个reduce阶段进行全局聚合。
优点:阶段分明;内存消耗确定;支持spill磁盘。
缺点:只按key进行排序;不能在线聚合;产生临时文件过多。
Spark:可以按partitionId排序、key排序;使用hashmap可以在线聚合;可以合并分区文件。
数据缓存机制
缓存:对某些需要多次使用的数据进行缓存,目的是为了加速计算。
3个条件:
- 会被重复使用的数据
- 数据不易过大
- 非重复缓存的数据
cache():是lazy操作,不会立即执行,生成job后才会执行写到内存,cache直将数据写到内存,如果想使用别的存储方式,可以使用persist(方式)。缓存数据只能在job间共享,应用之间不能共享。
缓存级别:
缓存级别 | 存储位置 | 序列化存储 | 内存不足放磁盘 |
---|---|---|---|
NONE | 不存储 | ||
MEMORY_ONLY | 内存 | ||
MEMORY_ONLY _SER | 内存 | 序列化 | |
MEMORY_AND_DISK | 内存+磁盘 | 放 | |
MEMORY_AND_DISK_SER | 内存+磁盘 | 序列化 | 放 |
DISK_ONLY | 磁盘 | 序列化 | |
OFF_HEAP | 对外内存 | 序列化 | |
MEMORY_ONLY_2 | 多台机器内存 | ||
MEMORY_ONLY _SER_2 | 多台机器内存 | 序列化 | |
MEMORY_AND_DISK_2 | 多台机器内存+磁盘 | 放 | |
MEMORY_AND_DISK_SER_2 | 多台机器内存+磁盘 | 序列化 | 放 |
DISK_ONLY_2 | 多台机器磁盘 | 序列化 |
缓存数据的替换和回收:
- 自动缓存替换:LRU最久未使用的缓存被替换。
- 用户主从回收缓存数据:umpersis()操作。
错误容忍机制
错误容忍机制:在应用执行失败时能够自动恢复应用执行,并且执行结果与正常执行时得到的结果一致。
方法:
- 重新执行计算任务来容忍错误
- 通过采用checkpoint机制,对数据进行持久化。
重新计算条件:
- task输入数据与之前是一致的
- task的计算逻辑需要满足确定性(输入确定,输出确定)
- task的计算逻辑需要满足幂等性(对同样数据进行多次运算,结果一致)
延时删除:上游stage的shuffle write的结果写入本地磁盘,只有当job完成后,才删除shuffle write写入磁盘的数据。
使用lineage的数据溯源方法来对数据重新计算,记录上游数据和上游方法。
checkpoint机制:将计算过程中的数据进行持久化。对计算耗时高的数据进行持久化。对于迭代性job需要每隔几个job就对一写中间数据进行checkpoint。
checkpoint:等到job执行结束后,再重新启动该job计算一遍,对其中需要checkpoint的RDD进行持久化。这样可能会增加开销,可以先对持久化的数据进行缓存,然后将缓存的数据持久化,不需要重新计算,从而提高效率。
CheckPoint与数据缓存的区别
- 目的不同:数据缓存的目的是加速,checkpoint的目的是job运行失败后能够快速恢复。
- 存储性质和位置不同:数据缓存主要使用内存,checkpoint使用分布式文件系统。
- 写入速度和规则不同:数据缓存速度快,可在job运行时进行缓存,checkpoint在job结束后,重新执行job。
- 对lineage的影响不同:数据缓存对lineage没有影响,chechpoint会切断RDD的lineage。
- 应用场景不同:数据缓存使用于多次读取,占用空间不大的数据,checkpoint适用于数据依赖关系复杂,重新计算代价高的数据。
内存管理机制
内存消耗来源:用户代码、shuffle机制中产生的中间数据、缓存数据。
堆内内存和堆外内存:堆内内存受JVM的管理,堆外内存受操作系统的管理。
堆内内存:执行内存(shuffle)、缓存内存、用户内存、预留内存。
堆外内存:执行内存、缓存内存。
Spark内存模型:
- 静态内存管理模型:数据缓存空间60%,框架执行空间20%,用户代码空间20%。
- 统一内存管理模型:数据缓存和框架执行共享一个大空间(60%),动态调整(有上下界),其他内存(40%),用户代码设置固定值。(执行内存被存储内存占用,存储内存可以转存到磁盘,但是存储内存被执行内存占用后,执行内存不能归还;简单来说,执行内存是老大)
task占用内存大小=[1/2N,1/N] * Execution Memory。
数据缓存空间管理:RDD缓存数据(RDD partition)、广播数据(Broadcase data),task计算结果(TaskResult)
Spark Streaming
SparkStreaming基于kafka获取数据的方式,主要有俩种,即Receiver和Direct。
基于Receiver的方式,是SparkStreaming给我们提供了kafka访问的高层api的封装;这种方式可能会因为底层的失败而丢失数据,如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming的预写日志机制(Write Ahead Log,WAL),该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如HDFS)上的预写日志中,所以当底层节点出现了失败,可以通过WAL中的数据进行恢复,但是效率会下降。
基于Direct的方式,就是直接访问,在SparkSteaming中直接去操作kafka中的数据,不需要前面的高层api的封装。而Direct的方式,可以对kafka进行更好的控制!同时性能也更好。