spark的调优
- 开发的调优
- 避免创建重复的RDD:如果需要对同一个文件进行多次计算最好是只读一次
- 尽可能复用同一个RDD
- 对多次使用的RDD进行持久化cache 和persist
- 尽量避免使用shuffle类的算子
- shuffle操作有一个特点就是上一个阶段的操作执行完下一个阶段才能执行
- reduceBykey,sortBy,distinct,groupBy
- 使用map-side(combiner)预聚合的shuffle操作在使用shuffle 的算子的时候,如果有预聚合的话shuffle的代价要小很多
- shuffle有数据倾斜
- 避免数据倾斜的程度或者避免数据倾斜
- 没有map-side的算子groupBykey,coGroup
- 有map-side的算子reduceBykey,combineBykey,aggrageBukey
- 使用高性能的算子
- 使用reduceByKey/aggregateByKey替代groupByKey
- 使用mapPartitions替代普通map
- 使用foreachPartitions替代foreach
- 使用filter之后进行coalesce操作
- 使用repartitionAndSortWithinPartitions替代repartition与sort类操作
- 一个操作能针对partition就不要针对单个元素
- 广播大变量
- 广播的数据量越大,进行广播的收益越大
- 使用Kryo优化序列化性能
-
这个是spark的序列化默认情况下使用的是java的序列化
-
java的序列化传输的时候会带有对象的类型只适合少量的和不同的对象进行序列化
-
使用以下两句代码实现序列化KryoSerializer
// 设置序列化器为KryoSerializer。 sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") // 注册要序列化的自定义类型。 sparkConf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
-
- 优化数据结构java中有三种数据比较耗费内存
- 字符串,对象,集合
- 尽量使用字符串替代对象
- 使用原始类型(比如Int、Long)替代字符串
- 使用数组替代集合类型
- 资源的调优
-
spark的应用程序的进程有两种:
- driver 主控程序必须保证不能出错,而且只有一个。
- executor task的载体数量有很多 所以侧重点在executor的内存的管理
-
spark程序帮助我们把应用程序执行构成当中所占的内存分为两个方面:
- 执行内存 这个是必须的
- 存储内存 这个是可有可无的
-
spark的内存的一个大致的整体划分(spark的所能利用的两个区域):
- JVM内部的堆内内存
- JVM外部内存/操作系统的内存
- 这两个区域有分为两个部分:执行内存和存储内存
-
spark的内存管理的两种方式
- spark1.x的静态内存管理模型 StaticMemoryManager 执行内存和存储内存相互之间不能占用 静态模型中的所有的区域
的内存都是固定不能变的,如需要调整只能通过参数进行调整 - soark2.x的统一内存管理模型 UnifiedMemoryManager 执行内存和存储内存相互之间能占用 动态占用机制
- execution内存不够用时候,可以去storage申请使用 execution占用了storage内存是不会归还的
- storage内存不够用的时候也可以去execution申请内存使用
- 重要的方法有6个 三个申请内存 三个释放内存的
- def acquireStorageMemory(blockId: BlockId,numBytes: Long,memoryMode: MemoryMode): Boolean//申请存储内存
- def acquireUnrollMemory(blockId: BlockId,numBytes: Long,memoryMode: MemoryMode): Boolean//申请展开内存
- def acquireExecutionMemory(numBytes: Long,taskAttemptId: Long,memoryMode: MemoryMode): Long //申请执行内存
- def releaseStorageMemory(numBytes: Long, memoryMode: MemoryMode): Unit //释放存储内存
- def releaseExecutionMemory(numBytes: Long,taskAttemptId: Long,memoryMode: MemoryMode): Unit//释放执行内存
- def releaseUnrollMemory(numBytes: Long, memoryMode: MemoryMode): Unit //释放展开内存
- spark1.x的静态内存管理模型 StaticMemoryManager 执行内存和存储内存相互之间不能占用 静态模型中的所有的区域
-
详细介绍spark的内存管理模型
-
堆外内存的两个参数
- spark.memory.offHeap.enabled 是否开启堆外内存默认为false
- spark.memory.offHeap.size 堆外内存的大小为默认为0
-
正常情况下JVM中的线程是无法向操作系统申请内存的只能存JVM中申请内存使用但是现在spark的线程可以向操作系统申请内存,
并且是task共用的//资源调优的示例 ./bin/spark-submit \ --master yarn \ --deploy-mode cluster \ --num-executors 100 \ 启动的进程数 10G的数据------>10个 --executor-memory 6G \ 每个exector的内存堆内内存 --executor-cores 4 \ --driver-memory 1G \ 主控程序分配的内存 --conf spark.default.parallelism=1000 \ 如果是local则默认分区数为1,而且没有executor 如果是spark则默认分区数是2个 --conf spark.storage.memoryFraction=0.5 \ --conf spark.shuffle.memoryFraction=0.3 \
-
- shuffle的调优
- SortShuffleManager
-普通机 需要关注两点:写出数据和排序- shuffle的操作分为两种一个是 带聚合 reduceBykey 另一个数不带聚合join coGroup
- bypass机制 shuffle read task的数量小于等于spark.shuffle.sort.bypassMergeThreshold
参数的值时(默认为200),就会启用bypass机制 - bypass机制与普通机制不同在于:第一,磁盘写机制不同;第二,不会进行排序
- Shuffle write mapper阶段 上一个stage的最后结构写出
- Shuffle read reduce阶段 下一个stage的拉取和上一个stage的结果合并
- spark.shuffle.file.buffer 默认为32kb BufferedOutputStream的buffer缓冲大小
- spark.reducer.maxSizeInFlight 默认值 48m 参数用于设置shuffle read task的buffer缓冲大小
- spark.shuffle.io.maxRetries 默认值为 3 该参数就代表了可以重试的最大次数
- spark.shuffle.io.retryWait 该参数代表了每次重试拉取数据的等待间隔,默认是5s。
- spark.shuffle.memoryFraction 参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。
- spark.shuffle.manager 参数用于设置ShuffleManager的类型默认值为sort
- spark.shuffle.sort.bypassMergeThreshold 如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作
- spark.shuffle.consolidateFiles 使用HashShuffleManager,该参数有效 默认为false
- SortShuffleManager
- 数据倾斜只要是分布式计算中都存在此类的问题
- 按照hash值分区是造成数据倾斜的最大原因
- 原因:经过shuffle的数据大小不一样,但是机器的性能相差不多,所以容易造成执行速度慢
- 部分任务的执行时长消耗的时间是普通任务的2倍或者3倍
- 解决倾斜的解决方案
- 使用Hive,ETL的预处理数据
- 过滤少数导致倾斜的key 使用的前提是对计算的影响并不大
- 提高shuffle操作的并行度相当于增加reduceTask的数量基本上可以缓解数据倾斜的情况
- 两阶段聚合(局部聚合+全局聚合)
- 将reduce join转为map join
- 采样倾斜key并分拆join操作
- 使用随机前缀和扩容RDD进行join 原来在一个task完成的join操作,现在通过增加新的连接条件可以实现多个task并行完成连接