Spark性能调优 - 关键性能考量

一、RDD并行度

默认情况下,Spark会对RDD自动分配合适的并行度,但这并不总是有效的。
Spark提供两种方法对操作的并行度进行调优:

  • 第一种方法是在数据混洗操作时,使用参数的方式为混洗后的RDD指定并行度

  • 第二种方法是对于任何已有的RDD,可以进行重新分区来获取更多或者更少的分区数。

    重新分区操作通过 repartition() 实现,该操作会把RDD随机打乱并分成设定的分区数目。
    如果确定要减少RDD分区,可使用 coalesce() 操作。由于没有打乱数据,该操作比 repartition() 更高效。

    举个例子,假设我们读取大量数据,然后马上进行filter()操作筛选掉数据集中绝大部分数据。默认情况下,filter()返回的RDD的分区数和其父节点一样,这样可能会产生很多空的分区或者只有很少数据的分区。在这种情况下,可通过合并得到分区更少的RDD来提高应用性能。如下:

    例:在PySpark shell中合并分区过多的RDD
    # RDD输入
    >>> input = sc.textFile("/data/*.log")
    >>> input.getNumPartitions()
    35000
    # 排除掉大部分数据的筛选方法
    >>> lines = input.filter(lambda line: line.startswith("2019-01-01"))
    >>> lines.getNumPartitions()
    4
    # 可以在合并之后的RDD进行后续分析
    >>> lines.count()
    

二、数据序列化格式

当Spark需要通过网络传输数据,或将数据溢写到磁盘上时,Spark需要把数据序列化为二进制格式。序列化会在数据进行混洗操作时发生,此时可能需要通过网络传输大量数据。默认情况下,Spark会使用Java内建的序列化库,第三方库Kryo有更好性能。

使用Kryo序列化工具:

  • 需设置 spark.serializerorg.apache.spark.serializer.KryoSerializer .
  • 向Kryo注册需要序列化的类,以获得最佳性能。若想强制要求注册,可设置 spark.kryo.registrationRequired => true
#使用Kryo序列化工具并注册所需类
val conf = new SparkConf()
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// 严格要求注册类
conf.set("spark.kryo.registrationRequired", "true")
conf.registerKryoClasses(Array(classOf[MyClass], classOf[MyOtherClass]))

不论是选用Kryo还是Java序列化,如果代码中引用到了一个没有扩展Java的Serializable接口的类,你都会遇到NotSerializableException。
很多JVM都支持通过设置选项 -Dsun.io.serialization.extended DebugInfo=true 来帮助调试,

我们可通过设置 spark-submit 的:
–driver-java-options 以及 --executor-java-options 来打开JVM设置选项

三、内存管理

在各个执行器进程中,内存有如下所列几种用途

  • RDD存储
    当调用RDD的persist() 或 cache() 方法时,这个RDD的分区会被存储到缓存区中。Spark会根据 spark.storage.memoryFraction 限制用来缓存的内存占整个JVM堆空间的比例大小。如果超出限制,旧的分区数据会被移出内存。

  • 数据混洗与聚合的缓存区
    当进行数据混洗操作是,Spark会创建出溢写中间缓存区:

    • 存储聚合操作的中间结果
    • 数据混洗操作中直接输出的部分缓存数据

    spark.shuffle.memoryFraction 可用于限定缓存区内存占总内存的比例

  • 用户代码
    Spark可执行用户的任意合法代码,用户的函数可自行申请大量内存。

在默认情况下,Spark会使用60%的空间来存储RDD,20%存储数据混洗操作产生的数据,剩下20%留给用户程序。用户可自行调节这些选项来追求更好的性能表现。

四、硬件供给

提供Spark的硬件资源会显著影响应用完成时间。影响集群规模的主要参数包括:

  • 分配给每个执行器节点的内存大小
  • 每个执行器节点占用核心数
  • 执行器节点总数
  • 用于存储临时数据的本地磁盘数

1. 执行器节点内存

通过设置 spark.executor.memory 或者 spark-submit 的 --executor-memory 标记设置 执行器节点内存

切记:“越多越好”原则在设置执行器节点内存时并不一定适用。使用巨大的堆空间可能会导致垃圾回收长时间暂停,从而严重影响Spark作业吞吐量。

缓解 长时间垃圾回收暂停 办法:

  1. 使用较小内存(比如不超过64GB)的执行器实例可缓解长时间垃圾回收暂停。
  2. 使用序列化格式存储大对象数据

2. 执行器节点占用核心数及节点总数

执行器节点数目以及每个执行器进程的核心数的配置选项则取决于各种部署模式

  1. 在YARN模式下,可以通过 spark.executor.cores 或 --executor-cores 标记来设置 执行器节点核心数
    可通过设置 --num-executors 设置 执行器节点总数
  2. 在Mesos和独立模式中, Spark会从调度器提供的资源中获取尽可能多的核心用于 执行器节点
    也可通过 spark.cores.max 限制一个应用中所有执行器节点所使用的核心总数。

3. 本地磁盘数

本地磁盘可存储数据混洗操作的中间数据,以及溢写到磁盘中的RDD分区数据。因此大量使用本地磁盘可以帮助提升Spark应用性能。

  1. Yarn模式下
    Spark 本地磁盘配置项会直接从YARN的配置中读取
  2. 独立模式下
    spark-env.sh 中设置环境变量 SPARK_LOCAL_DIRS
  3. Mesos或其它模式
    spark.local.dir 选项可重载集群默认存储位置

参考

  1. 《快速大数据分析》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章