1 分区数
默认分区数是分配给运行环境的最大CPU核数
读取外部文件:Math.min(2, 分配给运行环境的最大CPU核数)
2 如何分区
1 数据读取以行的形式读取
2 每行数据读取以偏移量为单位,偏移量不会重复读取
3 读取多个文件时候,计算分区以文件为单位分区
读取外部文件 textFile,我们设置分区数参数时,赋予的值不是实际分区数,而是最小分区数
– 如果路径下所有文件内容总字节数能整除设置的最小分区数,则实际分区数则为设置分区数
– 如果不能整除,看下面例子
注意读取数据时,假设每个分区读取3个字节,读取到某一行时数据超过了3个字节,程序会读完整行数据 (按照 next readLine 方式读取),改分区会记录超过3个字节的整行数据。
设置最小分区为3,外部文件数据如下
例子1
1
2
3
4
1234 数据占用10个字节,每个数字一个字节,每行最后会默认加 /r/n 两个字节,共10个字节。
会先3个分区每个分区写入3个字节,最后一个字节会写入第四个新分区。
虽然设置3个分区,但实际是用了4个分区。
4行对应的字节偏移量范围 分别是
1@@ => 012 [0,3]
2@@ => 345 [3,6]
3@@ => 678 [6,9]
4 => 9 [9,10]
最终输出结果,第一个分区 1,2(0-3之间,按行读会读取完2一整行数据),第二个分区3,第三个分区4,第四个分区空
例子2
123456
123456 占用6个字节。可以被3整除,所以实际分区数为3,每个分区放2个字节
例子3
文件19个字节,设置分区数为5
19/5 = 3,每个分区写入3个字节,所以需要7个分区
**
**
懒加载,不会立刻执行
value 类型
map 每个元素一个一个执行
data = data.map(_*2)
mapPartitions 以分区为单位,一个分区元素执行map
flatMap
集合List [1,2,3] 扁平为1,2,3。整体转个体
glom
和flatMap相反,个体转整体
groupBy 分组,每个分区放划分好不同组的数据
filter 过滤
sample 采样
distinct 去重
原理:rdd.map(x=> (x,null)).reduceByKey((x,y) => x, numPartitions).map(_._1)
给每个元素设成 (x,null) 然后聚合在取 第一个字段x值
coalesce 缩小分区,重新分区
比如去重,过滤之后数据变少,就可以重新分区
默认分区数据不会打乱重组,不会shffule,只是单纯的合并。可以参数打开重组
repartition 扩大分区,底层是coalesce,默认重新分区,数据会打乱重组
sortBy 默认正序排序
pipe 调用脚本
针对每个分区,都调用一次shell脚本,返回输出的rdd
双value类型
intersection 交集
union 并集
subtract 差集
zip 集合组合
分区和分区元素得保持一致
key-value类型
partitionBy 按照K值重新分区
rdd.partitionBy(new HashPartitioner(2)) 这里是hashcode%2取模分区
自定义分区器 rdd.partitionBy(new MyPartitioner(2))
class MyPartitioner(partitions: Int) extends Partitioner
重写方法:获取分区个数,和指定key分区规则
reduceByKey 根据相同key聚合
1 分区内聚合 2 shuffle分区间聚合
groupByKey 按照Key重新分组
不会分区内处理,直接shuffle分区间分组
aggregateByKey 按照Key处理分区内和分区间的聚合
aggregateByKey(0)(_+_,_+_)
参数: 1 zeroValue 每个分区每个key初始值 0
2 seqOp 分区内计算规则 _+_
3 combOp 分区间合并计算规则 _+_
foldByKey 是aggregateByKey简化版本
分区内和分区间同一计算逻辑,比reduceByKey多一个初始值设置
combineByKey 转换结构后,分区内和分区间的聚合操作
对拿到的数据做初始化,而不是设置初始值。然后做计算规则
sortByKey 按照key排序
mapValues 只对values操作
join 将相同key对应的多个value关联一起,交叉关联
cogroup 将相同的key,先聚合相同分区对应value,再聚合不同分区对应value
RDD
最小的计算单元,RDD数据处理通过装饰者设计模式,逐层套娃,实现最终的计算。
只有执行集合算子才会对数据进行业务逻辑操作。
RDD数据集:
RDD只封装计算逻辑,不存储数据。即下一轮RDD不会存储上一轮RDD的数据
弹性:
内存和磁盘的自动切换,数据丢视的自动恢复,计算出错的重试机制,可根据需要的重新分区
触发任务的执行
collect,take, save
持久化:
因为RDD不存储数据,通过血缘关系可以恢复数据
因为RDD不存储数据,如果一个RDD需要重复使用,是需要重头再执行获取数据
RDD对象可以重用,数据无法重用
对象.cache() 默认临时存储内存中用来重用
对象.persist(StorageLevel.DISK_ONLY)
可修改存储级别到磁盘中 (临时文件,执行完任务会删除)
涉及磁盘IO,性能会降低
cache和persist会在血缘关系中添加依赖,数据出现问题可以重头读取
对象.checkpoint()
检查点,需要落盘路径 (执行完任务后不会删除,长久的保存)
为了保证数据安全,重复使用存储数据需要重头获取。所以为了提高效率,一般和cache联合使用。先cache后checkpoint (cache内存中存储后,就不需要再重头获取数据,减少性能开销)
执行过程中,会切断并重新建立新的血缘关系 (等同数据源发生改变)
依赖
1 宽窄依赖
2 toDebugString 查看血缘关系
application - job - stage - task 每层都是一对多
一个applicationContext程序对应一个application
一个行动算子对应一个job
stage 数量 = shuffle/宽依赖 数量 + 1,ResultStage 是最后唯一的执行阶段
task:一个stage阶段中,最后一个RDD分区个数就是task个数
累加器
val sum = sc.longAccumulator("sum") 创建 (有log, double, collection类型)
sum.add(数值) 累加
sum.value 获取累加器的数值
广播变量
闭包数据,都以task为单位发送,一个executor包含多个task,多个task内存可能会重复保存数据
广播变量可以将任务数据保存在executor中,达到多个task资源共享executor内存的数据
1 Driver, Executor
yarn cluster 模式
yarn client 模式 (简单,略)
1 Driver -> Executor
2 Executor -> Driver
3 Executor -> Executor
旧版spark用akka/netty, 3x 版本后完全依赖netty
linux采用epoll形式模仿aio
1 RDD 依赖
2 阶段的划分
从后往前包含查找,从resultStage往前包含找stage
shuffle 数量 + 1
3 任务的切分
stage最后一轮rdd的分区数量
4 任务的调度
任务存放在任务池中。默认先进先出调度模式,也可公平调度
将任务池种取到的任务,序列化之后,发送task到executor (executor选取根据本地化级别来选取)
5种本地化级别
进程本地化:数据和计算在同一个进程中
节点本地化:同一个节点中
机架本地化:同一个机架
任意本地化
5 任务的执行
6 任务的重试和黑名单
推测执行:在Spark中,可以通过推测执行,来识别并在其他节点的Executor上重启某些运行缓慢的Task,并行处理同样的数据,谁先完成就用谁的结果,并将另一个未完成的Task Kill掉,从而加快Task处理速度。适用于某些Spark任务中部分Task被hang住或运行缓慢,从而拖慢了整个任务运行速度的场景
1 shuffle 原理和执行过程
早期hash shuffle等方式有小文件过多问题。目前shuffle方式通过索引写入一个文件如下图
shuffle 阶段一定会写磁盘。shuffle落盘数据量减小可以提高性能
上一轮task数据,先从内存写入file (内存不足时,会有temp临时文件溢写磁盘。溢写文件会归并排序)
下一轮task根据索引来读取节点上文件数据
2 shuffle 写磁盘
如果支持预聚合 (例如map相同key的value相加),也就是其他情况。
则是BaseShuffleHandle处理器,需要进行排序,然后写入同一个文件
预聚合如果内存不足,则会溢写磁盘
如果不支持预聚合,则不排序。为每个分区写一个文件,最后直接合并所有文件回归到一个文件+索引的方式
1 内存的分类
2 内存的配置
spark 3.0之后删除了静态内存管理,只有统一内存管理。包含了动态占用内存机制
执行内存和存储内存,谁内存富裕可以互相转换
1 存储内存缺失,执行内存富裕时,存储内存会借用部分执行内存。但是当需要归还给执行存储时,这部分内存执行的数据会被淘汰。如果使用cache方法设定级别是memory-only,是不会溢写磁盘,当这部分内存数据淘汰后,正常情况下也会发生cache数据丢失。
2 执行内存借用后且仍需使用,存储内存也不足时也无法强制让内存归还,存储内存会溢写磁盘。因为存储内存不足不影响程序执行,最多读取溢写磁盘数据,性能稍微差一点。
3 存储内存借用后且仍需使用,执行内存也不足时能强制让内存归还。因为如果不归还内存给执行内存,执行内存会执行出错,需要全部重新执行。
用来提高开发效率,兼容hive,方便写sql
DataFrame
以前sparkcore使用的sc.xxx 改成 spark.xxx 使用 DataFrame
val df = spark.read.json("xxxPath.json")
df.createTempView("user") 创建临时表 /
df.createOrReplaceTempView("user") 普通表的作用范围是session内, spark.newSession.sql(xxx user) 会找不到报错
df.createOrReplaceGlobalTempView("user") 是全局表,全局session内都可用
spark.sql("select * from user")
等等大量sql api
DataSet关注类型
DataFrame 是 DataSet 特定泛型,相当于DataSet(row)
RDD和dataFrame之间转换,DataSet和DataFrame转换,DataSet和RDD转换