Spark性能优化:开发调优篇

发表于:2018-8-01 10:40

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:citibank    来源:阿里云栖社区

  1、前言
  在大数据计算领域,Spark已经成为了越来越流行、越来越受欢迎的计算平台之一。Spark的功能涵盖了大数据领域的离线批处理、SQL类处理、流式/实时计算、机器学习、图计算等各种不同类型的计算操作,应用范围与前景非常广泛。在美团?大众点评,已经有很多同学在各种项目中尝试使用Spark。大多数同学(包括笔者在内),最初开始尝试使用Spark的原因很简单,主要就是为了让大数据计算作业的执行速度更快、性能更高。
   
  然而,通过Spark开发出高性能的大数据计算作业,并不是那么简单的。如果没有对Spark作业进行合理的调优,Spark作业的执行速度可能会很慢,这样就完全体现不出Spark作为一种快速大数据计算引擎的优势来。因此,想要用好Spark,就必须对其进行合理的性能优化。
  Spark的性能调优实际上是由很多部分组成的,不是调节几个参数就可以立竿见影提升作业性能的。我们需要根据不同的业务场景以及数据情况,对Spark作业进行综合性的分析,然后进行多个方面的调节和优化,才能获得最佳性能。
  笔者根据之前的Spark作业开发经验以及实践积累,总结出了一套Spark作业的性能优化方案。整套方案主要分为开发调优、资源调优、数据倾斜调优、shuffle调优几个部分。开发调优和资源调优是所有Spark作业都需要注意和遵循的一些基本原则,是高性能Spark作业的基础;数据倾斜调优,主要讲解了一套完整的用来解决Spark作业数据倾斜的解决方案;shuffle调优,面向的是对Spark的原理有较深层次掌握和研究的同学,主要讲解了如何对Spark作业的shuffle运行过程以及细节进行调优。
  本文作为Spark性能优化指南的基础篇,主要讲解开发调优以及资源调优。
  2、开发调优
  Spark性能优化的第一步,就是要在开发Spark作业的过程中注意和应用一些性能优化的基本原则。开发调优,就是要让大家了解以下一些Spark基本开发原则,包括:RDD lineage设计、算子的合理使用、特殊操作的优化等。在开发过程中,时时刻刻都应该注意以上原则,并将这些原则根据具体的业务以及实际的应用场景,灵活地运用到自己的Spark作业中。Spark性能优化的第一步,就是要在开发Spark作业的过程中注意和应用一些性能优化的基本原则。开发调优,就是要让大家了解以下一些Spark基本开发原则,包括:RDD lineage设计、算子的合理使用、特殊操作的优化等。在开发过程中,时时刻刻都应该注意以上原则,并将这些原则根据具体的业务以及实际的应用场景,灵活地运用到自己的Spark作业中。
  原则一:避免创建重复的RDD
  通常来说,我们在开发一个Spark作业时,首先是基于某个数据源(比如Hive表或HDFS文件)创建一个初始的RDD;接着对这个RDD执行某个算子操作,然后得到下一个RDD;以此类推,循环往复,直到计算出最终我们需要的结果。在这个过程中,多个RDD会通过不同的算子操作(比如map、reduce等)串起来,这个“RDD串”,就是RDD lineage,也就是“RDD的血缘关系链”。
  我们在开发过程中要注意:对于同一份数据,只应该创建一个RDD,不能创建多个RDD来代表同一份数据。
  一些Spark初学者在刚开始开发Spark作业时,或者是有经验的工程师在开发RDD lineage极其冗长的Spark作业时,可能会忘了自己之前对于某一份数据已经创建过一个RDD了,从而导致对于同一份数据,创建了多个RDD。这就意味着,我们的Spark作业会进行多次重复计算来创建多个代表相同数据的RDD,进而增加了作业的性能开销。
  一个简单的例子
  //也就是说,需要对一份数据执行两次算子操作。
  //错误的做法:对于同一份数据执行多次算子操作时,创建多个RDD。
  //这里执行了两次textFile方法,针对同一个HDFS文件,创建了两个RDD出来,
  //然后分别对每个RDD都执行了一个算子操作。
  //这种情况下,Spark需要从HDFS上两次加载hello.txt文件的内容,并创建两个单独的RDD;
  //第二次加载HDFS文件以及创建RDD的性能开销,很明显是白白浪费掉的。
  val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
  rdd1.map(...)
  val rdd2 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
  rdd2.reduce(...)
  //正确的用法:对于一份数据执行多次算子操作时,只使用一个RDD。
  //这种写法很明显比上一种写法要好多了,因为我们对于同一份数据只创建了一个RDD,
  //然后对这一个RDD执行了多次算子操作。
  //但是要注意到这里为止优化还没有结束,由于rdd1被执行了两次算子操作,第二次执行reduce操作的时候,
  //还会再次从源头处重新计算一次rdd1的数据,因此还是会有重复计算的性能开销。
  //要彻底解决这个问题,必须结合“原则三:对多次使用的RDD进行持久化”,
  //才能保证一个RDD被多次使用时只被计算一次。
  val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
  rdd1.map(...)
  原则二:尽可能复用同一个RDD
  除了要避免在开发过程中对一份完全相同的数据创建多个RDD之外,在对不同的数据执行算子操作时还要尽可能地复用一个RDD。比如说,有一个RDD的数据格式是key-value类型的,另一个是单value类型的,这两个RDD的value数据是完全一样的。那么此时我们可以只使用key-value类型的那个RDD,因为其中已经包含了另一个的数据。对于类似这种多个RDD的数据有重叠或者包含的情况,我们应该尽量复用一个RDD,这样可以尽可能地减少RDD的数量,从而尽可能减少算子执行的次数。
  一个简单的例子
  // 错误的做法。
  // 有一个<Long, String>格式的RDD,即rdd1。
  // 接着由于业务需要,对rdd1执行了一个map操作,创建了一个rdd2,而rdd2中的数据仅仅是rdd1中的value值而已,也就是说,rdd2是rdd1的子集。
  JavaPairRDD<Long, String> rdd1 = ...
  JavaRDD<String> rdd2 = rdd1.map(...)
  // 分别对rdd1和rdd2执行了不同的算子操作。
  rdd1.reduceByKey(...)
  rdd2.map(...)
  // 正确的做法。
  // 上面这个case中,其实rdd1和rdd2的区别无非就是数据格式不同而已,rdd2的数据完全就是rdd1的子集而已,却创建了两个rdd,并对两个rdd都执行了一次算子操作。
  // 此时会因为对rdd1执行map算子来创建rdd2,而多执行一次算子操作,进而增加性能开销。
  // 其实在这种情况下完全可以复用同一个RDD。
  // 我们可以使用rdd1,既做reduceByKey操作,也做map操作。
  // 在进行第二个map操作时,只使用每个数据的tuple._2,也就是rdd1中的value值,即可。
  JavaPairRDD<Long, String> rdd1 = ...
  rdd1.reduceByKey(...)
  rdd1.map(tuple._2...)
  // 第二种方式相较于第一种方式而言,很明显减少了一次rdd2的计算开销。
  // 但是到这里为止,优化还没有结束,对rdd1我们还是执行了两次算子操作,rdd1实际上还是会被计算两次。
  // 因此还需要配合“原则三:对多次使用的RDD进行持久化”进行使用,才能保证一个RDD被多次使用时只被计算一次。
  原则三:对多次使用的RDD进行持久化
  当你在Spark代码中多次对一个RDD做了算子操作后,恭喜,你已经实现Spark作业第一步的优化了,也就是尽可能复用RDD。此时就该在这个基础之上,进行第二步优化了,也就是要保证对一个RDD执行多次算子操作时,这个RDD本身仅仅被计算一次。
  Spark中对于一个RDD执行多次算子的默认原理是这样的:每次你对一个RDD执行一个算子操作时,都会重新从源头处计算一遍,计算出那个RDD来,然后再对这个RDD执行你的算子操作。这种方式的性能是很差的。
  因此对于这种情况,我们的建议是:对多次使用的RDD进行持久化。此时Spark就会根据你的持久化策略,将RDD中的数据保存到内存或者磁盘中。以后每次对这个RDD进行算子操作时,都会直接从内存或磁盘中提取持久化的RDD数据,然后执行算子,而不会从源头处重新计算一遍这个RDD,再执行算子操作。
   
   上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。

21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号