使用gzip格式的大文本文件的Spark作业

时间:2016-06-11 06:34:40

标签: hadoop apache-spark amazon-s3 spark-dataframe parquet

我正在运行一个Spark作业,这个作业花费了太长时间来处理输入文件。输入文件为Gzip格式的6.8 GB,包含110 M行文本。我知道它是Gzip格式的,因此它不可拆分,只有一个执行器将用于读取该文件。

作为调试过程的一部分,我决定只看看将gzip文件转换为镶木地板需要多长时间。我的想法是,一旦我转换为镶木地板文件,然后如果我在该文件上运行我的原始Spark作业,那么它将使用多个执行程序并且输入文件将被并行处理。

但即便是小工作也要花很长时间才能达到预期。这是我的代码:

val input = sqlContext.read.text("input.gz")
input.write.parquet("s3n://temp-output/")

当我在笔记本电脑(16 GB RAM)中提取该文件时,花了不到2分钟。当我在Spark集群上运行它时,我的期望是它将花费相同甚至更少的时间,因为我使用的执行程序内存是58 GB。花了大约20分钟。

我在这里缺少什么?我很抱歉,如果这听起来很业余,但我在Spark中相当新。

在gzip文件上运行Spark作业的最佳方法是什么?假设我没有选择以其他文件格式创建该文件(bzip2,snappy,lzo)。

1 个答案:

答案 0 :(得分:6)

在执行输入流程输出类型的Spark作业时,需要考虑三个不同的问题:

  1. 输入并行度
  2. 处理并行性
  3. 输出并行度
  4. 在您的情况下,输入并行度为1,因为在您的问题中,您声称无法更改输入格式或粒度。

    你也基本上没有处理过,所以你无法获得任何收益。

    但是,您可以控制输出并行性,这将带来两个好处:

    • 将写入多个CPU,从而减少写入操作的总时间。

    • 您的输出将分成多个文件,以便您在以后的处理中利用输入并行性。

    要提高并行度,您必须增加分区数量,您可以使用repartition()进行分区,例如

    val numPartitions = ...
    input.repartition(numPartitions).write.parquet("s3n://temp-output/")
    

    在选择最佳分区数时,需要考虑许多不同的因素。

    • 数据大小
    • Parition skew
    • 群集RAM大小
    • 群集中的核心数
    • 您将要进行的后续处理类型
    • 您将用于后续处理的群集(RAM和核心)的大小
    • 您要写的系统

    如果不了解您的目标和限制条件,很难提出可行的建议,但这里有一些常规指南可供使用:

    • 由于您的分区不会出现偏差(以上repartition的使用将使用纠正偏斜的散列分区器),如果您将分区数设置为等于假设您正在使用具有足够I / O的节点,则执行程序核心数量。

    • 处理数据时,您确实希望整个分区能够“适应”分配给单个执行程序核心的RAM。什么“适合”在这里意味着取决于您的处理。如果您正在进行简单的map转换,则可以对数据进行流式处理。如果您正在做涉及订购的事情,那么RAM需要大幅增长。如果您使用Spark 1.6+,您将获得更灵活的内存管理的好处。如果您使用的是早期版本,则必须更加小心。当Spark必须开始“缓冲”到磁盘时,作业执行停止。磁盘大小和内存大小可能非常不同。后者根据您处理数据的方式以及Spark从谓词下推获得的好处(Parquet支持)而有所不同。使用Spark UI查看各个工作阶段需要多少RAM。

    顺便说一句,除非您的数据具有非常特定的结构,否则不要硬编码分区号,因为那时您的代码将在不同大小的集群上以次优的方式运行。而是,使用以下技巧来确定群集中的执行程序数。然后,您可以根据您使用的计算机乘以每个执行程序的核心数。

    // -1 is for the driver node
    val numExecutors = sparkContext.getExecutorStorageStatus.length - 1
    

    作为参考点,在我们的团队中,我们使用相当复杂的数据结构,这意味着RAM大小>>磁盘大小,我们的目标是将S3对象保持在50-250Mb范围内,以便在每个执行器核心具有10-20Gb RAM的节点上进行处理。

    希望这有帮助。