Spark 开发调试技巧

Spark 部署模式简介:

  • Local

    一般就是跑在自己的本地开发机上,用于跑单元测试、学习算子的使用方式等。

  • Cluster

    • Standalone

      spark 自己负责资源的管理调度。

    • Mesos

      使用 mesos 来管理资源调度。

    • Yarn

      使用 yarn 来管理资源调度

开发和调试技巧

下面介绍的开发和调试技巧都是基于 Spark On Yarn 这种部署模式,这是现在企业常见的部署方式。

1.常用算子

spark 的算子分为2大类:TransformationsActions ,spark 操作是懒执行的,执行转换类算子时不会立即执行,只是把操作记录下来,等遇到动作类算子的时候就会提交作业真正开始执行。这样做的好处就是 spark 执行引擎可以智能优化转换类算子的执行流程,有些操作可以进行合并,提高执行效率。

下面分类列举一些开发中常用的算子(完整的算子介绍可以去看官方指导文档,文末有链接):

1.1 flatMap

map 类转换算子,遍历 rdd 对里面单个对象进行处理,返回 0个或多个处理后的对象。

与 map 算子相比:

​ map 算子只能是进去一个对象,返回一个对象,仅有遍历功能,比较局限。

​ flatMap 算子不仅有遍历和过滤(返回 0个对象)功能,还支持返回多个对象。

代码示例:

// 调用方式
Rdd.flatMapToPair(new OriginPairFlatMap());

// 算子具体实现,覆盖 call 方法,在里面写自己的业务逻辑
public class OriginPairFlatMap
    implements PairFlatMapFunction<Row, String, Object> {
    
  @Override
  public Iterator<Tuple2<String, Object>> call(Row row) {
    List<Tuple2<String, Object>> results = Lists.newArrayList();
      
    // 写自己的业务逻辑 ...

    return results.iterator();
  }
}

1.2 combineByKey

聚合类转换算子,会产生 shuffle,用于把 rdd 中的数据按 key 进行聚合。

与 groupByKey 比较:

​ combineByKey 可以自定义聚合逻辑,控制聚合后的集合大小,避免 OOM;可以在本地就进行合并,效率较高。

代码示例:

// 函数调用
JavaPairRDD<String, List<Person>> combinedRDD =
  javaPairRDD.combineByKey(
    CombineByKeyFunction.createCombinerFunction(),
    CombineByKeyFunction.createMergeValueFunction(groupThreshold),
    CombineByKeyFunction.createMergecombinerFunction(groupThreshold));

public class CombineByKeyFunction {

  // 第一个函数,创建集合,即第一次碰到这个 key 的数据时
  public static Function<Person, List<Person>> createCombinerFunction() {
    return value -> Lists.newArrayList(value);
  }

  // 第二个函数,把再次碰到的这个 key 的数据放到已有的集合里
  public static Function2<List<Person>, Person, List<Person>> createMergeValueFunction(
      int threshold) {
    return (lstValues, value) -> {
      // 控制聚合后的集合大小,避免 OOM
      if (lstValues.size() < threshold) {
        lstValues.add(value);
      }
      return lstValues;
    };
  }

  // 第三个函数,合并属于同一个 key ,但是在不同分区中的数据
  public static Function2<List<Person>, List<Person>, List<Person>> createMergecombinerFunction(
      int threshold) {
    return (allvalues, values) -> {
      if (allvalues.size() + values.size() <= threshold) {
        allvalues.addAll(values);
      }
      return allvalues;
    };
  }
}

1.3 filter

转换类算子,把 rdd 中符合要求的数据取出生成一个新的数据集。

使用 filter 算子后可能导致数据倾斜的问题,可以用 repartition 算子解决。

代码示例:

JavaRDD<String> originRdd=sparkContext.parallelize(Arrays.asList("it","is","my","life"));
JavaRDD<String> filterRdd=originRdd.filter(word->word.length()==2);

1.4 union

转换类算子,比较简单,把 2个数据集合并。比如从 hbase 的多张表里取出了数据,合并后再处理。

代码示例:

rdd1.union(rdd2);

1.5 distinct

转换类算子,对 rdd 进行去重。数据重复是个很普遍的现象,建议在读取原始数据后都调用 distinct 算子。

代码示例:

rdd.distinct()

1.6 repartition

转换类算子,用于对数据进行重新分区。

什么时候需要重新分区?

当分区个数和数据量不匹配时。partition 和 task 是一对一的关系,如果数据量过大而分区数过少,程序产生的任务个数就少了,并行度不够,无法充分利用集群的资源,影响运行速度。反之,数据量过少而分区数过多,可能程序执行的大部分时间都是在创建任务,而执行任务的时间却很短,这就反客为主了。

代码示例:

rdd.repartition(500);

1.7 saveAsTextFile

动作类算子,一般把计算好的结果存到 hdfs 上供后续使用。

代码示例:

rdd.saveAsTextFile("/xxx/yyy");

2.如何设置程序执行参数

先看一个完整的程序提交命令:

/usr/hdp/current/spark/bin/spark-submit --master yarn 
--conf spark.local.dir=/opt/tmp 
--driver-memory 2g 
--executor-memory 6G 
--conf spark.yarn.executor.memoryOverhead=2048 
--conf spark.executor.cores=3 
--conf spark.scheduler.mode=FAIR 
--conf spark.yarn.max.executor.failures=1024 
--conf spark.dynamicAllocation.enabled=true 
--conf spark.dynamicAllocation.minExecutors=2 
--conf spark.shuffle.service.enabled=true 
--queue testqueue 
--conf spark.sql.hive.convertMetastoreOrc=false 
--conf spark.sql.crossJoin.enabled=true 
--files file1 
--class com.lzy.daphnis.service.DaphnisAttributeStat /opt/test-spark/lib/daphnis-*-jar-with-dependencies.jar 

spark-submit --master yarn 指定程序执行模式为 yarn 。

spark.yarn.executor.memoryOverhead 这个参数是和 --executor-memory 一起产生作用的,比较重要,如果不设或者设得比较小,spark 程序很容易失败。原因如下:

​ --executor-memory 很容易理解,就是每个 executor 分配的内存,但是实际发现,程序在运行时很容易超过这个内存值,然后 executor 就被 shutdown 了,task 失败。memoryOverhead 就是为了解决这个情况,它允许executor 使用的内存超过设置的值一部分,这样一来 task 成功率就高了很多。

spark.dynamicAllocation.enabled 是否动态分配资源,可以作为一般配置,有 yarn 去自动分配 exexutor 资源,建议是开启的。后面还可以用别的参数调整并行度,更充分的利用集群资源。

–queue 这个和它的含义一样,指定程序运行使用的队列,程序运行的资源限制决定于队列被分配的资源。

–files 指定需要上传到集群的文件,如果执行的是 yarn cluster 模式,driver 不是当前机器,是无法读取到本地文件的,此时就需要这个参数上传到集群才能读取。

–class 指定程序运行的入口和 lib 包。

这里没有指定程序提交方式,就是默认的 yarn client。一般常用的程序提交参数就这些,有时候会根据程序业务逻辑不同再添加一些参数,比如设置默认的并行度、序列化方式等。

3.选择程序的提交模式

  • client 模式

    这个模式一般用于调试或者需要直接在程序日志里面拿到详细的程序运行信息

    client 模式会回选取当前提交程序的机器作为 driver ,也就是当前执行 spark 程序的这台机器。

    如何设置程序的提交模式 client 模式:

    ... --master yarn --deploy-mode client ...
    
  • cluster 模式

    这个模式一般用于生产环境,程序里只能拿到简单的执行信息,这个时候的程序日志基本没有什么用。如果程序出现问题,基本上只能把 executor 里的日志拿下来进行分析。

    cluster 模式会选取集群中的某一台机器成为 driver 。

    如何设置程序的提交模式为 cluster 模式:

    ... --master yarn --deploy-mode cluster ...
    

4.调试技巧

4.1 使用 collect 算子打印中间结果

有时调试问题需要打印程序的中间结果,可以使用 collect 算子把数据收集到 driver 端,然后打印到日志,如下:

List<String> tmpData=rdd.collect()
....

如果只需要取部分元素,也可以用 take 。

4.2 强制杀掉 spark 程序

先找到这个程序的 application id,程序日志里一般就有,或者从 hadoop 管理界面去找,然后执行命令:

yarn application -kill <appid>

4.3 收集所有 executor 的日志到本地

executor 里面记录了更详细的程序执行信息,甚至有些报错只能在 executor 日志里找到,因此学会这个技能很重要。如下:

yarn logs -applicationId <appid> > executors.log

4.4 观察程序执行情况

写出一个 spark 程序其实不难,熟悉了各个算子后,进行一些组合就能实现业务逻辑,但是程序能否抗住大数据量的检验,这个需要做的事情就比较多了。

即使使用 yarn client 模式,在日志中能看到的信息也比较有限。其实 hadoop 有提供 spark 程序的运行管理界面,一般是集群 ResourceManager 的 IP 加上 8088 端口就可以打开管理界面。当然,某些大数据平台厂商会修改这个端口号,但一般能在集群管理界面找到 RM 管理界面的入口。

打开程序管理界面后,注意观察 StageExecutor 这两个界面,可以找到程序执行慢的地方,卡住或者崩溃的地方。观察 executor 的数量也能知道自己程序的并行度够不够。

4.5 打印额外的程序信息

在提交程序的参数里面加上这个参数:

--verbose

可以打印出 spark 程序执行的一些额外信息,比如依赖的 jar 包路径,曾经利用这个参数解决了一个很难弄的 jar 包冲突的问题。

5.常见问题和解决方案

5.1 OOM 原因分析和解决

科普下 OOM 就是常见的 OutOfMemoryError 错误。

那么 spark 程序在什么情况下容易产生 OOM 呢?

  • mapPartition 算子

    map 算子的加强版吧,以分区为单位遍历 rdd 中的数据。但是当某个分区数据特别多的时候,就极易产生 OOM 。

    解决办法:可以换成 map 算子,牺牲一些性能,但是程序能跑过;或者解决数据倾斜的问题,保证没有哪个分区数据量会特别大。

  • 根据 key 进行分组的算子

    比如 groupByKey,combineByKey。

    在对数据进行分组时,会产生 shuffle ,如果某个分区流入过多的数据,自然就 OOM 了。

    groupByKey 只能是解决数据不均衡的问题,而 combineByKey 可以控制分区中的数据量,也就可以避免 OOM 了。

  • spark sql 执行 join 操作

    这个一般也是 shuffle 导致的 OOM 。

    解决的话,如果是大小表,可以把小表的数据广播到 executor ,在 map 的时候做 join。否则可以尝试调大 spark.sql.shuffle.partitions 参数,增加程序的并行度。

  • 没有正确设置 memoryOverhead 参数

    spark 程序在执行时很容易达到 executor 的内存限制,memoryOverhead 没设置或者设得比较小,就直接 OOM 了。memoryOverhead 可以设置 executor 使用一部分额外的内存,设置得稍微大一些,就不容易出现 OOM 了。

5.2 SOF 原因分析和解决

科普下 SOF 就是常见的 StackOverFlowError 错误。spark 程序报这个错误的原因和 java 程序是一样的,调用链过长导致栈内存不足,进而引发异常。

那么 spark 又是如何出现调用链过长的呢?

  • 程序里面有很多的转换类算子

    spark 是懒执行的,会先把操作记录下来,如果一直碰不到动作类算子,就有可能导致调用链过长,进而 SOF。

    解决:可以在中间调用动作算子 persist 或者 checkpoint 触发计算。

  • 在大表上执行复杂的 spark sql

    为什么是大表,spark 调用链的长度似乎跟数据量是有关系的。同一条 sql ,小表没有问题,大表就会 SOF ,这个具体原因,后面再详细研究下。

    解决:最直接的就是优化 sql ;其次把 sql 中需要做的部分操作移到 spark 程序中去做。

5.3 Spark 程序执行慢

决定 spark 程序执行速度的核心因素是数据量并行度。比如有 500亿数据,用 3台机器的集群去处理,这个无论如何快不起来的。

这里提供几个程序执行慢的具体排查解决思路:

  • 是否是数据量过大,集群处理能力不足

    这种情况,建议分批处理数据,否则很可能程序跑不过。

  • 是不是自己的程序并行度不够,没有充分利用集群的资源

    这个可以观察集群的资源占用情况,如果一直很低就有问题了。

    可以在程序提交参数里面设置默认并行度。

  • 是不是程序所使用的队列资源分配有问题

    这个可能是集群管理员给队列分配的资源少了,加资源就可以了。

  • 是不是程序中有多处 shuffle 操作

    shuffle 操作很耗时间和内存,当内存不够时,还会溢写磁盘,这个就更慢了。

    尽量简化程序,去掉不必要的 shuffle 操作。另外还有一些针对 shuffle 调优的参数可以试一下。

  • 是不是存在大量 executor 挂掉,真实执行任务的只有几个 executor 的情况

    程序写得有问题,最后就剩几个 executor 在工作,自然就慢了。

参考资料

  1. Spark 官方文档-算子介绍
  2. Spark 官方文档-yarn 参数
  3. spark task、job、partition之间的关系 宽窄依赖 spark任务调度
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 大数据开发工程师系列是指专门从事大数据开发的一类职业。Hadoop和Spark是大数据领域中最受欢迎的两个开源框架。 Hadoop是一个分布式计算框架,用于处理大规模数据集的分布式存储和计算。Hadoop的核心是Hadoop分布式文件系统(HDFS)和MapReduce计算模型。HDFS将数据分布存储在集群的多个节点上,MapReduce可以并行处理这些分布式数据。Hadoop具有高可靠性、高扩展性和高容错性的特点,并且还提供了许多其他工具和库,如Hive、Pig和HBase等。 Spark是一个快速、通用的大数据处理引擎,可以在多种数据源上进行高效的分布式计算。相比于Hadoop的MapReduce,Spark具有更快的数据处理速度和更强的扩展性。Spark提供了一个称为弹性分布式数据集(RDD)的抽象,可以在内存中高效地处理大规模数据集。此外,Spark还提供了许多高级组件和库,如Spark SQL、Spark Streaming和MLlib等,用于处理结构化数据、流式数据和机器学习。 作为大数据开发工程师,掌握Hadoop和Spark是非常重要的。使用Hadoop可以处理海量数据,并且具有高可靠性和容错性。而Spark则能够快速高效地处理大规模数据,并提供了更多的数据处理和分析功能。 大数据开发工程师需要熟悉Hadoop和Spark的使用和调优技巧,以及相关的编程语言和工具,如Java、Scala和Python。他们需要了解数据处理的算法和模型,并能够设计和实现高效的分布式计算方案。此外,大数据开发工程师还需要具备良好的沟通能力和团队合作能力,能够与数据科学家和业务团队紧密合作,共同解决实际问题。 总之,大数据开发工程师系列是一个专门从事大数据开发的职业群体。而Hadoop和Spark则是这个职业群体中最重要的两个工具,他们分别用于大规模数据处理和分布式计算。掌握Hadoop和Spark的使用和优化技巧,是成为一名优秀的大数据开发工程师的关键能力。 ### 回答2: 大数据开发工程师系列主要涉及到两个重要的技术:Hadoop和Spark。 Hadoop是一个开源的分布式计算框架,主要用于存储和处理大规模数据集。它通过将数据分散存储在集群中的多个节点上,并在节点之间进行数据通信和计算,实现了数据的并行处理和高可靠性。Hadoop的核心工具是HDFS(Hadoop分布式文件系统)和MapReduce(一种用于分布式计算的编程模型)。HDFS用于将数据分布式存储在集群中,而MapReduce则是用于分布式计算的框架,通过将计算任务分解成多个小任务并在各个节点上并行执行,大大提高了数据处理的效率和性能。 Spark是当前最受欢迎的大数据计算框架之一,也是一个开源项目。与Hadoop相比,Spark具有更快的数据处理速度和更强大的功能。Spark提供了一个可扩展的分布式数据处理框架,支持数据处理、机器学习、图计算等多种大数据应用场景。与传统的基于磁盘的计算框架相比,Spark利用内存计算的优势,可以快速地对大规模数据进行处理和分析。此外,Spark还提供了丰富的API和开发工具,使开发人员可以更轻松地构建和调试大数据应用程序。 作为大数据开发工程师,掌握Hadoop和Spark是必不可少的。熟悉Hadoop的使用和原理,能够有效地存储和处理大规模数据集。而对于Spark的掌握,则可以提高数据处理的速度和效率,使得大数据分析和挖掘更加容易实现。因此,大数据开发工程师需要具备对Hadoop和Spark的深入理解和熟练应用,同时还需要具备数据分析、算法和编程等多方面的技能,以应对复杂的大数据挑战。 ### 回答3: 大数据开发工程师是一个专注于处理大数据的职位,主要负责使用各种工具和技术来处理和分析大规模的数据集。 Hadoop和Spark是目前在大数据处理领域中非常流行的两个开源工具。Hadoop是一个分布式系统基础架构,可以在集群中存储和处理大规模数据。它的核心是Hadoop分布式文件系统(HDFS)和MapReduce计算模型。HDFS将数据分散存储在集群的不同节点上,而MapReduce模型则提供了一种并行处理数据的方式。大数据开发工程师需要熟悉Hadoop的架构和命令行工具,能够编写MapReduce程序来处理数据。 Spark是一个快速和通用的大数据处理引擎,可以在大规模集群上执行数据处理任务。它拥有比Hadoop更高的性能和更丰富的功能。Spark提供了强大的机器学习、图计算和流处理等功能。大数据开发工程师需要熟悉Spark的API和编程模型,能够使用Spark的各种组建和工具进行数据处理和分析。 作为大数据开发工程师,掌握Hadoop和Spark是非常重要的。使用Hadoop和Spark可以有效地处理大规模数据,提取有价值的信息。大数据开发工程师通过编写和优化MapReduce程序来实现数据处理的需求,同时也能利用Spark提供的机器学习和流处理等功能来进行更复杂的数据分析。通过合理地使用Hadoop和Spark,大数据开发工程师可以减少数据处理的时间和成本,提高数据处理的效率和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值