Learning Spark笔记16-调试Spark

第8章调试Spark




使用SparkConf配置Spark


Spark中主要的配置机制就是SparkConf类。当你创建一个SparkContext时,SparkConf是必须要有的。


Example 8-1. Creating an application using a SparkConf in Python
# Construct a conf
conf = new SparkConf()
conf.set("spark.app.name", "My Spark App")
conf.set("spark.master", "local[4]")
conf.set("spark.ui.port", "36000") # Override the default port
# Create a SparkContext with this configuration
sc = SparkContext(conf)


Example 8-2. Creating an application using a SparkConf in Scala
// Construct a conf
val conf = new SparkConf()
conf.set("spark.app.name", "My Spark App")
conf.set("spark.master", "local[4]")
conf.set("spark.ui.port", "36000") // Override the default port
// Create a SparkContext with this configuration
val sc = new SparkContext(conf)


Example 8-3. Creating an application using a SparkConf in Java
// Construct a conf
SparkConf conf = new SparkConf();
conf.set("spark.app.name", "My Spark App");
conf.set("spark.master", "local[4]");
conf.set("spark.ui.port", "36000"); // Override the default port
// Create a SparkContext with this configuration
JavaSparkContext sc = JavaSparkContext(conf);


SparkConf类相当简单:一个SparkConf实例包括用户可以覆盖的键值配置项。每个配置项都是字符串类型的键和值。使用set()方法设置值,然后提供给SparkContext的构造方法。除了set()方法,SparkConf类还提供了工具方法设置一般的参数。像是setAppName()和setMaster(),分别对应是spark.app.name和spark.master。


在上面的例子中,SparkConf的值是通过编程时候的代码设置的。大多数情况,可以动态为一个应用程序设置参数。Spark允许通过spark-submit工具来设置。当一个应用程序由spark-submit启动后,他会将配置文件值注入到环境中。当SparkConf构建的时候这些会被自动检测并填充。因此,用户的应用程序可以简单的构造一个空的SparkConf,然后在使用spark-submit时候传入。


Example 8-4. Setting configuration values at runtime using flags
$ bin/spark-submit \
 --class com.example.MyApp \
 --master local[4] \
 --name "My Spark App" \
 --conf spark.ui.port=36000 \
 myApp.jar


spark-submit也可以将配置文件从文件导入。这可以用于设置可以跨多个用户共享的环境配置,例如默认主服务器。默认的,spark-submit会查找conf/spark-defaults.conf文件。你也可以通过spark-submit的--properties-file参数指定。


$ bin/spark-submit \
 --class com.example.MyApp \
 --properties-file my-config.conf \
 myApp.jar


## Contents of my-config.conf ##
spark.master local[4]
spark.app.name "My Spark App"
spark.ui.port 36000


在某些情况下,可能会在多个位置设置相同的配置属性。举例,用户可能使用SparkConf对象的setAppName()方法直接设置,然后也通过spark-submit的--name参数进行了设置。在这种情况下,Spark有一个优先顺序。给出了在SparkConf对象上使用set()函数在用户代码中明确声明的配置的最高优先级,其次是通过spark-submit传递的值,最后是默认值。如果您想了解给定应用程序的哪些配置,您可以通过本章后面讨论的应用程序Web UI来查看显示的活动配置列表。


执行的组件:Jobs,Tasks和Stages


要调试Spark首先你需要深入理解系统的内部设计。在前几章中,您看到了RDD及其分区的“逻辑”表示。当执行的时候,Spark会将逻辑表示转换为物理执行计划,将多个操作合并到tasks中。


为了演示Spark的执行阶段,我们将演示一个示例应用程序,了解用户代码如何编译成较低级别的执行计划。下面的例子是Spark shell的日志分析。输入的数据,我们使用不同程度的日志消息的文本文件,文件中包含一些空行。


Example 8-6. input.txt, the source file for our example
## input.txt ##
INFO This is a message with content
INFO This is some other content
(empty line)
INFO Here are more messages
WARN This is a warning
(empty line)
ERROR Something bad happened
WARN More details on the bad thing
INFO back to normal messages


我们打开文件,然后计算每种程度的日志消息的数量。首先创建一个RDD


Example 8-7. Processing text data in the Scala Spark shell
// Read input file
scala> val input = sc.textFile("input.txt")
// Split into words and remove empty lines
scala> val tokenized = input.
 | map(line => line.split(" ")).
 | filter(words => words.size > 0)
// Extract the first word from each line (the log level) and do a count
scala> val counts = tokenized.
 | map(words => (words(0), 1)).
 | reduceByKey{ (a, b) => a + b }


 上面命令的结果是会生成一个RDD,包含每个程度日志的数量。在shell中执行这些行后,程序没有执行任何操作。 相反,它隐含地定义了RDD对象的有向非循环图(DAG),一旦发生动作,将被稍后使用。每个RDD维护一个指针,指针指向一个或多个父节点并且伴随着元数据(他们的关系类型)。例如,b = a.map()在一个RDD上执行,这个RDD的b保留着引用到a。这些指针允许一个RDD可以追踪它的所有祖先。


要显示一个RDD的血统,Spark提供一个toDebugString()方法。


Example 8-8. Visualizing RDDs with toDebugString() in Scala
scala> input.toDebugString
res85: String =
(2) input.text MappedRDD[292] at textFile at <console>:13
| input.text HadoopRDD[291] at textFile at <console>:13
scala> counts.toDebugString
res84: String =
(2) ShuffledRDD[296] at reduceByKey at <console>:17
+-(2) MappedRDD[295] at map at <console>:17
 | FilteredRDD[294] at filter at <console>:15
 | MappedRDD[293] at map at <console>:15
 | input.text MappedRDD[292] at textFile at <console>:13
 | input.text HadoopRDD[291] at textFile at <console>:13


 第一个可视化显示输入的RDD。我们可以调用sc.textFile()来创建RDD。谱系给我们一些关于什么sc.textFile()的线索,因为它揭示了哪些RDD是在textFile()函数中创建的。我们可以看到它创建了一个HadoopRDD,然后在其上执行映射来创建返回的RDD。counts的血统稍微复杂一些。那个RDD有几个祖先,因为input RDD有些其他的操作,像是map,filtering和reduction。


 Example 8-9. Collecting an RDD
scala> counts.collect()
res86: Array[(String, Int)] = Array((ERROR,1), (INFO,4), (WARN,2))


Spark的调度程序创建一个物理执行计划来计算执行操作所需的RDD。当我们在RDD上调用collect(),必须实现RDD的每个分区,然后转移到driver program。Spark的计划器从要计算的最后RDD开始(这个例子中就是counts),然后向后工作找到它必须要计算的东西。它会访问父辈RDD,父辈RDD的父辈等等,递归地开发计算所有祖先RDD所需的物理计划。在最简单的情况下,在这个图中,计划器为每个RDD输出一个计算阶段,该阶段对于RDD中的每个分区都有tasks,然后以相反的顺序执行这些阶段以计算最终所需的RDD。


在更复杂的情况下,物理层级不一定与RDD图形完全一一对应。 当计划器执行管道或将多个RDD压缩到单个阶段时,可能会发生这种情况。如果RDD可以从父母计算而不进行数据移动,就会出现管道问题。Example 8-8血统输出显示了使用相同的缩进步骤会在物理阶段会传递都一起。相同缩进级别的RDD的父辈在物理执行阶段被放在管道上。例如,当我们计算计数时,即使有大量的父级RDD,只显示了两个级别的缩进。这说明物理执行阶段只需要两个阶段。这个管道在连续过程中下有多个过滤器和map操作。


如果你访问应用的web UI页面,你会看到为了满足collect()动作发生了两个阶段。Spark UI的地址是http://localhost:4040。


除了管道,如果现有的RDD已经在集群存储器或磁盘上持久存在,Spark的内部调度器可能会截断RDD图形的谱系。在这种情况下Spark会“短路”,然后它会基于持久存储RDD重新计算。第2种发生截断的情况,即使没有调用persist(),当一个RDD作为一个较早的shuffle的副作用已经实现。这是一个底层优化,它充分利用了Spark shuffle输出写入磁盘的事实,并且利用RDD图的多个部分被重新计算的事实。


要查看缓存对物理执行的影响,我们来缓存计数RDD,并查看如何截取执行图以供将来的操作。如果你重新访问UI,您应该看到缓存减少了执行未来计算所需的阶段数。再次调用collect()会显示执行操作的一个阶段。


Example 8-10. Computing an already cached RDD
// Cache the RDD
scala> counts.cache()
// The first subsequent execution will again require 2 stages
scala> counts.collect()
res87: Array[(String, Int)] = Array((ERROR,1), (INFO,4), (WARN,2), (##,1),
((empty,2))
// This execution will only require a single stage
scala> counts.collect()
res88: Array[(String, Int)] = Array((ERROR,1), (INFO,4), (WARN,2), (##,1),
((empty,2))
为特定行动制作的一系列stages被称为job。当我们调用count()的时候,我们会创建一个job包括一个或多个stages。


一旦stage图定义好了,task就会被创建然后分发到内存的计划器中,计划器根据所使用的部署模式而有所不同。物理计划中的stages可以依赖于RDD谱系,因此它们将以特定的顺序执行。


一个物理阶段会启动task,在特殊的分区数据上做相同的事情。每个task内部执行相同的顺序:


1.从数据存储(如果RDD是输入RDD)获取其输入,现有RDD(如果阶段基于已缓存的数据)或混洗输出。


2.执行必要的操作来计算它所代表的RDD。例如,对输入数据执行filter()或map()函数,或执行分组或缩减。


3.写输出到shuffle,到外部的存储,或者回到driver(如果它是动作的最后一个RDD像count())


Spark中的大多数记录都是stages、task和shuffles进行表示的。了解用户代码如何编译成物理执行的一部分是一个先进的概念,但可以帮助您极大地调优和调试应用程序。


下面是Spark执行过程发生的事情:


用户代码定义一个RDDs的DAG
RDD上的操作创建新的RDD,返回到其父母,从而创建一个图形。


动作强制DAG转换为一个可执行的计划
当您在RDD上调用操作时,必须进行计算。 这也需要计算其父RDD。Spark的计划器提交一个job来计算所有的需要的RDDs。这个job会有一个或多个stages,stages是由task组成的并行计算的波。每个stage将对应DAG的一个或多个RDDs。一个单独stage可以通过管道对应多个RDDs


Tasks是计划执行在一个集群上
Stages是顺序的处理,单独的tasks启动计算RDD的一部分。一个job中一旦最后的stage完成,动作就完成了。在给定的Spark应用程序中,当创建新的RDD时,这整个步骤序列可能以连续的方式发生多次。


在给定的Spark应用程序中,当创建新的RDD时,这整个步骤序列可能以连续的方式发生多次


查找信息


Spark Web UI


了解Spark应用程序的行为和性能的第一站是Spark的内置Web UI。默认是在运行driver的机器上的4040端口上。需要注意的是,在YARN群集模式下,应用程序驱动程序在群集中运行的情况下,您应该通过YARN ResourceManager访问UI,该管理员代理直接向驱动程序请求。


Spark UI包含几个不同的页面,格式可能与版本的不同而不同。Spark1.2,UI包括4部分不同的内容。


jobs:过程,stages的指标,tasks和更多


在诸如Spark这样的数据并行系统中,性能问题的常见来源是倾斜,当少量任务与其他任务相比需要非常大的时间时,会出现这种情况。通过查看所有任务中不同指标的分布,stage页面可以帮助您识别倾斜。你最好从运行时的task开始;做一些task比别的花费更多时间?如果是这样的话,你可以深入查看是什么引起task变慢。少数任务读取或写入比别人更多的数据? 在某些节点上运行的任务是否很慢? 当您调试作业时,这些是有用的第一步。


除了查看task的倾斜之外,还应该关注task在每个生命周期阶段花费的时间,生命周期包括:读,计算和写。如果task花费比较少的时间读或写,但是整个的时间很长,那就可能是用户代码需要优化。有些tasks可能会花费几乎所有的时间从外部存储系统读取数据,并且不会因为Spark的额外优化而受益匪浅,因为它们是输入读取的瓶颈。


存储:持久化RDD的信息


存储的页面包含持久化RDDs的信息。当调用了RDD的persist()函数,那么一个RDD会被持久化,然后它会在一些job中进行计算。在很多情况下,如果很多RDDs被缓存了,旧的会从内存中清除用来存储新的。该页面将告诉您每个RDD的缓存数量以及缓存在各种存储介质(磁盘,内存等)中的数据量。扫描此页面可以帮助您了解重要的数据集是否适合内存。


executors:应用程序中的一个executors列表


这个页面列出了应用程序中活动的executors以及每个executor处理运行的指标。该页面有一个非常有价值的用途就是确认你的应用程序有你期望的资源量。调试问题时的第一步是扫描此页面,因为由于显而易见的原因导致执行程序数量少于预期的错误配置会影响性能。寻找具有异常行为的执行者也是有用的,例如非常大的成功任务比例。具有较高故障率的执行程序可能表明相关物理主机上的配置错误或故障。 简单地从群集中删除该主机可以提高性能。


executors页面的另一个特点可以使用Thread Dump按钮来收集栈跟踪。可视化执行程序的线程调用堆栈可以准确地显示正在执行的代码。一个executor的可视化的线程调用栈可以准确的显示正在执行的代码。如果执行者在短时间内使用此功能进行多次采样,则可以用用户代码标识“热点”或昂贵的部分。 这种类型的非正式分析通常可以检测用户代码中的低效率。


environment:调试Spark配置


此页面列举了Spark应用程序的一组活动属性。这里的配置表示应用程序配置的“基本事实”。如果您正在调试启用了哪些配置标志,特别是在使用多个配置机制的情况下,这会很有帮助。这个页也列出了应用程序添加的JAR和文件,像是跟踪缺少的依赖包非常有用。


Driver和Executor日志


在某些情况下,用户可以通过检查驱动程序和执行程序直接生成的日志来了解Spark的更多信息。日志包括更详细的异常信息像是内部的警告或者用户代码的异常。


Spark的日志文件的确切位置取决于部署模式:
在Spark的独立模式下,应用程序日志直接显示在独立主站的Web UI中。 它们默认存储在每个worker的Spark分配的work /目录中。


在Mesos中,日志存储在Mesos从站的工作/目录中,可以从Mesos主UI访问。


在YARN模式中,最简单收集日志的方法是使用YARN的日志收集工具(运行yarn logs -applicationId <app ID>)从你的应用程序生成报告。这样的工作只有在应用程序完全完成之后,因此YARN必须首先聚合这些日志。在YARN中查看运行的应用程序的日志,可以通过ResourceManager UI到节点的页面,浏览一个容器特定的节点。YARN会给你与该容器中Spark产生的输出相关的日志。在未来的Spark版本中,这个过程很可能会变得不那么迂回,直接链接到相关的日志。


默认情况下,Spark会输出一个健康的日志信息。还可以自定义日志记录行为以将日志记录级别或日志输出更改为非标准位置。Spark的日志记录子系统是基于log4j的,一个广泛使用的Java日志记录库,并使用log4j的配置格式。在conf /log4j.properties.template中,将一个示例log4j配置文件与Spark捆绑在一起。要定制Spark的日志记录,首先将该示例复制到名为log4j.properties的文件中。然后,您可以修改行为,例如根日志记录级别(日志记录输出的阈值级别)。默认是INFO,为了减少日志输出,你可以设置WARN或者ERROR。一旦你调整了日志以匹配你想要的级别或格式,你可以使用spark-submit的--files参数指定log4j.properties文件。如果以这种方式设置日志级别时遇到问题,请确保不包含任何本身包含log4j.properties文件的JAR。Log4j会扫描类路径的第一个配置文件,如果在其他地方找到配置文件,那么他将会忽略你自定义的配置。


关键的性能因素




在这一点上,你知道一些关于Spark如何在内部工作,如何跟踪正在运行的Spark应用程序的进度,以及指标和日志信息的去向。本节将介绍下一步,并讨论Spark应用程序中可能遇到的常见性能问题以及调整应用程序以获得最佳性能的提示。前三个小节介绍了为提高性能而可以进行的代码级更改,而最后一小节讨论了调整Spark运行的集群和环境。


并行水平


逻辑上表达RDD是一个对象的集合。在物理执行阶段,一个RDD被分割成一组分区,每个分区包括所有数据的一个子集。当Spark计划运行tasks的时候,在一个分区中它为数据存储创建一个单独task,并且该任务默认要求用单个核心去执行。输入RDD通常基于底层的存储系统选择并行性。例如,HDFS作为RDD的输入,对于底层HDFS文件的每个块都有一个分区。根据混洗其他RDD的RDD将根据其父RDD的大小设置并行度。


并行度可以通过两种方式影响性能。第一是,如果并行太小,Spark会让资源闲置。例如,如果你的应用程序被分配了1000核,你只运行了一个只有30个task的stage,你可以增加并行度来使用更多地核。如果并行太大,那么与每个分区相关的小额开销就会加起来并变得很重要。这种情况意味着,你的任务会立即完成-几毫秒-或不读取或写入任何数据的任务。


Spark提供两种方式调试并行度的操作。首先是在操作中shuffle数据,您始终可以将生成的RDD作为参数进行一定程度的并行处理。其次,任何现有的RDD都可以被重新分配以具有更多或更少的分区。如果您知道您正在缩小RDD,则可以使用coalesce()运算符;这比重新repartition()更有效,因为它避免了shuffle操作。如果你认为你又过多或过少的并行,它可以帮助重新分配你的数据。


举个例子,假设我们正在从S3读取大量的数据,但是立即执行一个filter()操作,该操作可能排除除了极小部分数据集外的所有数据。默认情况下,filter()返回的RDD将具有相同的值大小作为其父,并可能有许多空的或小的分区。在这种情况下,通过合并到更小的RDD来提高应用程序的性能。


Example 8-11. Coalescing a large RDD in the PySpark shell
# Wildcard input that may match thousands of files
>>> input = sc.textFile("s3n://log-files/2014/*.log")
>>> input.getNumPartitions()
35154
# A filter that excludes almost all data
>>> lines = input.filter(lambda line: line.startswith("2014-10-17"))
>>> lines.getNumPartitions()
35154
# We coalesce the lines RDD before caching
>>> lines = lines.coalesce(5).cache()
>>> lines.getNumPartitions()
4
# Subsequent analysis can operate on the coalesced RDD...
>>> lines.count()


序列化格式


当Spark通过网络传输数据或将数据泄漏到磁盘时,它需要序列化对象为二进制的格式。这是在shuffle操作中使用的,潜在的大量数据被传送。默认情况下Spark会使用Java内嵌的序列化器。Spark也提供Kryo,这是一个第三方的序列化库,通过提供更快的序列化时间和更紧凑的二进制表示形式来改进Java的序列化,但不能序列化所有类型的“开箱即用”对象。


想要使用Kryo序列化,你可以设置spark.serializer为org.apache.spark.serializer.KryoSerializer。为了更好的性能,你想要计划序列化也可以注册Kryo的类,注册一个类允许Kryo避免使用单独的对象编写完整的类名,节省的空间可以增加数以千计或数百万的序列化记录。如果你想强制这种类型的注册,你可以设置spark.kryo.registrationRequired为true,如果遇到未注册的类,Kryo会抛出错误。


Example 8-12. Using the Kryo serializer and registering classes
val conf = new SparkConf()
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// Be strict about class registration
conf.set("spark.kryo.registrationRequired", "true")
conf.registerKryoClasses(Array(classOf[MyClass], classOf[MyOtherClass]))


无论使用Kryo还是Java的序列化程序,都可能遇到NotSerializableExcep如果你的代码引用了一个没有扩展Java的Serializable接口的类。在这种情况下跟踪到具体哪个引起的问题,因为许多不同的类可以从用户代码引用。很多JVM提供提供选项来调试这种问题-Dsun.io.serialization.extended DebugInfo=true。也可以使用spark-submit的选项--driver-java-options和--executor-java-options。一旦你找到了这个问题,那么最简单的方法就是修改它实现Serializable。如果你无法修改类,那么需要使用更高级的方法。比如创建一个实现Java的Externalizable接口的类型的子类,或者使用Kryo自定义序列化行为。


内存管理


Spark以不同的方式使用内存,所以理解和调试Spark的内存使用可以优化你的应用程序。在每个executor的内部,内存用于几个目的:


RDD存储


当你在一个RDD上调用persist()或cache()方法时,它的分区会存储在内存buffer中。当缓冲整个JVM堆的一部分时候,Spark会限制内存使用的数量,通过设置spark.storage.memoryFraction。如果限制超过了,旧的分区会从内存中清除。


shuffle和聚合buffer
当执行shuffle操作时候,spark会创建用于存储shuffle输出数据的中间buffer。这些缓冲区用于存储聚合的中间结果,以及缓冲将作为shuffle的一部分直接输出的数据。Spark会尝试限制总的内存使用数量spark.shuffle.memoryFraction。


用户代码

Spark执行任意的用户代码,所以用户函数本身可能需要大量的内存。例如,如果用户应用程序分配大型数组或其他对象,这些将争用整体内存使用量。用户代码可以在分配RDD存储空间和随机存储空间之后访问JVM堆中剩下的所有内容。


默认情况下Spark会为RDD存储留下60%的空间,shuffle内存20%,用户程序20%。一些情况下用户可以调这些参数为了更好的性能。如果你的用户代码分配非常大的对象,降低存储和shuffle分区避免内存溢出可能是有意义的。


除了调整内存分区,为了一些工作负载你可以提高特定元素的Spark的默认缓存行为。Spark默认的cache()操作使用MEMORY_ONLY存储等级持久化内存。这就意味着,如果没有足够的空间缓存新的RDD分区,旧的会被简单的删除,如果再需要的话,他们会被重新计算。使用MEMORY_AND_DISK存储级别调用persist()有时候更好,MEMORY_AND_DISK会将RDD分区丢到磁盘,如果再需要他们时候会将本地存储读回内存。这可以比重新计算块便宜得多,并且可以导致更可预测的性能。如果你的RDD分区特别昂贵的话这是非常有用的(例如,从数据库读取数据)。


第二种改进是在默认的缓存策略序列化对象来代替原始java对象,你可以使用MEMORY_ONLY_SER或MEMORY_AND_DISK_SER存储级别。缓存序列化对象会由于序列化对象的代价而略微减慢缓存操作,因为许多单独的记录可以被存储为单个序列化的缓冲区。这是因为垃圾收集的成本随着堆中对象的数量而变化,而不是数据的字节数,并且这种缓存方法将获取许多对象并将它们序列化成单个巨型缓冲区。如果您将大量数据(例如千兆字节)缓存为对象或看到长时间的垃圾收集暂停,请考虑使用此选项。在每个任务的“GC时间”列下的应用程序UI中,可以看到这种暂停。


硬件配置


您给Spark的硬件资源将对您的应用程序的完成时间产生重大影响。主参数会影响集群中每个executor使用内存的数量,每个executor的核心数量,executor的总数量,用于临时数据的本地磁盘数量。


在所有的开发模式,executor内存是通过spark.executor.memory或spark-submit的--executor-memory选项控制的。执行程序的数量和内核选项根据部署模式而有所不同。在Mesos和Standalone模式,Spark会向计划器请求尽可能多的核和executor的数量。然而,Mesos和Standalone模式都提供了一个限制一个应用程序所有executor的最大核心数参数spark.cores.max。在shuffle操作中,本地磁盘用于临时存储。


一般来说,Spark应用程序会受益于更多的内存和核。Spark框架允许线性缩放;添加两倍的资源可以使你的应用快两倍。调整Spark应用程序的大小时需要考虑的另一个问题是,是否计划将中间数据集作为工作负载的一部分进行缓存。如果你计划使用高速缓存,越多的数据放到缓存,你就会获得更好的性能。Spark的storage UI会显示内存中缓存数据的部分详情。一种方法是首先将数据的一个子集缓存在一个较小的群集上,并推断出你需要将大量数据存储在内存中的总内存。


除了内存和核,Spark使用本地磁盘卷来存储shuffle操作期间所需的中间数据以及溢出到磁盘的RDD分区。使用较大数量的本地磁盘可以帮助加速Spark应用程序的性能。在YARN模式,本地磁盘的配置直接从YARN读取,它提供了自己的机制来指定临时存储目录。在Standalone模式,你可以在spark-env.sh中设置SPARK_LOCAL_DIRS环境变量,当部署一个Standalone集群时候,Spark应用程序启动的时候也会继承这些配置。在Mesos模式,或者你运行在另一个模式,想要覆盖集群默认存储位置,你可以设置spark.local.dir选项。在所有情况下,您都可以使用一个逗号分隔的列表来指定本地目录。
每个磁盘卷都有一个本地目录可供Spark使用。写入将被均匀分布在所有提供的本地目录中。 大量的磁盘将提供更高的整体吞吐量。


一个警告是“越多越好”是针对为executor设定内存大小的时候。使用非常大的堆会导致垃圾回收暂停,从而降低一个Spark job的吞吐量。请求较小的executor(64GB或更少)可能是有益的,Mesos和YARN可以支持将多个较小的executor包装到同一台物理主机上,因此请求较小的executor并不意味着您的应用程序将具有较少的整体资源。Spark的Standalone模式,你需要为一个应用程序启动多个worker(用SPARK_WORKER_INSTANCES),达到在一个主机上运行超过一个executor。这个限制可能会在Spark的更高版本中被删除。 此外使用更小的执行程序,以序列化的形式存储数据也可以帮助缓解垃圾收集。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Spark SQL是Spark中用于处理结构化数据的模块。它提供了一种基于DataFrame和SQL的编程接口,可以方便地进行数据分析和处理。Spark SQL支持多种数据源,包括Hive、JSON、Parquet等,可以通过SQL语句或DataFrame API进行数据查询和操作。Spark SQL还支持用户自定义函数(UDF)和聚合函数(UDAF),可以满足更复杂的数据处理需求。Spark SQL的优势在于它可以与Spark的其他模块无缝集成,如Spark Streaming、MLlib等,可以构建完整的数据处理和分析流程。 ### 回答2: 本篇笔记主要是介绍Spark SQL的基本概念和编程模型。 Spark SQL是面向Spark计算引擎的一种高性能的分布式数据处理技术,它提供一种基本的高度抽象的编程模型,使得开发大规模的数据仓库和数据分析应用变得容易和高效。 Spark SQL最核心的概念就是DataFrames,DataFrame是RDD的超集,提供了更高层次的抽象和对数据的结构化的处理能力,在数据处理的过程中常常会用到一些基本的操作:过滤、选择、聚合、排序等等,而这些操作都可以一步一步地以DataFrame为基础完成。 在使用Spark SQL的过程中,可以通过DataFrame API和Spark SQL语言两种方式进行编程。DataFrame API是Spark SQL提供的一种编程API,它提供了常见的操作,如选择、过滤和聚合等。而Spark SQL语言则是一种基于SQL的编程语言,和传统的SQL查询语言类似,可以通过SQL查询语句来对数据进行查询和操作。Spark SQL可以支持多种数据源,包括JSON、Parquet、ORC、Hive、JDBC等等,因此可以轻松地读取和处理不同类型的数据源。 Spark SQL还提供了高级的功能,如User-Defined Functions(UDFs)、Window Functions和Structured Streaming等等。UDFs允许开发者自定义函数并在Spark SQL中使用,将SQL和代码结合起来,提高了处理数据的灵活性和可扩展性;Window Functions则是一种用来进行滑动窗口操作的函数,常常用于计算数据的局部或全局统计量;Structured Streaming提供了数据流处理的能力,并且实现了端到端的Exactly-Once语义。 总之,Spark SQL提供了很多的功能和便利,特别是在大数据处理和分析领域,它的优势尤为突出。结合Spark的强大计算能力和Spark SQL的抽象编程模型,在大规模的数据分析和仓库方面都具有非常高的可扩展性和灵活性。 ### 回答3: Spark SQL是Spark生态系统中的一个组件,它负责处理结构化数据。它提供了SQL查询和DataFrame API,可以从不同的数据源中读取和处理数据。Spark SQL能够理解SQL语言,这使得开发人员可以使用传统的SQL查询方式来处理数据,同时还可以利用Spark的优势,例如分布式计算和内存缓存。 Spark SQL支持许多不同类型的数据源,包括Hive表、传统的RDD、Parquet文件、JSON文件、CSV文件和JDBC数据源等。Spark SQL可以通过使用数据源API将这些数据源加载到Spark中,然后可以在Spark中处理和查询这些数据。 Spark SQL还支持特定于数据源的优化器和执行引擎,这允许Spark SQL针对不同的数据源执行优化操作。例如,使用Hive数据源时,Spark SQL会使用Hive的元数据来优化查询计划。当使用Parquet文件格式时,Spark SQL会使用Parquet文件中的元数据来优化查询计划。 在Spark SQL中,DataFrame是一种非常重要的概念。它是一种强类型的分布式数据集,可以使用DataFrame API进行操作。DataFrame API是一种更面向数据的API,例如过滤数据、聚合数据等。Spark SQL中的DataFrame可以看作是类似于表的对象,它可以和Spark SQL中的SQL查询混合使用。 除了DataFrame API和SQL查询,Spark SQL还支持UDF(用户自定义函数)。UDF允许用户在SQL查询或DataFrame API中定义自己的函数,以实现更复杂的数据操作。使用UDF时,用户可以使用累加器和广播变量等Spark的分布式计算功能,使得UDF具备高性能和可伸缩性。 总之,Spark SQL是大数据处理领域中一种非常方便和强大的处理结构化数据的工具。它可以方便地与其他Spark组件结合使用,例如Spark Streaming、Spark MLlib等。使用Spark SQL,开发人员可以在不同的数据源之间轻松地查询和转换数据,并利用Spark分布式计算的优势,实现高性能和可伸缩性的数据处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艺菲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值