指南:优化Apache Spark作业(第2部分)

说明:借助谷歌翻译,以个人理解进行修改

原文地址:https://blog.cloudera.com/blog/2015/03/how-to-tune-your-apache-spark-jobs-part-2/

在本系列的结语中,了解资源调优,并行性和数据表示如何影响Spark作业性能。

在这篇文章中,我们将完成“指南:优化Apache Spark作业(第1部分)”中的内容。我将尽力涵盖所有你想知道的关于如何使Spark程序快速运行的知识。尤其是,您将了解资源调整,或配置Spark以利用群集所提供的所有资源。然后我们将讨论优化并行性,这是工作性能中最困难和最重要的参数。最后,您将学习如何表示数据本身,Spark将读取的磁盘上(扰流警报:使用Apache Avro或Apache Parquet)或内存中,缓存或移动时所需的格式。

 

调整资源分配

Spark用户列表是一系列的问题,“我有一个500节点的集群,但是当我运行我的应用程序时,我一次只能看到两个任务正在执行。HALP“。考虑到控制Spark资源利用率的参数数量,这些问题并不公平,但在本节中,您将学习如何从群集中挤出最后一滴果汁。这里的建议和配置在Spark的集群管理器(YARN,Mesos和Spark Standalone)之间略有不同,但我们将只关注Cloudera向所有用户推荐的YARN。

有关在YARN上运行Spark的一些背景知识,请查看关于此主题的帖子。

Spark(和YARN)考虑的两个主要资源是CPU和内存。当然,磁盘和网络I / O也是Spark性能中的一部分,但Spark和YARN目前都没有做任何事情来积极管理它们。

应用程序中的每个Spark执行程序都具有相同的固定核数量和相同的固定堆大小。当在命令行中调用spark-submit,spark-shell和pyspark时,可以通过--executor-cores指定内核的数量,也可以通过在spark-defaults.conf文件中配置spark.executor.cores属性,或着在SparkConf对象中设置属性。同样,可以用--executor-memory 或spark.executor.memory属性来控制堆大小。该cores属性控制执行程序可以运行的并发任务数。 --executor-cores 5意味着每个执行者最多可以同时运行五个任务。内存属性会影响Spark可以缓存的数据量以及用于分组,汇总和连接的shuffle数据结构的最大值。

该--num-executors命令行参数或spark.executor.instances配置属性控制请求的executors的数量。在CDH 5.4 / Spark 1.3开始,您将能够通过配置spark.dynamicAllocation.enabled属性来启用动态分配,从而避免配置该属性。动态分配使Spark应用程序可以在有待处理任务积压时请求executor,并在空闲时释放executor。

同样重要的是,要考虑Spark所请求的资源如何与Yarn的可用资源相匹配。相关的YARN属性是:

yarn.nodemanager.resource.memory-mb 控制每个节点上容器使用的最大内存总和。

yarn.nodemanager.resource.cpu-vcores 控制每个节点上容器使用的最大内核总数。

请求5个executor内核将导致在Yarn中请求5个虚拟内核。由于一些原因,从Yarn中请求内存要稍微复杂一些:

  • --executor-memory/spark.executor.memory控制executor的堆内存大小,但JVM也可以使用堆内的某些内存,例如用于interned字符串和直接字节缓冲区。该spark.yarn.executor.memoryOverhead属性的值被添加到executor内存中以确定每个executor从YARN请求的完整内存。它默认为max(384,.07 *spark.executor.memory)。
  • YARN可能会将请求的内存略微增加一点。YARN 中的yarn.scheduler.minimum-allocation-mb和yarn.scheduler.increment-allocation-mb属性分别控制每次增加的最小值和递增值。

下图显示Spark和YARN中内存属性的层次结构:

如果以上这些还不够,在确定Spark executor的大小时还需要考虑最后几个问题:

  • 应用程序master程序是一个非executor容器,具有从YARN请求容器的特殊功能,它占用了自己的资源,必须进行预算。。在yarn-client模式下,它默认为1024MB和一个vcore。在yarn-cluster模式下,应用程序master程序运行driver进程,因此通过使用--driver-memory和--driver-cores属性来设置其资源通常很有用。
  • Executor的运行内存过大通常会导致垃圾回收延迟。对于单个executor来说,64GB是一个很好的上限。
  • 我注意到HDFS客户机有大量线程并发的问题。一个大致的猜测是,每个executor最多可以执行五个任务,以实现完全的写入能力,因此最好将每个executor的核心数量保持在该数量以下。
  • 运行小的executor(例如,使用单个内核和仅够运行单个任务的内存)会抛弃在单个JVM中运行多个任务所带来的好处。例如,广播变量需要在每个executor上复制一次,那么许多小的executor将导致更多的数据副本。

为了让这一切变得更加具体,这里有一个配置Spark应用程序,尽可能多地使用集群资源的示例:假设一个集群有六个节点运行NodeManager,每个节点配备16个内核和64GB内存。NodeManager的容量yarn.nodemanager.resource.memory-mb和yarn.nodemanager.resource.cpu-vcores,可能应该分别设置为63 * 1024 = 64512(兆字节)和15。我们避免将100%的资源分配给YARN容器,因为节点需要一些资源来运行OS和Hadoop守护进程。在这种情况下,我们为这些系统进程留下了1G内存和一个核心。Cloudera Manager帮助计算并自动配置这些Yarn属性。

可能的第一冲动是使用--num-executors 6--executor-cores 15 --executor-memory 63G。但是,这是错误的方法,因为:

  • 63GB + executor的内存开销不符合NodeManagers的63GB容量。
  • 应用程序master将在其中一个节点上占用一个核心,这意味着该节点上不会有15个核心分配给executor。
  • 每个executor有15个内核会导致HDFS I / O吞吐量不佳。

更好的选择是使用--num-executors 17 --executor-cores5 --executor-memory 19G。为什么?

这个配置会导致除了Application Master所在节点有两个executor外,其他所有节点上都有三个executor。

--executor-memory可以被推导出来:(每节点63/3个executor)=21.21 * 0.07 = 1.47。21 - 1.47≈19。


并行调优

正如你可能已经知道的那样,Spark是一个并行处理引擎。可能不那么明显的是,Spark不是一个“神奇”的并行处理引擎,并且其计算出最佳的并行度的能力是有限的。每个Spark stage都有许多task,每个task都按顺序处理数据。在优化Spark作业时,task的数量可能是决定性能的唯一最重要的参数。

Task的数量是如何确定的?上一篇文章介绍了Spark将RDD分成几个stage的方式。(作为作为一个快速提醒,像repartition和reduceByKey这样的转换会导致stage边界。)stage中的task数量与stage中最后一个RDD中的分区数目相同。RDD中分区的数量与它所依赖的RDD中分区的数量相同,但有几个例外:thecoalesce转换允许创建一个比它的父RDD更少分区的RDD,union转换创建一个包含了其父RDD的分区数量总和的RDD, cartesian用他们的产品创建了一个RDD。

没有父母的RDDs怎么办?由textFile或hadoop文件生成的RDDs的分区由所使用的底层MapReduce InputFormat决定。通常,每个正在读取的HDFS块都会有一个分区。并行化产生的RDD分区来自用户给出的参数,或者如果用户没有给出则由spark.default.parallelism属性决定。

要确定RDD中分区的数量,可以调用rdd.partitions().size()。

主要担心的是任务数量太小。如果任务少于可用于运行任务的插槽,则stage将不会利用所有可用的CPU。

少量的任务也意味着对每个任务中出现的任何聚合操作都有更多的内存压力。任何join、cogroup或*ByKey操作都需要在hashmap或内存缓冲区中对对象进行分组或排序。 join,cogroup和groupByKey在它们触发的shuffle端的stage中的task中使用这些数据结构。reduceByKey和aggregateByKey在他们触发的shuffle两边的stage的任务中使用给这些数据结构。

当这些聚合操作的记录在内存中放不下时,会发生一些混乱。首先,在这些数据结构中保存许多记录会对垃圾回收造成压力,这可能导致暂停任务队列。其次,当内存放不下这些记录时,Spark会将它们溢写到磁盘,这会导致磁盘I / O和排序。大量shuffle期间的这种开销可能是我在Cloudera客户中看到的导致工作停滞的头号原因。

那么如何增加分区的数量呢?如果有问题的阶段是在读取Hadoop上时,您的选择是:

  • 使用重新分区转换,这将触发洗牌。
  • 配置你的InputFormat来创建更多的分割。
  • 将输入数据写入HDFS用较小的块大小。

如果一个stage从另一个stage获得输入,则触发stage边界的转换将接受一个numPartitions参数,例如

val rdd2 =rdd1.reduceByKey(_ + _, numPartitions = X)

“X”应该是什么?优化分区数最直接的方法是实验:查看父RDD中的分区数,然后保持1.5倍,直到性能停止改进。

还有一种更有原则的计算X的方法,但由于某些量很难计算,所以很难应用先验知识。我把它包括在这里不是因为它被推荐用于日常使用,而是因为它有助于理解发生了什么。主要目标是运行足够多的task,以便为每个task指定的数据量适合于该task可用的内存。

每个任务可用的内存是(spark.executor.memory*spark.shuffle.memoryFraction* spark.shuffle.safetyFraction)/ spark.executor.cores。memoryFraction和safetyFraction分别默认为0.2和0.8。

总的shuffle数据使用内存的大小更难确定。最接近的探索式就是找出Shuffle Spill(内存)和Shuffle Spill(磁盘)之间的比率。然后用这个数字乘以总的shuffle写入数据量。然而,如果stage正在缩小,这可能会有些复杂:

 

然后再进行一些调整,因为太多的分区通常比太少的分区要好。

事实上,如果有疑问,在大量任务(以及分区)方面出错几乎总是会更好。这个建议与MapReduce的建议相反,MapReduce要求您在任务数量上更加保守。不同之处在于MapReduce的任务启动开销很高,而Spark则没有。


减少你的数据结构

数据以记录的形式通过Spark流动。一条记录有两个表示:一个反序列化的Java对象表示和一个序列化的二进制表示。一般情况下,Spark使用反序列化表示内存中的记录,以及序列化表示存储在磁盘上或通过网络传输的记录。有计划的工作是以序列形式存储一些内存中的shuffle数据。

spark.serializer属性控制用于在这两个表示之间转换的序列化器。Kryo序列化器,org.apache.spark.serializer.KryoSerializer是首选项。不幸的是,它并不是默认的,因为在早期版本的Spark中有一些不稳定,并且希望不破坏兼容性,但是应该始终使用Kryo序列化器。

这两种表示方式占用空间的大小对Spark性能有巨大影响。值得回顾一下传递的数据类型并寻找削减一些冗余的地方。

臃肿的反序列化对象将导致Spark更频繁地将数据溢写到磁盘,并减少Spark可以缓存的反序列化记录(例如在MEMORY存储级别)的数量。Spark调优指南有一个很大的部分,可以将这些内容精简。

臃肿的序列化对象将导致更大的磁盘和网络I / O,并减少Spark可以缓存的序列化记录的数量(例如,在MEMORY_SER存储级别)。此处的主要操作项目是确保注册您定义的任何自定义类并使用SparkConf#registerKryoClassesAPI 传递。


数据格式

无论何时,当您有权决定如何将数据存储在磁盘上时,请使用可扩展的二进制格式,如Avro、Parquet、Thrift或Protobuf。选择其中一种格式并坚持下去。很明显,当一个人谈到在Hadoop上使用Avro、Thrift或Protobuf时,他们的意思是每个记录都是存储在序列文件中的Avro/Thrift/Protobufstruct。JSON是不值得的。

每当您考虑以JSON存储大量数据时,请考虑一下将在中东开始的冲突,加拿大将被堵塞的美丽河流或将在美国中心地区建造的核电站的放射性沉降物,为一遍又一遍解析文件所花费的CPU周期提供动力。同时,试着学习别人的技能,这样你就能说服你的同事和上司也这样做。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值