web-dev-qa-db-ja.com

reduceByKey:内部的にはどのように機能しますか?

SparkとScalaが初めてです。SparkでreduceByKey関数が機能する方法について混乱しました。次のコードがあるとします。

val lines = sc.textFile("data.txt")
val pairs = lines.map(s => (s, 1))
val counts = pairs.reduceByKey((a, b) => a + b)

マップ関数は明確です。sはキーであり、data.txtからの行を指し、1は値です。

ただし、reduceByKeyが内部的にどのように機能するかわかりませんでしたか? 「a」はキーを指しますか?あるいは、「a」は「s」を指しますか?次に、a + bは何を表しますか?彼らはどのように満たされていますか?

52
user764186

個別のメソッドとタイプに分けましょう。これは通常、新しい開発者の複雑さを明らかにします。

pairs.reduceByKey((a, b) => a + b)

になる

pairs.reduceByKey((a: Int, b: Int) => a + b)

変数の名前を変更すると、もう少し明確になります

pairs.reduceByKey((accumulatedValue: Int, currentValue: Int) => accumulatedValue + currentValue)

したがって、累積値指定されたキーの場合を取得し、それを次の値そのキーのと合計していることがわかります。さて、重要な部分を理解できるように、さらに詳しく説明しましょう。したがって、メソッドを次のように視覚化しましょう。

pairs.reduce((accumulatedValue: List[(String, Int)], currentValue: (String, Int)) => {
  //Turn the accumulated value into a true key->value mapping
  val accumAsMap = accumulatedValue.toMap   
  //Try to get the key's current value if we've already encountered it
  accumAsMap.get(currentValue._1) match { 
    //If we have encountered it, then add the new value to the existing value and overwrite the old
    case Some(value : Int) => (accumAsMap + (currentValue._1 -> (value + currentValue._2))).toList
    //If we have NOT encountered it, then simply add it to the list
    case None => currentValue :: accumulatedValue 
  }
})

そのため、reduce ByKeyがキーを見つけて追跡する定型句をとることで、その部分の管理について心配する必要がないことがわかります。

必要に応じて、より深く、より正確に

言われていることはすべて、それはここで行われたいくつかの最適化があるときに起こることの単純化されたバージョンです。この操作は連想的であるため、sparkエンジンはこれらの削減を最初にローカルで実行し(多くの場合マップ側削減と呼ばれます)、次にドライバーでもう一度実行します。データと操作を実行すると、できる限り小さくしてから、その削減を有線で送信できます。

86
Justin Pihony

reduceByKey関数の要件の1つは、結合的でなければならないということです。 reduceByKeyがどのように機能するかについての直観を構築するために、まず、連想連想関数が並列計算でどのように役立つかを見てみましょう。

associative function in action

ご覧のとおり、元のコレクションを分割して、連想関数を適用することで、合計を蓄積できます。シーケンシャルなケースは簡単で、慣れています:1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10。

結合性により、同じ関数を順番に並列に使用できます。 reduceByKeyはそのプロパティを使用して、パーティションで構成される分散コレクションであるRDDから結果を計算します。

次の例を考えてみましょう。

// collection of the form ("key",1),("key,2),...,("key",20) split among 4 partitions
val rdd =sparkContext.parallelize(( (1 to 20).map(x=>("key",x))), 4)
rdd.reduceByKey(_ + _)
rdd.collect()
> Array[(String, Int)] = Array((key,210))

Sparkでは、データはパーティションに分散されます。次の図では、(4)パーティションが左側にあり、細い線で囲まれています。まず、関数を各パーティションにローカルに、パーティション内で順番に適用しますが、4つのパーティションすべてを並行して実行します。次に、各ローカル計算の結果は、同じ関数繰り返しを適用して集計され、最終的に結果になります。

enter image description here

reduceByKeyaggregateByKeyaggregateByKeyの特殊化であり、2つの関数を取ります。1つは各パーティションに(順次に)適用され、もう1つは各パーティションの結果に適用されます(並列に) )。 reduceByKeyは両方のケースで同じ連想関数を使用します。各パーティションで順次計算を実行し、ここで説明したように最終結果にそれらの結果を結合します。

45
maasg

あなたの例では

val counts = pairs.reduceByKey((a,b) => a+b)

abは両方ともIntのタプルの_2pairsアキュムレーターです。 reduceKeyは、同じ値sを持つ2つのタプルを取り、それらの_2値をaおよびbとして使用して、新しいTuple[String,Int]を生成します。この操作は、各キーsに対してタプルが1つだけになるまで繰り返されます。

Non -Spark(または、実際には非並列)reduceByKeyとは異なり、最初の要素は常にアキュムレーターであり、2番目の要素は値であり、reduceByKeyは分散方式で動作します。すなわち、各ノードは、タプルのセットをniquely-keyedタプルのコレクションに減らし、最後のniquely-keyedタプルのセットがあるまで複数のノードからタプルを減らします。これは、ノードからの結果が削減されるため、aおよびbはすでに削減されたアキュムレーターを表すことを意味します。

6
Arne Claassen

Spark RDD reduceByKey関数は、連想リデュース関数を使用して各キーの値をマージします。

ReduceByKey関数はRDDでのみ機能し、これは変換操作であり、遅延評価されることを意味します。連想関数がパラメーターとして渡され、ソースRDDに適用され、結果として新しいRDDが作成されます。

したがって、あなたの例では、rddペアには(s1,1)、(s2,1)などの複数のペア要素のセットがあります。そしてreduceByKeyは関数(accumulator、n)=>(accumulator + n)を受け入れます。変数をデフォルト値0に設定し、各キーの要素を加算して、合計カウントがキーとペアになった結果のrddカウントを返します。

1
KayV