Spark调优与调试

SparkConf

Spark 中最主要的配置机制是通过 SparkConf 类对 Spark 进行配置。在创建 SparkContext 之前,需要创建出一个 SparkConf 的实例。

conf = SparkConf()
conf.set("spark.app.name", "My Spark App")
conf.set("spark.master", "local[4]")
conf.set("spark.ui.port", "36000")  # 重载默认端口配置

sc = SparkContext(conf=conf)

tf = sc.textFile('README.md')
while True:
    print(tf.filter(lambda x: "Spark" in x).count())
    time.sleep(2)

打开新端口页面:http://192.168.33.32:36000/jobs/

动态配置

在很多情况下,动态地为给定应用设置配置选项会方便得多。 Spark 允许通过 spark-submit 工具动态设置配置项。当一个新的 SparkConf 被创建出来时,这些环境变量会被检测出来并且自动配好。

这样,在使用spark-submit 时,用户应用中只要创建一个“空”的 SparkConf,并直接传给 SparkContext的构造方法就行了。

$ bin/spark-submit \
    --master local[4] \
    --name "My Spark App" \
    --conf spark.ui.port=36000 \

spark-submit 也支持从文件中读取配置项的值:
你也可以通过 spark-submit 的 –properties-file 标记,自定义该文件的路径。

$ bin/spark-submit \
    --properties-file my-config.conf \
# my-config.conf
spark.master local[4]
spark.app.name "My Spark App"
spark.ui.port 36000

Spark配置文件读取优先级:
1. 最高级:用户代码中显式调用 set() 方法设置的选项
2. spark-submit 传递的参数
3. 写在配置文件中的值
4. 最低级:系统的默认值

常用配置项

完整配置项地址:http://spark.apache.org/docs/latest/configuration.html

选项默认值描述
spark.executor.memory(–executor-memory)512m执行器进程分配的内存
spark.executor.cores(–executor-cores)1限制应用使用的核心个数的配置项,在 YARN 模式下,每个任务分配指定数目的核心
spark.cores.max(–total-executor-cores)1在独立模式和 Mesos 模式下,设置所有执行器进程使用的核心总数的上限
spark.speculationfalse设为 true 时开启任务预测执行机制。当出现比较慢的任务时,这种机制会在另外的节点上也尝试执行该任务的一个副本。打开此选项会帮助减少大规模集群中个别较慢的任务带来的影响
spark.storage.blockManagerTimeoutIntervalMs45000(毫秒)内部用来通过超时机制追踪执行器进程是否存活的阈值
spark.serializerorg.apache.spark.serializer.JavaSerializer指定用来进行序列化的类库,包括通过网络传输数据或缓存数据时的序列化。我们推荐在追求速度时使用org.apache.spark.serializer.KryoSerializer 并且对 Kryo 进行适当的调优
spark.[X].port(任意值)用来设置运行 Spark 应用时用到的各个端口。这些参数对于运行在可靠网络上的集群是很有用的。 有效的 X 包括 driver、fileserver、broadcast、replClassServer、blockManager,以及executor
spark.eventLog.enabledfalse设为 true 时,开启事件日志机制
spark.eventLog.dirfile:///tmp/spark-events指开启事件日志机制时,事件日志文件的存储位置

Spark执行的组成部分

README.md

INFO This is a message with content
INFO This is some other content

INFO Here are more messages
WARN This is a warning

ERROR Something bad happened
WARN More details on the bad thing
INFO back to normal messages

sp_test.py

tf = sc.textFile('README.md')
tokenized = tf.map(lambda x: x.split(" ")).filter(lambda x: len(x) > 1)
counts = tokenized.map(lambda x: (x[0], 1)).reduceByKey(lambda x, y: x + y)
print(counts.collect())

这一系列命令生成了一个叫作 counts 的 RDD,其中包含各级别日志对应的条目数。

程序定义了一个 RDD对象的有向无环图(DAG),我们可以在稍后Action操作被触发时用它来进行计算。每个RDD 维护了其指向一个或多个父节点的引用, 以及表示其与父节点之间关系的信息(b = a.map() 时, b 这个 RDD 就存下了父节点 a 的一个引用,这些引用使得 RDD 可以追踪到其所有的祖先节点)。

打印Spark RDD运行流程

Spark 提供了 toDebugString() 方法来查看 RDD 的谱系:

print(tf.toDebugString())
(2) README.md MapPartitionsRDD[1] at textFile at NativeMethodAccessorImpl.java:0 []
 |  README.md HadoopRDD[0] at textFile at NativeMethodAccessorImpl.java:0 []

print(counts.toDebugString())
(2) PythonRDD[7] at RDD at PythonRDD.scala:48 []
 |  MapPartitionsRDD[6] at mapPartitions at PythonRDD.scala:427 []
 |  ShuffledRDD[5] at partitionBy at NativeMethodAccessorImpl.java:0 []
 +-(2) PairwiseRDD[4] at reduceByKey at /vagrant/test/sp_test.py:20 []
    |  PythonRDD[3] at reduceByKey at /vagrant/test/sp_test.py:20 []
    |  README.md MapPartitionsRDD[1] at textFile at NativeMethodAccessorImpl.java:0 []
    |  README.md HadoopRDD[0] at textFile at NativeMethodAccessorImpl.java:0 []

第一条命令输出了 RDDinput 的相关信息,sc.textFile() 方法所创建出的 RDD 类型先创建出了一个HadoopRDD 对象,然后对该 RDD执行映射操作, 最终得到了返回的 RDD。

counts 的谱系图则更加复杂一些,有多个祖先RDD,这是由于我们对 input 进行了其他操作,包括额外的映射、筛选以及归约。

在调用Action之前, RDD 都只是存储着可以计算的描述信息。要触发计算,需要对 counts 调用一个Action操作,比如使用 collect() 。

Spark 调度器会创建出用于计算Action操作的 RDD 物理执行计划。例如调用 RDD 的collect() 方法,每个分区都会被物化出来并发送到驱动器程序中。Spark 调度器从最终被调用Action操作的 RDD(在本例中是 counts)出发,向上回溯所有必须计算的 RDD。 递归向上生成计算必要的祖先 RDD 的物理计划,然后以相反的顺序执行这些步骤,计算得出最终所求的 RDD。

优化策略

输出的谱系图使用不同缩进等级来展示 RDD 是否会在物理步骤中进行流水线执行。例如,当计算 counts 时,尽管有很多级父 RDD,但从缩进来看总共只有两级,这表明物理执行只需要两个步骤。由于执行序列中有几个连续的筛选和映射操作,这个例子中才出现了流水线执行。

除了流水线执行的优化, 当一个 RDD 已经缓存在集群内存或磁盘上时, Spark 的内部调度器也会自动截短 RDD 谱系图。在这种情况下, Spark 会“短路”求值,直接基于缓存下来的 RDD 进行计算。还有一种截短 RDD 谱系图的情况发生在当 RDD 已经在之前的数据混洗中作为副产品物化出来时。

一个物理步骤会启动很多任务, 每个任务都是在不同的partition上做同样的事情:
1. 从数据存储(输入 RDD)或已有 RDD(基于已经缓存的数据)或数据混洗的输出中获取数据。
2. 对输入的RDD进行操作,例如filter(),map()
3. 把输出写到一个数据混洗文件中, 写入外部存储,或者是发回驱动器程序

归纳一下, Spark 执行时有下面所列的这些流程:
1. 用户代码定义RDD的有向无环图,RDD 上的操作会创建出新的 RDD,并引用它们的父节点,这样就创建出了一个图
2. Action把有向无环图强制转译为执行计划,Spark 调度器提交一个作业来计算所有必要的 RDD。 这个作业包含一个或多个步骤,每个步骤其实也就是一波并行执行的计算任务。一个步骤对应有向无环图中的一个或多个 RDD,一个步骤对应多个 RDD 是因为发生了流水线执行。
3. 任务在集群中调度并执行

关键性能考量

并行度

RDD 的逻辑表示其实是一个对象集合,在物理执行期间,RDD 会被分为一系列的partition, 每个partition都是整个数据的子集。当 Spark 调度并运行任务时, Spark 会为每个分区中的数据创建出一个任务,该任务在默认情况下会需要集群中的一个计算核心来执行。

Spark 也会针对 RDD 直接自动推断出合适的并行度,这对于大多数用例来说已经足够了。 输入 RDD 一般会根据其底层的存储系统选择并行度。

并行度会从两方面影响程序的性能:
1. 当并行度过低时, Spark 集群会出现资源闲置的情况。比如,假设你的应用有 1000 个可使用的计算核心,但所运行的步骤只有 30 个任务,你就应该提高并行度来充分利用更多的计算核心。
2. 当并行度过高时,每个分区产生的间接开销累计起来就会很大。

评判并行度是否过高的标准包括任务是否是瞬间(毫秒级)完成或者是否有读写任何数据。

Spark 提供了两种方法来对操作的并行度进行调优:
1. 在数据混洗操作时,使用参数的方式为混洗后的 RDD 指定并行度
2. 对于任何已有的 RDD,可以进行重新分区来获取更多或者更少的分区数,重新分区操作通过 repartition()实现,该操作会把 RDD 随机打乱并分成设定的分区数目。如果你确定要减少 RDD 分区,可以使用coalesce() 操作。由于没有打乱数据,该操作比 repartition() 更为高效。

序列化格式

当 Spark 需要通过网络传输数据,或是将数据写到磁盘上时, Spark 需要把数据序列化为二进制格式。序列化会在数据进行混洗操作时发生,此时有可能需要通过网络传输大量数据。

Spark 支持使用第三方序列化库 Kryo(https://github.com/EsotericSoftware/kryo) 可以提供比 Java 的序列化工具更短的序列化时间和更高压缩比的二进制表示。

要使用 Kryo 序列化工具,你需要设置 spark.serializer 为 org.apache.spark.serializer.KryoSerializer。

内存管理

内存对 Spark 来说有几种不同的用途:
1. RDD存储:当调用 RDD 的 persist() 或 cache() 方法时,这个 RDD 的分区会被存储到缓存区中。
2. 数据混洗与聚合的缓存区:当进行数据混洗操作时, Spark 会创建出一些中间缓存区来存储数据混洗的输出数据。
3. 用户代码:Spark 可以执行任意的用户代码, 所以用户的函数可以自行申请大量内存。

在默认情况下, Spark 会使用 60%的空间来存储 RDD, 20% 存储数据混洗操作产生的数据,剩下的 20% 留给用户程序。

以 MEMORY_AND_DISK 的存储等级调用 persist() 方法会获得更好的效果,因为在这种存储等级下, 内存中放不下的旧分区会被写入磁盘,当再次需要用到的时候再从磁盘上读取回来。

对于默认缓存策略的另一个改进是缓存序列化后的对象而非直接缓存。我们可以通过MEMORY_ONLY_SER 或者 MEMORY_AND_DISK_SER 的存储等级来实现这一点。缓存序列化后的对象会使缓存过程变慢, 因为序列化对象也会消耗一些代价,不过这可以显著减少 JVM 的垃圾回收时间, 因为很多独立的记录现在可以作为单个序列化的缓存而存储。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spark SQL的调优参数可以分为以下几类: 1. Spark SQL的通用参数: - `spark.sql.shuffle.partitions`:控制shuffle操作时的分区数,默认值为200。 - `spark.sql.autoBroadcastJoinThreshold`:控制自动广播的表大小,默认为10MB。 - `spark.sql.broadcastTimeout`:控制广播超时时间,默认值为5分钟。 - `spark.sql.execution.arrow.enabled`:控制是否启用Arrow优化,默认为false。 2. Spark SQL的查询优化参数: - `spark.sql.cbo.enabled`:控制是否启用基于成本的优化器(CBO),默认为false。 - `spark.sql.statistics.histogram.enabled`:控制是否启用直方图统计信息,默认为false。 - `spark.sql.statistics.ndv.scale.factor`:控制基数估计的规模因子,默认为2.0。 - `spark.sql.inMemoryColumnarStorage.compressed`:控制是否启用列式存储压缩,默认为true。 - `spark.sql.adaptive.enabled`:控制是否启用自适应查询执行,默认为false。 3. Spark SQL的内存管理参数: - `spark.sql.shuffle.memoryFraction`:控制shuffle操作的内存占比,默认为0.2。 - `spark.sql.execution.arrow.maxRecordsPerBatch`:控制Arrow批处理的最大行数,默认为1024。 - `spark.sql.execution.sort.externalSorter.maxMemoryUsage`:控制外部排序的内存占比,默认为0.4。 - `spark.sql.execution.arrow.fallback.enabled`:控制是否开启Arrow优化失败时的回退机制,默认为true。 4. Spark SQL的调试参数: - `spark.sql.debug.maxToStringFields`:控制最大的toString字段数目,默认为25。 - `spark.sql.debug.maxPlanStringLength`:控制最大的计划字符串长度,默认为1000。 以上仅列举了一部分常用的Spark SQL调优参数,具体使用时需要根据实际情况进行选择和调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值