foreachPartitions
は、アキュムレータ変数に合計を実行するためにforeach
を介して流れる場合を考慮したRDD
メソッドと比較して、より高いレベルの並列処理により、パフォーマンスが向上するかどうかを知りたいと思います。
foreach
およびforeachPartitions
はアクションです。
副作用を伴う操作を呼び出すための汎用関数。 RDDの各要素に対して、渡されたfunctionを呼び出します。 これは通常、アキュムレーターの操作または外部ストアへの書き込みに使用されます。
注:foreach()
の外部のアキュムレーター以外の変数を変更すると、未定義の動作が発生する場合があります。詳細については、 クロージャを理解する を参照してください。
例 :
scala> val accum = sc.longAccumulator("My Accumulator")
accum: org.Apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(My Accumulator), value: 0)
scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s
scala> accum.value
res2: Long = 10
foreach()
に似ていますが、各要素に対して関数を呼び出す代わりに、パーティションごとに呼び出します。関数は反復子を受け入れることができる必要があります。これは、mapPartitions
()と同様に、関数呼び出しの回数を減らすため、foreach()
よりも効率的です。
foreachPartition
の使用例:
/** * foreachパーティションを使用してデータベースに挿入します。 * * @param sqlDatabaseConnectionString * @param sqlTableName */ def insertToTable(sqlDatabaseConnectionString:String、sqlTableName:String):Unit = { // numPartitions =与えることを計画できる同時DB接続の数 datframe.repartition(numofpartitionsyouwant) val tableHeader:String = dataFrame.columns.mkString( "、") dataFrame.foreachPartition {partition => //注:各パーティションの1つの接続(より良い方法は、接続プールを使用することです) val sqlExecutorConnection:Connection = DriverManager.getConnection(sqlDatabaseConnectionString) //バッチサイズ1000一部のデータベースはexに1000を超えるバッチサイズを使用できないため、Azure sql partition.grouped(1000).foreach { group => val insertString:sca la.collection.mutable.StringBuilder = new scala.collection.mutable.StringBuilder() group.foreach { record => insertString.append( "( '" + record.mkString( "、 ")+" ')、 ") } sqlExecutorConnection.createStatement() .executeUpdate(f" INSERT INTO [$ sqlTableName]($ tableHeader)値 " + insertString.stripSuffix("、 ")) } sqlExecutorConnection.close()//接続を閉じて、接続は枯渇しません。 } }
sparkstreaming(dstreams)とkafka producerのforeachPartition
の使用法
dstream.foreachRDD { rdd =>
rdd.foreachPartition { partitionOfRecords =>
// only once per partition You can safely share a thread-safe Kafka //producer instance.
val producer = createKafkaProducer()
partitionOfRecords.foreach { message =>
producer.send(message)
}
producer.close()
}
}
注:パーティションごとにプロデューサーを作成するこの方法を避けたい場合は、Kafkaプロデューサーは非同期でバッファーであるため、
sparkContext.broadcast
を使用してプロデューサーをブロードキャストすることをお勧めします送信する前に大量のデータ。
アキュムレータは、スニペットを試してみて、それをいじってみます...それを通して、パフォーマンスをテストできます
test( "Foreach-Spark"){ import spark.implicits ._ var accum = sc.longAccumulator sc.parallelize(Seq(1,2 、3))。foreach(x => accum.add(x)) assert(accum.value == 6L) } test( " Foreachパーティション-Spark "){ import spark.implicits ._ var accum = sc.longAccumulator sc.parallelize(Seq(1,2,3))。foreachPartition( x => x.foreach(accum.add(_))) assert(accum.value == 6L) }
foreachPartition
パーティションの操作なので、明らかに[foreach
foreachPartition
は、データベース接続やkafkaプロデューサーなど、要素ごとに1つではなくパーティションごとに1つを初期化する高価なリソース(foreach
)にアクセスするときに使用する必要があります。アキュムレータに関しては、上記のテスト方法でパフォーマンスを測定できます。アキュムレータの場合も同様に高速に動作するはずです。
また... map vs mappartitions を参照してください。これは同様の概念を持っていますが、変換です。
foreach
は、多くのノードでループを自動実行します。
ただし、各ノードでいくつかの操作を行いたい場合があります。たとえば、データベースに接続します。接続を確立してforeach
関数に渡すことはできません。接続は1つのノードでのみ行われます。
したがって、foreachPartition
を使用すると、ループを実行する前に各ノードでデータベースに接続できます。
foreach
とforeachPartitions
にはそれほど違いはありません。内部では、foreach
が実行しているのは、提供された関数を使用してイテレーターのforeach
を呼び出すことだけです。 foreachPartition
は、イテレータのループの外側で何かをする機会を与えてくれます。通常は、データベース接続をスピンアップするような高価なものや、それらの行に沿ったものです。したがって、各ノードのイテレータに対して一度だけ実行して、全体を通して再利用できるものがない場合は、foreach
を使用して明快さと複雑さを軽減することをお勧めします。
foreachPartition
は、ノードごとのアクティビティではなく、パーティションごとに実行されることを意味するものではありません。パフォーマンスが低下する可能性がある場合、ノードの数に比べてパーティションの数が多くなる可能性があります。ノードレベルでアクティビティを実行する場合は、説明されているソリューション here が役立つかもしれませんが、私はテストしていません
foreachPartition
は、パーティションごとに集約しているデータを反復処理する場合にのみ役立ちます。
良い例は、ユーザーごとのクリックストリームの処理です。ユーザーのイベントストリームを終了するたびに計算キャッシュをクリアしますが、ユーザーの行動の洞察を計算するために同じユーザーのレコード間で保持します。