Spark优化

Spark优化

一,资源调优

1.1 在部署spark集群中指定资源分配的默认参数

在spark安装包的conf下 spark-env.sh

SPARK_WORKER_CORES 每台worker节点分配的最大核数

SPARK_WORKER_MEMORY 每台worker节点分配的最大内存

SPARK_WORKER_INSTANCES 每台节点上启动的Worker数量

1.2 在提交Application的时候给当前的Application任务分配响应的资源命令
  • 提交Application的时候使用选项

–executors-cores 指定每个executors 占用的核数

–executor-memory 指定每个Executor 占用的内存

–total-executors-cores 指定提交的Application占用的 核数

  • 配置信息(Application的代码中设置或在Spark-default,conf 中设置)

spark.executor.cores

spark.executor.memory

spark.max.cores

  • 动态分配资源
spark.shuffle.service.enabled   true   //启动 External  shuffle  Service 服务

spark.shuffle.service.port   7337   //Shuffle  Service服务端口,必须和yarn-site中的一致

spark.dynamicAllocation.enabled   true   //开启动态资源分配

spark.dynamicAllocation.minExecutors  1    //每个Application最小分配的 executors 数

spark.dynamicAllocation.maxExecutors  30   //每个Application  最大并发分配的executors数

spark.dynamicAllocation.schedulerBackLogTimeout  1s   

spark.dynamicAllcation.sustainedSchedulerBacklogTimeout   5s

二,并行度调优

  • 如果读取的数据在HDFS,降低了block大小,相当于提高了RDD中的partition个数
sc.textFile("path",numPartitions)
sc.parallelize("path",numPartitions)
sc.makeRDD("path",numPartitions)
repartitions / coalesce
reduceByKey / groupByKey / join --- (xxx,numPartitions)
spark.default.parallelism  numPartitions
spark.sql.shuffle.partitions  200
  • 自定义分区器
package com.spark.spark.test;

import org.apache.spark.Partitioner;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function2;
import scala.Tuple2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class ParTionerTest {

    public static void main(String[] args) {

        SparkConf sparkConf = new SparkConf().setMaster("local").setAppName("partioner");

        JavaSparkContext sc = new JavaSparkContext(sparkConf);

        JavaPairRDD<String, String> javaPairRDD = sc.parallelizePairs(Arrays.asList(
                new Tuple2<>("1", "张三"),
                new Tuple2<>("2", "李四"),
                new Tuple2<>("3", "王五"),
                new Tuple2<>("4", "小明"),
                new Tuple2<>("5", "小红")
        ));




        javaPairRDD.mapPartitionsWithIndex(new Function2<Integer, Iterator<Tuple2<String, String>>, Iterator<String>>() {

            @Override
            public Iterator<String> call(Integer integer, Iterator<Tuple2<String, String>> tuple2Iterator) throws Exception {
                List<String> list = new ArrayList<>();
                while(tuple2Iterator.hasNext()){
                    System.out.println("index: " + integer + " value" + tuple2Iterator.next());
                }
                return list.iterator();
            }
        },true).collect();


        JavaPairRDD<String, String> partitionByPairRDD = javaPairRDD.partitionBy(new Partitioner() {
            /**
             * 定义分区规则
             * @param key
             * @return
             */
            @Override
            public int getPartition(Object key) {

                Integer value = Integer.parseInt((String)key);

                return value % 3;
            }

            /**
             * 定义分成几个区
             *
             * @return
             */
            @Override
            public int numPartitions() {
                return 3;
            }
        });

        partitionByPairRDD.mapPartitionsWithIndex(new Function2<Integer, Iterator<Tuple2<String, String>>, Iterator<String>>() {

            @Override
            public Iterator<String> call(Integer integer, Iterator<Tuple2<String, String>> tuple2Iterator) throws Exception {
                List<String> list = new ArrayList<>();
                while(tuple2Iterator.hasNext()){
                    System.out.println("index: " + integer + " value" + tuple2Iterator.next());
                }
                return list.iterator();
            }
        },true).collect();

    }
}
  • 如果读取数据是在SparkStreaming中的,数据来源是kafka时

receiver模式:task的并行度,取决于 blockInterval 的间隔时间,默认是200ms,一般建议不低于 50ms

direct模式:取决于kafka中topic的分区数

三,代码调优

3.1 避免创建重复的RDD
val rdd1 = sc.textFile(path)
val rdd2 = sc.textFile(path)
//这就是创建了重复的 RDD
//有什么问题? 对于执行性能来说没有问题,但是呢,代码乱
3.2 复用同一个RDD
val rdd1 = RDD<String,String>
val rdd2 = rdd.map(_._2)
//这样的话 rdd2 是 rdd1 的子集。rdd2 执行了一个操作 filter 
rdd2.filter() = rdd1.map((_._2)).filter()
3.3 对多次使用的RDD进行持久化

如果选择一种最合适的持久化策略?

  • 默认情况下,性能最高的是 Memory_only ,但前提是你的内存足够大,可以放下整个RDD的所有数据,因为不进行序列化和反序列化的操作,避免了这部分的性能开销,对后续算子的操作,都是基于纯内存中的数据的操作,不需要从磁盘中读取,性能更高。在实际中,能用这种策略的场景有限的,如果数据比较多时,内存受限制。

  • Memory_only_Ser级别。该级别会将RDD数据序列化之后保存到内存中,此时每个partition仅仅是一个字节数组而已,大大减少了对象数量,并降低了内存占用。这种级别比Memory——only级别多出来的性能开销,主要是序列化和反序列化的开销。但是后续算子可以基于纯内存进行操作,因此性能总体还是比较高的。此外,如果是RDD数据量过多的话,还是会导致OOM内存溢出的异常。

  • Memory_AND_DISK_Ser策略。该策略会优先把数据缓存在内存中,内存缓存不下才会写入磁盘。同时会对数据进行序列化,这样数据大小会降低。

  • 通常不建议使用Disk_Only 和后缀 为_2的级别:因为完全基于磁盘文件进行数据的读写,会导致性能的急剧降低,有时还不如重新计算一次RDD数据。后缀为 _2 的级别,必须将所有数据都复制一份副本,并发送到其他节点上,数据复制以及网络传输会导致较大的性能开销,除非是要求作业的高可用性,否则不建议使用。

持久化算子:

cache : Memory_Only

persist: Memory_Only Memory_Only_Ser Memory_And_Disk_ser 一般不选择带 _2的级别

checkpoint :

1.如果一个RDD的计算时间比较长或者计算起来比较复杂,一般将这个RDD的计算结果保存到HDFS上,这样数据会更加安全。

2.如果一个RDD的依赖关系非常长,也会使用checkpoint,切断依赖关系,提高容错的效率。

3.4 尽量避免使用shuffle类的算子

使用广播变量来模拟使用Join的使用情况,一个RDD比较大,一个RDD比较小。

join算子= 广播变量+filter 广播变量+map 广播变量+flatmap

3.5 使用map-side 预聚合的shuffle操作

即尽量使用有 combiner 的shuffle 类算子

combiner概念:在map端,每一个 map task计算完毕后进行的局部聚合操作

combiner好处:

1.降低shuffle write写磁盘的数据量

2.降低shuffle read 读磁盘的数据量

3.减少了reduce task聚合的次数

有combiner的 shuffle类算子:

  1. reduceByKey这个算子在map端是由combiner的,在一些场景中可以使用reduceByKey代替 groupByKey
  2. aggregateByKey --> 传入三个参数,初始值,匿名函数(map端聚合逻辑),匿名函数(reduce端聚合逻辑)
  3. combineByKey --> 传入三个参数,匿名函数(对传入的第一个值操作的函数),匿名函数(map端聚合逻辑),匿名函数(reduce端聚合逻辑)
3.6 尽量使用高性能的算子
  • reduceByKey代替 groupByKey
  • mapPartition 代替 map
  • foreachPartitons 代替 foreach
  • filter后使用 coalesce减少分区数
  • 使用repartitionAndSortWithinPartitions (shuffle过程中排序) 代替repartition And sortByKey 类操作
  • 使用repartition和coalesce算子操作分区
3.7 使用广播变量

​ 开发过程中,会遇到需要在算子函数中使用外部变量的场景(尤其是大变量,比如100M以上的集合),此时就应该使用Spark的广播(BroadCast)功能来提升性能,函数中使用到外部变量时,默认情况下,Spark会将该变量赋值多个副本,通过网络传输到task中,此时每个task都有一个变量副本,如果变量本身比较大的话,那么大量的变量副本在网络中传输的性能开销,以及在各个节点的Executor中占用过多内存导致的频繁GC,都会极大的影响性能。如果使用的外部变量比较大,建议使用spark中的广播功能,对该变量进行广播。广播后的变量,会保证每个Executor中的内存中,只驻留一份变量副本,而Executor中的task执行时共享该Executor中的那份变量副本。这样的话,可以大大减少副本的个数,也减少了网络传输的性能开销,并减少了Executor的内存的占用开销,降低了GC的频率。

​ 广播变量的发送方式:Executor一开始并没有广播到每个task中,而是在task运行中,需要用到广播变量,回去找Executor中的blockManager要,blockManager和Driver里面的Blockmanagermaster要。

​ 使用广播变量可以大大的减少集群中变量的副本数,不使用广播变量,集群中的变量副本数是和task个数一样,使用广播变量集群中的变量副本数和executor的个数相同。

3.8 使用Kryo优化序列化性能

在Spark中,主要有三个地方设计到了序列化:

1.在算子函数中使用到外部变量时,改变量会被序列化后进行网络传输

2.将自定义的类型作为RDD的泛型类型时,所有自定义类型对象,都会进行序列化。因此这种情况下,也要求自定义的类型必须实现Seriallizable接口。

3.使用可序列化的持久化策略时(Memory_Only_Ser) ,Spark 会将 RDD中的每个partition都序列化成一个大的字节数组。

Kryo序列化器介绍:

​ Spark 支持使用 Kryo 序列化机制。Kryo 序列化机制,比默认的 Java 序列化机制,速度要快,序列化后的数据要更小,大概是 Java 序列化机制的 1/10。所以 Kryo 序列化优化以后,可以让网络传输的数据变少;在集群中耗费的内存资源大大减少。
​ 对于这三种出现序列化的地方,我们都可以通过使用 Kryo 序列化类库,来优化序列化和反序列化的性能。Spark 默认使用的是 Java 的序列化机制,也就是 ObjectOutputStream/ObjectInputStream API 来进行序列化和反序列化。但是 Spark 同时支持使用 Kryo 序列化库,Kryo 序列类库的性能比 Java 序列化类库的性能要高很多。官方介绍,Kryo 序列化机制比 Java 序列化机制,性能高 10 倍左右。

​ Spark 之所以默认没有使用Kryo 作为序列化类库,是因为 Kryo 要求最好要注册所有需要进行序列化的自定义类型,因此对于开发者来说,这种方式比较麻烦。

Sparkconf.set("spark.serializer", 
"org.apache.spark.serializer.KryoSerializer")
.registerKryoClasses(new Class[]{SpeedSortKey.class}) //SpeedSortkey 是自定义的一个类
3.9 优化数据结构

java中有三种类型比较消耗内存:

1.对象,每个Java对象都有对象头,引用等额外的信息,因此比较占用内存空间

2.字符串,每个字符串内部都有一个字符数组以及长度等额外信息

3.集合类型,比如HashMap,LinkedList等,因为集合类型内部通常会使用一些内部类来封装集合元素,比如Map.Entry

​ 因此 Spark 官方建议,在 Spark 编码实现中,特别是对于算子函数中的代码,尽量不要使用上述三种数据结构,尽量使用字符串替代对象,使用原始类型(比如 Int、Long)替代字符串,使用数组替代集合类型,这样尽可能地减少内存占用,从而降低 GC 频率,提升性能。

3.10 使用高性能的库 fastutil

fasteutil 介绍:

fastutil 是扩展了 Java 标准集合框架(Map、List、Set;HashMap、ArrayList、HashSet)的类库,提供了特殊类型的 map、set、list 和 queue;

fastutil 能够提供更小的内存占用,更快的存取速度;我们使用 fastutil提供的集合类,来替代自己平时使用的 JDK 的原生的 Map、List、Set,好处在于,fastutil 集合类,可以减小内存的占用,并且在进行集合的遍历、根据索引(或者 key)获取元素的值和设置元素的值的时候,提供更快的存取速度。fastutil 的每一种集合类型,都实现了对应的 Java 中的标准接口(比如 fastutil 的 map,实现了 Java 的 Map 接口),因此可以直接放入已有系统的任何代码中。fastutil 最新版本要求 Java 7 以及以上版本。

四,数据本地化

4.1 数据本地化的级别
  • Process_Local

task要计算的数据在本进程(Executor)的内存中。
在这里插入图片描述

  • Node_Local

task要计算的数据在本节点磁盘上

task要计算的数据在本节点的其他Executor进程的内存中
在这里插入图片描述

  • No_Pref

task计算的数据在关系型数据库中,如mysql
在这里插入图片描述

  • Rack_Local

task所计算的数据在同机架的不同节点的磁盘或者Executor进程中的内存中。
在这里插入图片描述

  • Any

跨机架。

4.2 Spark 数据本地化调优

​ Spark 中任务调度时,TaskScheduler 在分发之前需要依据数据的位置来分发,最好将 task 分发到数据所在的节点上,如果 TaskScheduler 分发的 task在默认 3s 依然无法执行的话,TaskScheduler 会重新发送这个 task 到相同的 Executor 中去执行,会重试 5 次,如果依然无法执行,那么 TaskScheduler会降低一级数据本地化的级别再次发送 task。如下图中,会先尝试 1,PROCESS_LOCAL 数据本地化级别,如果重试 5 次每次等待 3s,会默认这个 Executor 计算资源满了,那么会降低一级数据本地化级别到 2,NODE_LOCAL,如果还是重试 5 次每次等待 3s 还是失败,那么还是会降低一级数据本地化级别到 3,RACK_LOCAL。这样数据就会有网络传输,降低了执行效率。
在这里插入图片描述

  • 如何提高数据本地化的级别?

可以增加每次发送task的等待时间(默认是3秒),将3s 倍数调大。

spark.locality.wait
spark.locality.wait.process
spark.locality.wait.node
spark.locality.wait.rack

注意:等待时间不能调的太大。调整数据本地化的级别不要本末倒置了,虽然每一个task的本地化级别是最高了,但反而整个Applcation的执行时间变长了。

五,内存调优

这里我们说的内存调优针对的是Executor端的内存调优,因为task运行在Executor端,可能出现计算内存不够用的情况。

​ Spark JVM 调优主要是降低 gc 时间,可以修改 Executor 内存的比例参数。RDD 缓存、task 定义运行的算子函数,可能会创建很多对象,这样会占用大量的堆内存。堆内存满了之后会频繁的 GC,如果 GC 还不能够满足内存的需要的话就会报 OOM。比如一个 task 在运行的时候会创建 N 个对象,这些对象首先要放入到 JVM 年轻代中。比如在存数据的时候我们使用foreach 来将数据写入到内存,每条数据都会封装到一个对象中存入数据库
中,那么有多少条数据就会在 JVM 中创建多少个对象。

Spark中如何内存调优?

SparkExecutor堆内存中存放( 以静态内存管理来说:) :RDD的缓存数据和广播变量(60%) , shuffle聚合内存(20%) ,task的运行 (20%)

  • 提高Executor的内存(内存大了,自然20% 也跟着变大了)

  • 降低存储内存比例或者降低聚合内存比例(降低其他占用内存比例)

六,Spark Shuffle调优

  • Buffer大小(hash shuffle) – 32Kb
  • Shuffle read(sort shuffle) 拉取数据量的大小 --48M
  • Shuffle 聚合内存的比例 --20% (静态内存管理)
  • 拉取数据量重试次数 --5次
  • 重试的时间间隔
  • Spark Shuffle的种类
  • HashShuffle 合并机制
  • SortShuffle byPass机制 200次

七,调节Executor的堆外内存

​ Spark底层 shuffle 的传输方式是 netty 传输,netty在进行网络传输的过程会申请堆外内存(netty是零拷贝),所以使用了堆外内存。默认情况下,这个堆外内存上线是默认Executor的内存大小的10%,真正处理大数据的时候,这里都会出现问题,导致Spark作业反复奔溃,无法运行;此时就会去调节这个参数,则至少1G,甚至2G,4G.

​ executor 在进行 shuffle write,优先从自己本地关联的 mapOutPutWorker中获取某份数据。如果本地 mapOutPutWorkers 没有的话,那么会通过TransferService,去远程连接其他节点上 executor 的 block manager 去获取,如果本地 block manager 没有的话,那么会通过 TransferService,去远程连接其他节点上 executor 的 block manager 去获取,尝试建立远程的网络连接,并且去拉取数据。频繁创建对象让 JVM 堆内存满溢,进行垃圾回收。正好碰到那个 exeuctor 的 JVM 在垃圾回收。处于垃圾回过程中,所有的工作线程全部停止;相当于只要一旦进行垃圾回收,spark / executor 停止工作,无法提供响应,spark 默认的网络连接的超时时长是 60s;如果卡住 60s 都无法建立连接的话,那么这个 task 就失败了。task 失败了就会出现 shuffle file cannot find 的错误。那么如何调节等待的时长呢?

在 ./spark-submit 提交任务的脚本里面添加

--conf  spark.core.connection.ack.wait.timeout=300  //默认是60s

​ Executor 由于内存不足或者堆外内存不足了,挂掉了,对应的 Executor 上面的 block manager 也挂掉了,找不到对应的 shuffle map output 文件,Reducer 端不能够拉取数据。我们可以调节堆外内存的大小,如何调节?

yarn下:

--conf  spark.yarn.executor.memoryOverhead=2048 单位 M

standalone下:

--conf  spark.executor.memoryOverhead=2048  单位 M

八,解决数据倾斜

8.1 使用Hive ETL 预处理数据

方案适用场景:

​ 如果导致数据倾斜的是HIve表。如果Hive表中的数据本身很不均匀(比如说 某个key对应了100w数据,其他key才对应了10几条数据),而且业务场景需要频繁的使用Spark对Hive表执行某个操作,那么比较使用这种技术方案。

方案实现思路:

​ 此时可以评估下,是否可以通过Hive 来进行数据预处理(即 通过Hive ETL预先对数据按照 key进行聚合,或者是预先和其他表进行 join ),然后在spark作业中针对的数据源就不是原来的Hive表,而是预处理后的Hive表。此时由于数据已经运行进行过聚合或join操作了,那么在Spark作业中也就不再需要使用原先的 shuffle类算子执行这类操作了。

方案实现原理:

​ 这种方案从数据源的方向解决了数据倾斜的问题,因为彻底避免了Spark中执行shuffle类算子,那么肯定不会有数据倾斜的问题了。但是这种方式只是将数据倾斜的问题,放在了Hive层面,Hive在进行ETL操作,聚合或者join的时候,还是会出现数据倾斜,导致Hive ETL的速率很慢。

8.2 过滤少数导致数据倾斜的Key

方案使用场景:

​ 如果发现导致数据倾斜的key就少数几个,而且对计算本身的影响并不大的话,那么很适合这种方案。比如99%的key就只对应 10多条数据,但是只有 个别的几个key对应了100w数据,从而导致了数据倾斜。

方案实现思路:

​ 如果判断那少数几个数据量特别多的Key,对作业的执行和计算结果不是特别重要的话,那么干脆直接过滤调那少数的几个key。比如: 在SparkSql语句中可以直接使用 where 条件语句过滤调那些key,或者在spark core中对RDD执行 filter算子,过滤掉那些Key。如果需要每次作业执行时,动态判定哪些key的数据量特别多然后再进行过滤,那么可以使用 sample算子对RDD进行采样,然后计算出每个key的数量,取数据量最多的Key过滤掉即可。

方案实现原理:

将导致数据倾斜的key给过滤掉之后,这些key就不会参与计算了,自然不可能参数数据倾斜的情况了。

8.3 提高shuffle操作的并行度

方案实现思路:

​ 在对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量。对于SparkSql中的 shuffle 类语句,比如 group by, join 等,需要设置一个参数, 即 spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,该值默认是 200,对于很多场景来说都有点过小。

方案实现原理:

增加 shuffle read task的数量,可以让原本分配给一个task的多个key,分配给多个task,从而让每个task处理比原来更少的数据,那么自然而然每个task的执行时间都会变短了。

8.4 双重聚合

方案使用场景:

​ 对RDD进行 reduceBykey等聚合类 shuffle算子或者在 sparkSql中使用 groupBy 语句进行分组聚合时,比较使用这种方案。

方案实现思路:

​ 这个方案的核心实现思路就是进行两阶段聚合。第一次是局部聚合,先给每个 key 都打上一个随机数,比如 10 以内的随机数,此时原先一样的 key就变成不一样的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着对打上随机数后的数据,执行 reduceByKey 等聚合操作,进行局部聚合,那么局部聚合结果,就会变成了(1_hello, 2) (2_hello, 2)。然后将各个 key 的前缀给去掉,就会变成(hello,2)(hello,2),再次进行全局聚合操作,就可以得到最终结果了,比如(hello, 4)。

方案实现原理:

​ 将相同的key通过加前缀随机数的方式,变成多个不同的key,就可以让原本聚合到一个task中的数据,分配到多个task上去,然后进行一次聚合,再去掉前缀,再次聚合,就可以得到最终的结果。
在这里插入图片描述

​ 如果一个RDD中有一个Key导致的数据倾斜,同时还有其他的key,那么一般先对数据集进行抽样,然后找出倾斜的key,再使用filter对原始的RDD进行分离为两个RDD,一个是由倾斜的Key组成的RDD1,一个是由其他key组成的RDD2,那么对于RDD1可以使用随机前缀进行多分区进行聚合,对于另一个RDD2可以直接聚合计算,最后将结果再合并起来。

8.5 将reduce join 转为 map join

BroadCast + filter (或者 map )

方案使用场景:

​ 在对RDD使用Join类操作,或者是在SparkSql中使用join语句时,而且Join操作中的一个RDD或表的数据量比较小(比如几百M 或者 一两G ),比较适用此方案。

方案实现思路:

​ 不使用Join算子进行连接操作,而是用Broadcast变量与map类算子实现Join操作,进而完成规避掉shuffle类的操作,彻底避免数据倾斜的发生和出现。将较小RDD中的数据直接通过collect算子拉取到Driver端的内存中来,然后对其创建一个Broadcast变量;接着对另外一个RDD执行map类算子,在算子函数内,从 Broadcast 变量中获取较小 RDD的全量数据,与当前 RDD 的每一条数据按照连接 key 进行比对,如果连接 key 相同的话,那么就将两个 RDD 的数据用你需要的方式连接起来。

方案实现原理:

​ 普通的 join 是会走 shuffle 过程的,而一旦 shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个 RDD 是比较小的,则可以采用广播小 RDD 全量数据+map 算子来实现与 join 同样的效果,也就是 map join,此时就不会发生 shuffle 操作,也就不会发生数据倾斜。

8.6 采样倾斜key并分析join操作

方案使用场景:

​ 两个RDD/Hive表进行join的时候,如果数据量都比较大,无法采用解决方案五,那么此时可以看一下RDD/HIve中的key分布情况。如果出现数据倾斜,是因为其中某一个RDD/HIve表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有Key都分布比较均匀,那么采用这个解决方案是比较合适的。

方案实现思路:

​ 对包含少数几个数据量过大的Key的RDD,通过sample算子采样出一份样本,然后统计一下每个key的数量,计算出来数据量最大的是哪几个key.然后将这几个key对应从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上n以内的随机数做为前缀,而不会导致倾斜的大部分key形成另外一个RDD.接着将需要join的另一个RDD,也过滤出那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按照顺序附加一个0-n的前缀,不会导致倾斜的大部分key也形成另外一个RDD.再将附加了随机前缀的独立RDD和另一个膨胀了的RDD进行Join,此时就可以将原先相同的key打散成多份,分散到多个task中去进行join了。二另外两个普通的RDD就照常join即可。最后将两次Join的结果使用union算子合并起来即可,就是最终的Join结果。
在这里插入图片描述

8.7 使用随机前缀和扩容RDD进行Join

方案使用场景:

​ 如果在进行Join操作时,RDD中有大量的Key导致数据倾斜,那么进行分析Key也没什么意义,此时就只能使用最后一种解决方案来解决问题了。

方案实现思路:

​ 该方案的实现思路基本和 方案六类似,首先查看RDD/Hive表中的数据分布情况,找到哪个造成数据倾斜的RDD/Hive表,比如有多个Key都对应超过1万条数据。然后将该RDD的每条数据都打上一个n以内的随机前缀,同时对另外一个RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0-n的前缀。最后将两个处理后的RDD进行Join即可。
在这里插入图片描述

九,Spark故障解决

9.1 shuffle file cannot found

磁盘小文件找不到

  • connection timeout

提高建立连接的超时时间,或者降低gc,降低了gc,那么spark不能堆外提供服务的时间就少了,那么超时的可能就会降低。

  • fetch data fail

提高拉取数据的重试次数以及间隔时间。

  • OOM/executor lost

提高堆外内存大小,提高堆内内存大小。

9.2 reduce OOM

BlockManager拉取的数据量大,reduce task处理的数据量小

  • 降低每次拉取的数据量
  • 提高shuffle 聚合的内存比例
  • 提高Executor的内存
9.3 序列化问题
9.4 Null值问题
val rdd = rdd.map{x=>{
	x+”~”;W
}}
rdd.foreach{x=>{
	System.out.println(x.getName())
}}
  • 8
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值