Spark基础概念
RDD 弹性分布式数据集
-
弹性:当内存计算资源不足时,可以刷到磁盘上,rdd使用checkpoint在数据更新和丢失后对数据模型进行重建
-
分布式:可以分布在多台机器上进行计算
-
数据集:一组只读的,可分区的分布式数据集合,将形同属性的数据记录放在一起,每个分区相当于一个数据集片段
Spark概念关键词
- Master stand-alone模式中的主节点,管理所有work节点
- Worker stand-alone模式中work节点,负责任务的执行
- Driver 运行application的main方法
- Executor 负责Task的执行
- Job 一个Application中会有多个Job
- Task Task分为ShuffleMapTask和ResultTask
- TaskSet 在DagScheduler发送到TaskScheduler时 task的集合
- Stage 一个job会有多个Stage ,stage也分为ResultStage和ShuffleMapStage
- DAGScheduler job基于stage的有向无环图
- TaskScheduler 将TaskSet交给Work运行,
- yarn-cluster driver和Appmaster一起
- yarn-client driver在客户端,和appmaster分开的
RDD 创建方式
- 集合方式创建
val num = Array(1,2,3,4,5)
val rdd = sc.parallelize(num)
- 外部数据集
val rdd = sc.textFile("hdfs:///xxx")
RDD转换
sc.textFile 得到 HadoopRDD
.map/.faltMap 得到 MapPartitionsRDD
.reduceByKey 会先调用隐式转换 rddToPairRDDFunction方法,去调用PairRDDFunctions的reduceBykey方法,会返回一个ShuffledRDD
任务执行起点
当RDD中出现action算子时,每个action算子会触发sc.runJob启动任务
RDD 算子
- Transform:
map,filter,flatMap,sample,union,distict,groupByKey,reduceByKey,sortByKey,join,mapPartitions
- Action:
collect,count,take,first,saveAsTextFile
- Controller算子:
cache , persist
spark dependency
NarrowDependency
-
OneToOneDependency extends NarrowDependency
即parent rdd和child rdd中的partitions一一对应,例如 map filter -
RangeDependency extends NarrowDependency
多个Rdd合并到一个RDD,每个parent rdd占子Rdd的一个区间,例如 union
ShuffleDependency
RDD生产DAG过程
RDD.action–>sparkContext.runjob()–> dagScheduler.runJob --> submitJob --> eventProcessLoop.post --> DAGSchedulerEventProcessLoop.onReceive --> doOnReceive --> handleJobSubmitted --> createResultStage
DAG提交任务过程
- stage 生成过程
createResultStage --> getOrCreateParentStages --> getShuffleDependencies.toList(获取到所有的父dependency) --> getOrCreateShuffleMapStage(dependency 获取Some类型Stage或ShuffleMapStage )
Spark external shuffle service
External shuffle Service是长期存在于NodeManager进程中的一个辅助服务。通过该服务来抓取shuffle数据,减少了Executor的压力,在Executor GC的时候也不会影响其他Executor的任务运行。
spark dataset和dataframe的区别
在spark2中 DataFrame = DataSet[Row]
- DataSet是有明确类型定义的数据集合,在scala中可以用case class表示
- DataSet静态类型和运行时类型检查,语法,分析错误在编译阶段就会抛出,DataFrame语法错误,会在编译阶段报错,分析错误(字段不存在)则运行时才能抛出。 单纯的sql则均需要在运行时才能抛出。
- DataFrame和DataSet会经过 catalyst优化,生成逻辑和物理执行计划,查询会效率高。
- 假如你需要丰富的语义,对半结构化数据处理,filter map sum aggregation等,注重类型检查,python, 使用tungsten等就使用DataFrame和DataSet吧
Spark-sql知识点
- spark2.0 以sparksesseion为统一的入口
- import spark.implicits._ 导入可以将RDD转化为DataFrame
- spark2.0 可以完整的hive支持,udf等,不需要额外的配置
- df.createOrReplaceTempView(“people”) 注册临时视图,作用域时会话级别
- df.createGlobalTempView(“people”) 注册全局视图,作用域时整个spark application
- 可以通过继承UserDefinedAggregateFunction[非类型安全] 实现自定义的聚合函数
- 可以通过继承Aggregator实现[类型安全]的自定义聚合函数
- df = spark.read.format(“json”).read(“xxx”) 可以指定 json parquet jdbc orc csv text libsvm
- spark sql调优 设置spark.sql.shuffle.partitions默认200
- spark动态资源最小 spark.dynamicAllocation.minExecutors=1
- spark动态资源最大 spark.dynamicAllocation.maxExecutors=1000
- spark thrift server和hivesever2其实一样,都依赖于hive的metastore
- User defined aggregation functions (UDAF)
- Apache Arrow 作为标准数据交换格式,不要再序列化和反序列化,设计时采用适配器模式,适配各个大数据组件,列式存储,基于内存
spark sql不支持hive的部分
- 不支持bucket
- hive可以小文件合并,sparksql不支持
- 仅元数据查询,sparksql也会启动任务去执行
- udf 重写的子类中部分函数没有实际意义,部分UDF不兼容
structed streaming
- Structed Streaming是基于Spark sql 引擎构建的
- 流批统一都可以基于sparksql
- 丰富的语言支持,scala java python r
- 快速 可扩展 容错,端到端的exectly-once
- Structed Streaming 可以实现端到端的100ms的exactly-once。spark2.3引入了“Continuous Processing” 可以实现低至1ms的at least once
- structedstreaming的核心思想就是将实时流看作是连续追加的表,datastream as an unbounded table
- structed Streaming 输出模式output mode有三种,complete mode, append mode, update mode
- strutted streaming 处理eventtime和延迟数据
- Structed streaming 可以实现端到端的exactly-once,必须source是可以回放,sink是幂等的
- structed Streaming 可以实现sechma推导,也可以实现分区发现
- 窗口聚合函数,watermarker到12:11时才会就算12:00-12:10的窗口结果
- spark2.0,开始支持了动态流和静态流join
- spark2.3 开始支持动态流和动态流的join,output 模式只能是append,不能在join之前使用聚合函数
- spark structed streaming 在双流join时会以慢的watermark为全局watermark ,在2.4版本中可以通过参数spark.sql.streaming.multipleWatermarkPolicy 设置max或min(默认),设置为max之后,慢的流数据可能会被丢弃
Structed Streaming清理窗口状态的几个条件:
1.output mode必须是append 或 update
2.聚合必须有event-time列
3.聚合列必须和窗口eventTime是同一个字段, 错例:df.watermark(“time”,“1 min”).groupby(“time2”).count
4.watermarker函数需要在聚合函数之前被调用
Spark shuffle
spark shuffle 需要消耗大量的网络IO,序列化反序列化,读取数据磁盘IO,Cpu
- 通过使用reduceByKey 替换groupbykey
- 用broadcast+filter替换join
- Mapvalues替换map
- 先去重在合并
- hashshuffle
SortShuffleManager
普通运行机制:每个task只产生一个文件
bypass机制:每个task只产生一个文件,少了排序过程
参数配置:
shuffle write 缓冲区,shuffle read 缓冲区
shuffle io retrywait,可以增大 增加shuffle的稳定性
spark.shuffle.memoryFraction 默认0.2,shuffle read用于聚合的内存比例
spark.shuffle.consolidatefiles 默认为false,在hashShuffleManager时设置为true会开启shuffle write 文件的合并
并行度调优:
spark.sql.default.parlism
spark数据倾斜的表现就是 有executor回oom cpu打满
kudu是支持随机读写,同时支持olap引擎
开始流查询任务
需要设置以下参数:
1.sink. 输入格式,类型,地址等
2.output mode 输出类型
3.唯一查询名
4.checkpoint
5.trigger interval 触发周期
输出模式
structed streaming 输出模式,append update compelete
状态操作和join有很多功能不支持
sink类型
file sink,
kafka sink,
foreach sink 自定义sink,实现close open proess方法,
cosole sink 用于调试,
memory sink 用于调试
触发器
micro-batch 默认,连续批次,上一个执行完成即开始下一个批次
fiexed-interval micro batches 固定周期微批
one-time micro batch 一次执行微批
continous with fixed checkpoint interval
流查询监控
可以使用query.lastProgress返回json类型的数据
也可以用query.status
也可以使用streaming Linstener 异步获取任务进度
容错保证
使用wal + checkpoint
spark streaming
-
spark steaming 的编程模型是DStreams(代表一系列的RDD)
-
spark steaming 的步骤
new sparkConf, new StramingContext, lines = new DStream, words = lines.flatMap(_.split(" ")), pairs = words.map(word => (word,1)), result = pairs.reduceByKey( _ + _ ) result.print ssc.start ssc.awaitTremination
-
一个Jvm中只能有一个StreamContext
-
spark streaming source 有两类,1.基本输入,文件(不需要receiver,文件的修改是会被忽略的,只检测路径下的新文件) 网络 2.外部数据源,kafka flume
-
spark streaming 本地模式 local[x] x需要大于1 ,不然只会运行receiver进程
-
sparkstreaming 状态操作步骤 1.定义状态 2.定义状态更新函数
-
调用updateStateByKey需要配置checkpoint
-
sparkstreaming 的操作可以分为:算子类,窗口操作类,join
-
saveAsTextfile saveasobjectfile saveashadoopfile ,当然也可以用foreachaRDD(func)自定义输出
-
使用foreachrdd时一定要在内部调用rdd.foreachPartition
-
sparkstreaming 可以和 Dataframe 结合,注册临时表createOrReplaceTempView,使用spl语句查询
-
DStrams和RDD类似,开发者可以对其进行持久化, presist()
-
checkpoint 需要借助外界可靠存储,比如hdfs s3等 ,通过streamingContext.checkpoint(path), 可以由getOrCreate方法获得scc,间隔周期不能太小,会影响吞吐量,建议设置为滑动窗口周期的5-10倍,时间是批次间隔的倍数。checkpoint的数据有两类 1 元数据(配置,算子操作,未完成的批次)2.数据checkpoint(比如中间计算的状态RDD)
-
broadcast和accumulators的状态在checkpoint恢复时不能重新实例化,需要在编码时创建单例以便在任务重启时进行实例化
-
spark steaming 任务部署
1.打包 2.executors内存配置 3.配置checkpoint 4.配置wal,不过会影响吞吐 5.设置最大接受速率 spark.straming.receiver.maxRate ,对于kafka direct模式使用参数 spark.streaming.kafka.maxRatePerPartition .在spark 1.5版本后,引入了spark.streaming.backpressure.enable=true启动背压。消费速率会自动计算
-
spark streaming 任务监控
1.RestApi 2.继承StreamingListener 3.重要参数 processing time(批次处理时间) 和 scheduler delay (处理延迟)
-
sparkstreaming 任务调优
- 减少批次的执行时间
- 首先不要让数据接受receiver成为瓶颈。可以并行多个receiver,提高吞吐量。
- 接收器的块间隔,接收的数据会在存储在spark内存之前合并为数据块。任务数=批次间隔/块间隔 所以要尽量的减少块间隔时间,但是推荐不少于50ms
- 对于多个receiver的情况,可以调用repartition方法 对数据进行重新分区
- 计算的并行度 可以通过spark.default.parllsiem设置,默认是rdd的分区数
- 数据的反序列化和序列化会带来很大的cpu消耗,第一种。通过receiver接收到的数据默认是StorageLevel.Memory_and_disk_2即数据被反序列化存储以减少cpu消耗,而且为了容错,在内存不足时会将数据保存在磁盘。第二种。因为有窗口这类操作,数据会多次处理,所以默认时以StogeLevel.memory_only_ser即序列化方式存储的,和spark core的 memory 不同。序列化的保存方式更耗cpu。综上所述。使用kryo可以减少内存和cpu消耗
-
设置合理的批次时间
- 可以通过延迟时间来测试批次时间设置是否合理
-
设置合理的内存
- sparkstreaming 程序所使用的算子类很大程度决定了内存的使用量,比如window,updateStateByKey等就会需要大量内存,比如map,filter就需要少量内存
- receiver一般会将数据进行序列化,StrogeLevel.memory_disk_ser_2,如果内存不够会进行写磁盘,这会耗费很大的性能。所以receiver需要配置足够的内存
- gc 通过gc的设置,不希望流计算任务因为gc有很大的暂停
- 减少gc 1 启用kryo减少rdd的内存和cpu,也可以使用spark.rdd.compress 压缩rdd,减少了内存但是会增加cpu
- 清除旧数据 streamingContext.remeber()设置
- 建议使用cms垃圾回收器
- 其他 比如使用堆外内存,更多的executor减少GC
- 减少批次的执行时间
-
需要记住的点:
- receiver接收到数据后,每隔blockinterval会生成一个block, blockmanager会将block分发给其他executor
- 每一个block interval意味着创建一个RDD分区,每个分区对应一个Task
- blockinterval 调大意味着 每个block的数据更多。spark.locality.wait调大会增加本地处理数据块的机会,需要在两个参数之间做平衡
- 可以使用dataStrem.repartition进行重新分区
- 如果批处理时间超过了批间隔时间,会导致receiver内存溢出,抛出blocknotfoundexecption,目前没有好的暂停办法,使用spark.streaming.receiver.maxRate来限制接收的速率
-
容错
- rdd是一个不可变的,确定的,可重复计算的不可变数据集,worker故障导致的rdd分区数据丢失,可以通过lineage重新计算。不论什么错误spark都可以保证结果的最终正确
- 三种容错机制 at least once,at most once,exactly once
- 接收端:在spark1.3后使用direct api可以和Kafka实现exectly-oncea语义
- 输出端:支持幂等更新,事物更新
spark 配置
- local[2]代表最小并行度
- 动态加载spark配置 spark-submit --name --master --conf --conf 可以使用多个conf
- SparkConf设置的参数优先级高于spark-submit,高于conf/spark-defaults
- spark 参数分为两类 程序属性和运行时参数
spark 调优
由于spark基于内存计算的特性,在集群中执行的任务,cpu、内存、网络io都可能成为瓶颈,假如数据适合存储在内存中,那么瓶颈就是网络宽带,但还是需要一些调优。比如将rdd以序列化形式存储以减少内存。spark优化主要是两个方面,1.数据序列化,对于网络io至关重要 2.内存
数据序列化
- spark默认使用java 序列化框架,kryo序列化是java的10倍,但是kryo需要你提前注册
内存调优
三个方面:对象占用内存,访问对象,以及垃圾回收。通常情况下Java对象访问很快,但是与原始数据相比会膨胀2-5倍
数据膨胀的几种原因:
- 每个对象都有一个对象头(object header 对象类,指针等)占16字节,比一般的int类型实际值都大
- 字符串一般会有额外40字节的开销(存储为char,并预留一定的空间)而且内部使用utf16存储,会每个字符占两个字节,所以一个10字节的字符串轻松占用60字节
- 公共集合类,比如map.entry不仅有对象占用还有指向下一对象的指针占用
- 还有java的自动装箱
内存管理:
spark内存主要分为执行execution和存储storage,在有必要情况下,执行会驱逐存储从而占用其内存,以下参数不建议改动
- spark.memory.fraction 默认0.6 表示60%用于计算,剩下的用于用书数据结构存储等
- spark.memory.storageFraction 默认0.5 表示数据存储占用的内存比例
如何确认内存消耗
在spark的webui处的storage处查看rdd占用的内存。
调整数据结构
减少内存的有效方式是,避免java类型带来的开销
- 设计java类型优先使用基本类型和数组,避免使用集合类如hashmap
- 避免使用包含很多小对象的嵌套结构
- 数字或枚举替换string
- 当jvm小于32g 使用UseCompressedOops压缩对象指针(8字节–>4字节)
序列化RDD储存
减少内存的另一有效方法是序列化存储rdd,但是要进行反序列化操作会导致耗时增加,所以建议使用kryo(比java序列化占用更小)
垃圾回收优化
gc优化,首先是使用更小的数据结构,或者是序列化
- 设置gc打印参数 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps ,注意gc是记录在work节点上的
- spark gc的目的是保证长时间存活的对象存储在old区,短期对象存储在young区,减少full gc
- 查看gc日志是否有大量的full gc
- 如果日志中有大量的minor gc,而很少的major gc ,可以通过-Xmn设置为eden区的4/3倍
- 如果old区接近满,可以调低spark.memory.fraction减少缓存在内存中的数据量。或者减少young区的大小,即调整NewRatio参数,默认是2,即young:old=1:2,可以调到3或4,即old占比3/4或4/5
- 尝试使用g1垃圾回收器,调整XX:G1HeapRegionSize
- 可以在spark任务重指定 spark.executor.extraJavaOption 设置jvm相关参数
其他因素
-
并行度 :spark任务并行度也很重要,1.有些算子本来就可以设置并行度 比如reduceByKey等 2.通过设置spark.default.parallesiem 一般建议每个cpu有2-3个任务
-
reduceTask的内存使用 :通常得到一个oom错误不是因为rdd不适合内存,而是因为执行shuffle类算子,比如sortByKey reduceByKey join groupByKey 等,每个人任务需要一个hashmap来存储分组数据,最简单的办法是增加任务的并行度。减少每个task处理的数据量
-
使用广播变量broatcast可以减少序列化,以及spark任务的启动开销等
-
数据本地化
- process_local 同一jvm
- node_local 同一机器
- no_pref 没有位置便好
- rack_local 同一机架
- any data 不同机架其他位置
数据本地化的两个选择:
+ 等到同一节点的任务释放
+ 立即在其他节点启动任务
spark 通常会等待同节点任务释放,但是超时后会启动任务到空闲cpu,数据拷贝,可以通过spark.locality设置数据本地化相关参数
spark 任务调度
spark资源分配最简单的是静态资源分配,应用程序可以占用最大的资源并且占有。一般有三种部署模式。
stand-alone,yarn,mesos
###动态资源分配
动态资源分配,可以根据工作负载动态调整资源,在任务不需要时回收,需要时重新申请。
- 开启动态资源 spark.dynamicAllocation.enable=true,spark.shuffle.service.enable=enable。shuffle service的目的是允许删除程序但是不删除他们写入的文件。
资源分配策略
spark任务没有确定的方法知道一个ecexutor在删除时将来会有任务执行,或者一个新增的ecexutor时空闲的,所以需要启发式来确定何时删除和新增executor
申请策略:每隔spark.dynamicAllocation.schedulerBacklogTimeout(默认1s)会触发轮询请求executor,每次呈指数级增长 1,2,4,8
删除策略:删除策略比较简单 超过了 spark.dynmicAlloaction.executorIdleTimeout就会被回收,一般如果还有待执行的任务时,不会有executor超时待删除
spark executor执行完成shuffle 写任务后,还需要充当下一读数据任务的服务,所以假如此时executor被回收,任务需要重新计算。所以引入了external shuffle service 一个常驻的进程,用户任务之间获取数据文件。
任务执行完成后会保存缓存数据,默认情况下是不会删除这些executor的,未来版本可以将其保存在堆外内存中。
任务内调度
spark 单个任务内部以FIFO调度任务,每个任务分为stage,位于队列头部的任务优先开始执行,假如资源够用则后续的任务也会立即执行
在spark 0.8之后可以配置调度策略为fair ,即小任务可以立即获得资源开始执行。
通过参数spark.scheduler.mode=fair 设置,也可以使用fair调度池
集群模式概览
spark应用程序作为一个独立的进程集允许在集群上,sparkContext称为driver,作为程序的协调器
注意几点:
- 每个spark application都有自己的executor,且相互之间进程隔离
- spark 不依赖底层cluster manager ,不管是yarn mesos
- spark driver需要接受来自executor的连接,所以driver对于executor必须是可连接的
- spark driver最好和executor运行在一个局域网内
spark任务提交
spark-submit 可以提交所以的 spark应用。可以使用assembly打包,但是需要将spark和hadoop的依赖设置为provided 因为他们在运行时由集群提供。
–supervise 可以在appmaster执行失败时重新执行,yarn模式不生效
spark 源码分析
spark-submit 执行返回的cmd为
/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/java -cp /Users/liyuhuan/tools/spark/conf/:/Users/liyuhuan/tools/spark/jars/* -Xmx1g org.apache.spark.deploy.SparkSubmit --master local[2] --class org.apache.spark.examples.SparkPi ../examples/jars/spark-examples_2.11-2.1.0.jar 100