收集方法

时间:2017-11-30 11:36:49

标签: scala apache-spark spark-dataframe

我们目前在使用scala语言编写的sparksql中面临性能问题。申请流程如下所述。

  1. Spark应用程序从输入hdfs目录
  2. 读取文本文件
  3. 使用以编程方式指定架构在文件顶部创建数据框。此数据帧将保留在内存中的输入文件的精确复制。数据框中将有大约18列

    var eqpDF = sqlContext.createDataFrame(eqpRowRdd, eqpSchema)

  4. 从步骤2中构建的第一个数据框创建过滤后的数据框。此数据框将在不同关键字的帮助下包含唯一的帐号。

    var distAccNrsDF = eqpDF.select("accountnumber").distinct().collect()

  5. 使用步骤2和2中构建的两个数据帧。 3,我们将获取属于一个帐号的所有记录,并在过滤后的数据之上执行一些Json解析逻辑。

    var filtrEqpDF = eqpDF.where("accountnumber='" + data.getString(0) + "'").collect()

  6. 最后,json解析数据将被放入Hbase表

  7. 在这里,我们在数据框之上调用collect方法时遇到了性能问题。因为collect会将所有数据提取到单个节点然后进行处理,从而失去并行处理的好处。 同样在实际情况中,我们可以预期有100亿条数据记录。因此,由于内存或磁盘空间的限制,将所有这些记录收集到驱动程序节点中可能会使程序本身崩溃。

    我不认为在我们的情况下可以使用take方法,它将一次获取有限数量的记录。我们必须从整个数据中获取所有唯一的帐号,因此我不确定是否采用方法 一次有限的记录,将符合我们的要求

    感谢任何帮助以避免调用收集方法并遵循其他一些最佳做法。如果有人遇到类似的问题,代码片段/建议/ git链接将非常有用

    代码段

        val eqpSchemaString = "acoountnumber ....."
      val eqpSchema = StructType(eqpSchemaString.split(" ").map(fieldName => 
    StructField(fieldName, StringType, true))); 
        val eqpRdd = sc.textFile(inputPath)
        val eqpRowRdd = eqpRdd.map(_.split(",")).map(eqpRow => Row(eqpRow(0).trim, eqpRow(1).trim, ....)
    
        var eqpDF = sqlContext.createDataFrame(eqpRowRdd, eqpSchema);
    
    
        var distAccNrsDF = eqpDF.select("accountnumber").distinct().collect()
    
    
        distAccNrsDF.foreach { data =>
    
          var filtrEqpDF = eqpDF.where("accountnumber='" + data.getString(0) + "'").collect()
    
    
    
          var result = new JSONObject()
    
          result.put("jsonSchemaVersion", "1.0")
          val firstRowAcc = filtrEqpDF(0)
          //Json parsing logic 
          {
             .....
             .....
          }
        }
    

2 个答案:

答案 0 :(得分:2)

这种情况通常采用的方法是:

  • 而不是collect,调用foreachPartitionforeachPartition将函数分别应用于基础Iterator[Row]的每个分区(由DataFrame表示)(分区是Spark的并行性的原子单位)
  • 该函数将打开与HBase的连接(因此每个分区一个)并通过此连接发送所有包含的值

这意味着每个执行程序都会打开一个连接(它不是可序列化的,但它位于函数的边界内,因此不需要通过网络发送)并独立地将其内容发送到HBase,而无需收集所有数据在驱动程序(或任何一个节点,就此而言)。

看起来你正在阅读一个CSV文件,所以类似下面这样的东西可以解决这个问题:

spark.read.csv(inputPath).         // Using DataFrameReader but your way works too
  foreachPartition { rows =>
    val conn = ???                 // Create HBase connection
    for (row <- rows) {            // Loop over the iterator
      val data = parseJson(row)    // Your parsing logic
      ???                          // Use 'conn' to save 'data'
    }
  }

答案 1 :(得分:2)

如果您拥有大量数据,则可以在代码中忽略collect。

收集在驱动程序中将数据集的所有元素作为数组返回。在过滤器或其他返回足够小的数据子集的操作之后,这通常很有用。

这也会导致驱动程序内存不足,因为collect()会将整个RDD / DF提取到一台机器上。

我刚刚编辑了你的代码,这应该适合你。

        var distAccNrsDF = eqpDF.select("accountnumber").distinct()
            distAccNrsDF.foreach { data =>
              var filtrEqpDF = eqpDF.where("accountnumber='" + data.getString(0) + "'")
              var result = new JSONObject()
              result.put("jsonSchemaVersion", "1.0")
              val firstRowAcc = filtrEqpDF(0)
              //Json parsing logic 
              {
                 .....
                 .....
              }
            }