web-dev-qa-db-ja.com

Apache Spark:ペアRDDをキーで複数のRDDに分割して値を保存する

Spark 1.0.1を使用して大量のデータを処理します。各行にはID番号が含まれ、一部には重複したIDが含まれます。同じID番号を持つすべての行を同じ場所ですが、効率的に実行するのに問題があります(ID番号、データ行)のペアのRDD [(String、String)]を作成します。

val mapRdd = rdd.map{ x=> (x.split("\\t+")(1), x)} 

動作しますが、パフォーマンスは低下しますが、ID番号を収集し、各IDのRDDをフィルター処理し、テキストファイルと同じIDの値のRDDを保存します。

val ids = rdd.keys.distinct.collect
ids.foreach({ id =>
    val dataRows = mapRdd.filter(_._1 == id).values
    dataRows.saveAsTextFile(id)
})

また、groupByKeyまたはreduceByKeyを試して、RDDの各タプルにキーとして一意のID番号と、そのID番号の改行で区切られた結合データ行の文字列が含まれるようにしました。 foreachを使用してデータを保存するためにRDDを1回だけ繰り返したいが、RDDとして値を与えることができない

groupedRdd.foreach({ tup =>
  val data = sc.parallelize(List(tup._2)) //nested RDD does not work
  data.saveAsTextFile(tup._1)
})

基本的に、ID番号によってRDDを複数のRDDに分割し、そのID番号の値を独自の場所に保存する必要があります。

31
smli

この問題は キーによる複数の出力への書き込みSpark-one Spark job

回答を参照してください。

import org.Apache.hadoop.io.NullWritable

import org.Apache.spark._
import org.Apache.spark.SparkContext._

import org.Apache.hadoop.mapred.lib.MultipleTextOutputFormat

class RDDMultipleTextOutputFormat extends MultipleTextOutputFormat[Any, Any] {
  override def generateActualKey(key: Any, value: Any): Any = 
    NullWritable.get()

  override def generateFileNameForKeyValue(key: Any, value: Any, name: String): String = 
    key.asInstanceOf[String]
}

object Split {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("Split" + args(1))
    val sc = new SparkContext(conf)
    sc.textFile("input/path")
    .map(a => (k, v)) // Your own implementation
    .partitionBy(new HashPartitioner(num))
    .saveAsHadoopFile("output/path", classOf[String], classOf[String],
      classOf[RDDMultipleTextOutputFormat])
    spark.stop()
  }
}

上記の同様の回答を見ましたが、実際にはカスタマイズされたパーティションは必要ありません。 MultipleTextOutputFormatは、各キーのファイルを作成します。同じキーを持つ複数のレコードが同じパーティションに分類されることは問題ありません。

new HashPartitioner(num)。numは必要なパーティション番号です。多数の異なるキーがある場合は、numberをbigに設定できます。この場合、各パーティションは多くのhdfsファイルハンドラーを開きません。

13
zhang zhan

これにより、ユーザーIDごとにデータが保存されます

val mapRdd = rdd.map{ x=> (x.split("\\t+")(1),
x)}.groupByKey(numPartitions).saveAsObjectFile("file")

ユーザーIDに基づいてデータを再度取得する必要がある場合は、次のようなことができます

val userIdLookupTable = sc.objectFile("file").cache() //could use persist() if data is to big for memory  
val data = userIdLookupTable.lookup(id) //note this returns a sequence, in this case you can just get the first one  

この場合、ファイルに保存する特別な理由はないことに注意してください。私はOPがそれを要求したので、私はそれをしただけです。ファイルに保存すると言われているので、最初のグループ化が完了した後いつでもRDDをロードできます。

最後に、lookupはIDにアクセスするフィルターアプローチよりも高速ですが、sparkからプルリクエストを送信する場合は、チェックアウトできます this answer より速いアプローチのために

0
aaronman