Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Apache Spark Tutorial

Apache Spark Tutorial

Avatar for K Yamaguchi

K Yamaguchi

April 28, 2015
Tweet

More Decks by K Yamaguchi

Other Decks in Technology

Transcript

  1. 素朴な実装 3 frequency = defaultdict(int) for line in opened_file: for

    word in some_splitter(line): frequency[word] += 1 for word in frequency: some_output(word, frequency[word]) ファイル frequency メモリに 辞書/ハッシュ/ 連想配列/Mapを 持つ 頻度 (Pythonic) frequency = collections.Counter( word for line in opened_file for word in some_splitter(line)) for word, count in frequency.iteritems(): some_output(word, count)
  2. 巨⼤なファイルだと…… 4 : for line in opened_file: for word in

    some_splitter(line): frequency[word] += 1 : 巨⼤な ファイル メモリが ⾜りない!!
  3. 出⼒を分割する 6 frequency = defaultdict(int) for line in opend_file: for

    word in some_splitter(line): if hash(word) % 2 == 1: frequency[word] += 1 : frequency = defaultdict(int) for line in opend_file: for word in some_splitter(line): if hash(word) % 2 == 0: frequency[word] += 1 : 巨⼤な ファイル 頻度 頻度 ハッシュの 剰余で間引いて 半分に ある単語は⼀⽅の ファイルにだけ存在 単純に結合すればOK もう⼀度読む 残り半分
  4. 組み合わせる 7 f = [defaultdict(int) for i in range(2)] for

    l in of: for w in sp(l): f[hash(w) % 2]¥ [w] += 1 ファイル ファイル 同じ単語が ⼊っている ファイル同⼠を 統合する 剰余で分割 ファイルを 分割 単純な 結合でOK 並列可能
  5. でもHadoopは簡単じゃなかった p 定型部分の⽅が多い p 何度もMapReduce する処理を書こうと すると、 ほとんど同じで 微妙な違いしかない ソースがたくさん

    できる 10 /* * 仮型引数 * FileInputFormatだと第1引数はLongWriteable, 第2引数はtext。 * 第3引数がmap出⼒のkey, 第4引数がmap出⼒のvalue */ public static class Map extends Mapper<LongWritable, Text, Text, LongWritable> { private Text outKey = new Text("LINES"); private LongWritable outValue = new LongWritable(1); @Override protected void map(LongWritable inputKey, Text value, Context context) throws IOException, InterruptedException { context.write(outKey, outValue); } } /* * <map出⼒のkey=reduce⼊⼒, map出⼒のvalue=reduce⼊⼒, * reduce出⼒のkey, reduce出⼒のvalue> */ public static class Reduce extends Reducer<Text, LongWritable, Text, LongWritable> { private LongWritable outValue = new LongWritable(); @Override protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException { long total = 0L; for (LongWritable value : values) { total += value.get(); } outValue.set(total); context.write(key, outValue); } } ←⾏数をカウントする MapReduce 個⼈の感想です
  6. Sparkのコンポーネント 18 node00 node01 node02 node03 node04 node05 node20 driver

    executor executor master executor executor executor executor executor executor executor executor executor (shell実⾏モードの場合)
  7. driver 19 node00 node01 node02 node03 node04 node05 node20 driver

    executor executor master executor executor executor executor executor executor executor executor executor 利⽤者が⾒ているコンソール(spark-shell) WebUIも提供する 定数・関数などの定義(定義したものは各 executorに分散され共有される)
  8. executor 20 node00 node01 node02 node03 node04 node05 node20 driver

    executor executor master executor executor executor executor executor executor executor executor executor 実際にジョブを実⾏している部分
  9. master 21 node00 node01 node02 node03 node04 node05 node20 driver

    executor executor master executor executor executor executor executor executor executor executor executor SparkではなくHadoop YARNのコンポーネント なにをしているかはわかってない YARN Resource Managerとやりとりしている(︖)
  10. かんたん並⾏処理 p a.reduceは2プロセスで b.reduceは1プロセスで実⾏される p Pythonプロセスと実⾏時間で確認 25 (Python) # 0から999999999までの総和を計算する

    from operator import add a = sc.parallelize(xrange(1000000000), 2) b = sc.parallelize(xrange(1000000000), 1) a.reduce(add) 499999999500000000 b.reduce(add) 499999999500000000
  11. かんたん並⾏処理 p Scalaではスレッドが使われる 26 (Scala) // 0から999999999までの総和を計算する val a =

    sc.parallelize(1L until 1000000000, 2) val b = sc.parallelize(1L until 1000000000, 1) a.reduce(_ + _) 499999999500000000 b.reduce(_ + _) 499999999500000000
  12. RDD : 作成 p RDDの作成はSparkContextから 29 (Python) sc.parallelize([1, 2, 3,

    4, 5]) # ローカルコレクションをRDDに変換 sc.textFile("file.text") sc.textFile("directory/*.gz") # ⽂字列のコレクションになる # テキストファイルを指定してRDDを作成 # クラスタがローカル実⾏ならローカルのファイルから # クラスタがHadoop-YARN上にあるならHDFSから # クラスタがAWS上にあるならS3から読みこんだり # 圧縮ファイルなら解凍後のデータを読む # 実⾏してもすぐにファイルを読みこまない Spark-Shellのメモリ上で インスタンス化している という意味
  13. RDD : アクション(1) 30 (Python) nums = sc.parallelize([4, 3, 2,

    5, 1]) nums.collect() # => [4, 3, 2, 5, 1] # 全ての要素をローカルのコレクションにする nums.take(3) # => [4, 3, 2] 冒頭3個の要素 nums.top(3) # => [5, 4, 3] ⾃然順序づけで⼤きい⽅から3つの要素 p RDDから具体的なデータへ変換する (ローカルコレクション, 値, ファイル) Pythonだとcmp()関数 Java/ScalaだとComparable での⽐較
  14. RDD : アクション(2) 31 (Python) nums = sc.parallelize(xrange(1000)) nums.count() #

    => 1000 # 要素数 nums.reduce(lambda x, y: x + y) # => 499500 # 畳み込み (((…(((0+1)+2)+3)+…+998)+999) nums.saveAsTextFile("destination") # テキストファイルとして出⼒ p RDDから具体的なデータへ変換する (ローカルコレクション, 値, ファイル)
  15. RDD : 変換(基本) p RDDを元に別のRDDを定義する p RDDはイミュータブル(書き換え不能) 32 (Python) nums

    = sc.parallelize([1, 2, 3]) nums.map(lambda x: x * x) # => [1, 4, 9] 関数適⽤ nums.filter(lambda x: x % 2 == 0) # => [2]フィルタリング nums.flatMap(lambda x: range(x)) # => [0, 0, 1, 0, 1, 2] # 「1つの値からコレクションを返す」関数を元に複数データを⽣成
  16. RDD : 変換(2-value tuple) p 2値タプルのRDDに追加されるメソッド 33 (Python) pets =

    sc.parallelize( [("cat", 1), ("dog", 1), ("cat", 2)]) pets.groupByKey() # => [("cat", [1, 2]), ("dog", [1])] pets.reduceByKey(lambda x, y: x + y) # => [("cat", 3), ("dog", 1)] pets.sortByKey() # => [(cat, 1), (cat, 2), (dog, 1)]
  17. RDD : 変換(2つの2-value tuple) p 2値タプルのRDDに追加されるメソッド 34 (Python) pets =

    sc.parallelize( [("cat", 1), ("dog", 1), ("cat", 2)]) names = sc.parallelize( [("cat", "Tama"), ("dog", "Pochi")]) pets.join(names) # => [("dog", (1, "Pochi")), # ("cat", (1, "Tama")), # ("cat", (2, "Tama"))] pets.cogroup(names) # => [("dog", ([1], ["Pochi"])), # ("cat", ([1,2], ["Tama"]))] 相当
  18. RDD : メソッド群 35 アクション データの具体化 変換 抽象コレクションの操作 foreach collect

    reduce fold count saveAsTextFile map flatMap filter collect distinct sample sortByKey groupByKey reduceByKey zip cartesian union intersection subtract repartition join cogroup etc… take first top takeOrdered etc… メタ操作 cache unpersist checkpoint etc… こっちのcollectは Pythonにはない
  19. 並列コレクション 38 node1 node2 node3 executor executor executor (executorが3つの場合) (Python)

    sc.parallelize(xrange(100), 3) #パーティション数3を指定 sc.count() # 3並列で処理される 0 1 2 3 : 32 33 34 35 36 : 65 66 67 68 69 : 99 RDD パーティション
  20. 分割可能なファイル 42 (executorが3つの場合) (Python) sc.textFile("/work/test/large.txt") # 300MBぐらい sc.count() # 3並列で処理される

    node1 node2 node3 executor executor executor large.txt (block1) large.txt (block2) large.txt (block3) RDD
  21. 分割可能なファイル p 境界をまたぐ分が移動してから処理が始まる 44 (executorが3つの場合) (Python) sc.textFile("/work/test/large.txt") # 300MBぐらい sc.count()

    # 3並列で処理される node1 node2 node3 executor executor executor large.txt (block1) large.txt (block2) large.txt (block3) RDD
  22. 分割可能でないファイル 46 node1 node2 node3 executor executor executor data.gz (block1)

    data.gz (block2) data.gz (block3) HDFS Spark 300MBのgzipファイルがあって こんな⾵に分散されているとすると
  23. 分割不可能なファイル 47 (executorが3つの場合) (Python) sc.textFile("/work/test/data.gz") # 300MBぐらい sc.count() # 1並列でしか処理されない

    node1 node2 node3 executor executor executor data.gz (block1) data.gz (block2) data.gz (block3) data.gz (block2) data.gz (block3) ネットワーク 越しのコピー RDD
  24. 変換の連鎖 p ここまで実⾏してもまだ テキストを読みこんでない (RDDからRDDへの変換操作だから) 50 (Python) src = sc.textFile("hightemp.txt")

    # ファイルを指定してRDDを作成する tuples = src .map(lambda x: x.split("¥t")) .filter(lambda x: len(x) > 3) # 100本ノック12 col1 = tuples.map(lambda x: x[0]) col2 = tuples.map(lambda x: x[1]) tabで分割 要素数4 以上 1列⽬ 2列⽬ ⾼知県¥t江川崎¥t41¥t2013-08-12 埼⽟県¥t熊⾕¥t40.9¥t2007-08-16 岐⾩県¥t多治⾒¥t40.9¥t2007-08- 16 ⼭形県¥t⼭形¥t40.8¥t1933-07-25 ⼭梨県¥t甲府¥t40.7¥t2013-08-10 :
  25. アクション p いつの間にか並⾏処理されている 51 (続き) col1.count() 24 # ここではじめてファイルを読みこんで #

    split->filter->map->count が実⾏される col1.saveAsTextFile("col1") # またファイルを最初から読みこんで # split->filter->map->saveAsTextFile が実⾏される # これでcol1というテキストが作成される……かと思いきや # col1というディレクトリができている # ローカル実⾏だと2ファイルある(part-00000 part-00001) かもしれないし、されないかもしれない
  26. キャッシュの制御 52 (続き) # 毎回ファイルを読みこむのは効率が悪い col1.cache() # これで可能ならばメモリに保持するようになった col1.count() 24

    # またファイルから読んでsplit->filter->map->count col1.distinct().count() 12 # 異なり数を数える(100本ノック17) col1.take(2) [u'¥u9ad8¥u77e5¥u770c', u'¥u57fc¥u7389¥u770c'] ここではもう ファイルを 読んでいない
  27. もうちょっと変換 53 (続き) # 2カラム⽬でソート sorted_by_col2 = tuples.sortBy(lambda x: x[1])

    # 3カラム⽬(数値)で降順ソート(100本ノック18) sorted_by_col3 = tuples .sortBy(lambda x: float(x[2]), False) # 1コラム⽬の頻度の⾼い順にソート(100本ノック19) from operator import add frequency = col1.map(lambda x: (x, 1)) .reduceByKey(add) .sortBy(lambda x: x[1], False) # ここに書いたのは全部RDD変換定義のみで出⼒はない 昇順 降順
  28. 何をしているか(100本ノック19) 54 ⾼知県 江川崎 41 2013-08-12 埼⽟県 熊⾕ 40.9 2007-08-16

    岐⾩県 多治⾒ 40.9 2007-08-16 ⼭形県 ⼭形 40.8 1933-07-25 ︓ String [⾼知県,江川崎,41,2013-08-12] [埼⽟県,熊⾕,40.9,2007-08-16] [岐⾩県,多治⾒,40.9,2007-08-16] [⼭形県,⼭形,40.8,1933-07-25] ︓ ⾼知県 埼⽟県 岐⾩県 ⼭形県 ︓ String (⾼知県,1) (埼⽟県,1) (岐⾩県,1) (⼭形県,1) ︓ (String, Int) (⾼知県,[1].reduce(add)) (埼⽟県,[1,1,1].reduce(add)) (岐⾩県,[1,1].reduce(add)) (⼭形県,[1,1,1].reduce(add)) ︓ (String, Int) map(x.split("¥t")) filter(len(x)>3) map(x[0]) map((x, 1)) reduceByKey(add) Array[String] col2 sortBy(x[1], False) tuples 型名はScala準拠 (埼⽟県,3) (⼭形県,3) (⼭梨県,3) (群⾺県,3) ︓ frequency
  29. もうちょっとアクション 55 (続き) import json def pp(obj): print json.dumps(obj, ensure_ascii=False)

    pp(sorted_by_col3.take(3)) [["⾼知県", "江川崎", "41", "2013-08-12"], ["埼⽟県", "熊⾕", "40.9", "2007-08-16"], ["岐⾩県", "多治⾒", "40.9", "2007-08-16"]] # 先頭から3個 pp(col1.takeOrdered(3)) ["千葉県", "千葉県", "和歌⼭県"] # ⾃然順序づけで⼩さい⽅から3個 pp(col1.top(3)) ["⾼知県", "静岡県", "静岡県"] # ⾃然順序づけで⼤きい⽅から3個
  30. もうちょっとアクション 56 (続き) for data in frequency.take(10): print u'({}, {})'.format(*data)

    (埼⽟県, 3) (⼭形県, 3) (⼭梨県, 3) (群⾺県, 3) (静岡県, 2) (岐⾩県, 2) (愛知県, 2) (千葉県, 2) (⾼知県, 1) (⼤阪府, 1)
  31. テキストを読んで頻度カウント 58 val src = sc.textFile("tuples.tsv") val tuples = src.

    map(_.split("¥t")). filter(_.size > 1) val aFreq = tuples. map( t => (t(0), 1L) ).reduceByKey(_+_) val bFreq = tuples. map( t => (t(1), 1L) ).reduceByKey(_+_) val instancesFreq = tuples. map( t => ((t(0), t(1)), 1L) ).reduceByKey(_+_) ここからは Scala です combination¥toffers alabama¥thome wedding¥tgreek evil¥tdead :
  32. 何をしているか 59 combination offers alabama home wedding greek evil dead

    String [combination, offers] [alabama, home] [wedding, greek] [evil, dead] (offers, 81) (home, 36) (greek, 24) (dead, 20) (String, Int) map(_.split("¥t")) filter(_.size>1) Array[String] (combination, 20) (alabama, 40) (wedding, 40) (evil, 16) (String, Int) ((combination, offers), 1) ((alabama, home), 5) ((wedding, greek), 5) ((evil, dead), 3) ((String, String), Int) map reduceByKey map reduceByKey map reduceByKey tuples aFreq bFreq instanceFreq
  33. 頻度をつなぎあわせていく 60 val pmiSrc = instancesFreq. map{ case ((a, b),

    t_f) => (a, (b, t_f)) }. join(aFreq). map{ case (a, ((b, t_f), a_f)) => (b, (a, t_f, a_f)) }. join(bFreq). map{ case (b, ((a, t_f, a_f), b_f)) => (a, b, t_f, a_f, b_f) } Scalaだとパターンマッチで書けるけどPythonだと map(lambda x: (x[1][0][0], x[0], x[1][0][1], x[1][0][2], x[1][1])) pmi(a, b) = log P(a, b) P(a) P(b) (aの⽂字列, bの⽂字列, [a, b]の頻度, aの頻度, bの頻度) という組み合わせ(タプル)が欲しい
  34. 何をしているか 61 map ((combination, offers), 1) ((alabama, home), 5) ((wedding,

    greek), 5) ((evil, dead), 3) (combination, (offers, 1)) (alabama, (home, 5)) (wedding, (greek, 5)) (evil, (dead, 3)) (combination, 20) (alabama, 40) (wedding, 40) (evil, 16) (String, Int) ((String, String), Int) (combination, ((offers, 1), 20)) (alabama, ((home, 5), 40)) (wedding, ((greek, 5), 40)) (evil, ((dead, 3), 16)) (String, ((String, Int), Int)) join (String, (String, Int)) ("a", 1) ("a", 2) ("b", 3) ("c", 4) ("a", "あ") ("a", "い") ("b", "か") ("d", "た") から ※joinはinner joinするメソッド ("a", (1, "あ")) ("a", (1, "い")) ("a", (2, "あ")) ("a", (2, "い")) ("b", (3, "か")) を作る (続く) instanceFreq aFreq
  35. 何をしているか 62 map (combination, ((offers, 1), 20)) (alabama, ((home, 5),

    40)) (wedding, ((greek, 5), 40)) (evil, ((dead, 3), 16)) (offers, (combination, 1, 20)) (home, (alabama, 5, 40)) (greek, (wedding, 5, 40)) (dead, (evil, 3, 16)) (offers, 81) (home, 36) (greek, 24) (dead, 20) (String, Int) (String, ((String, Int), Int)) (String, (String, Int, Int)) (offers, ((combination, 1, 20), 81)) (home, ((alabama, 5, 40), 36)) (greek, ((wedding, 5, 40), 24)) (dead, ((evil, 3, 16), 20)) (String,((String, Int, Int), Int)) join (combination, offers, 1, 20, 81) (alabama, home, 5, 40, 36) (wedding, greek, 5, 40, 24) (evil, dead, 3, 16, 20) (String, String, Int, Int, Int) map =(a, b, [a, b]の頻度, aの頻度, bの頻度) 前ページ 最後 pmiSrc bFreq
  36. 計算する 63 val instancesAll = tuples.count def calcDiscountPmi(instance:Long, a:Long, b:Long)

    = { def smooth (x:Long, y:Double) = { x / (x + y) } def discount(iTmp:Long, aTmp:Long, bTmp:Long) = { smooth(iTmp, 1.0) * smooth(math.min(aTmp, bTmp), 1.0) } def calcPmi(iTmp:Long, aTmp:Long, bTmp:Long) = { import math.log log(iTmp) - log(aTmp) - log(bTmp) + log(instancesAll) } calcPmi(instance, a, b) * discount(instance, a, b) } val pmi = pmiSrc.map{ case (a, b, t_f, a_f, b_f) => (calcDiscountPmi(t_f, a_f, b_f), a, b, t_f, a_f, b_f) } pmi.top(5).foreach(println) (5.771154762538349,fat,greek,8,36,24) (5.724583909571343,hong,kong,6,28,17) (5.660412678732772,freaks,legged,4,16,9) (5.632288650398451,greek,fat,5,20,19) (5.590241876891969,scams,scams,3,8,7) 普通の定数 普通の関数 (クロージャ) RDD変換
  37. val s:String="hello" s: String = hello 値の束縛 val i=1 i:

    Int = 1 型推論 var j=1 j: Int = 1 変数への代⼊ i=i+1 //間違い(再代⼊不可) error: reassignment to val j=j+1 j: Int = 2 s.contains("el") res: Boolean = true メソッド呼び出し s contains "em" res: Boolean = false 演算⼦スタイル 1+2 は 1.+(2) のこと val t1=("a",3) t1: (String, Int) = (a,3) タプル val t2="b"->4 t2: (String, Int) = (b,4) 2値タプルのシンタックスシュガー t1._1 res: String = a t1._2 res: Int = 3 タプルの要素 val nums = Seq(1,2,3) nums: Seq[Int] = List(1, 2, 3) シーケンス(Seqはファクトリでもある) nums.map((x:Int)=>x*2) res: Seq[Int] = List(2, 4, 6) 無名関数,マップ nums.map(x=>x*2) 同上(型推論) nums.map(_*2) 同上(プレースホルダー) nums.reduce((x,y)=>x+y) res: Int = 6 畳み込み nums.reduce(_+_) 同上(プレースホルダー) def even(x:Int):Boolean={ x%2==0 } even: (x: Int)Boolean 関数(最後に評価した値が返り値) nums.filter(even) res: Seq[Int] = List(2) フィルタリング for (i<-nums) { println(i) } 1 2 3 繰り返し,標準出⼒ nums.foreach(println) 同上 val tuples=Seq(t1, t2) tuples: Seq[(String, Int)] = List((a,3), (b,4)) tuples.map{t=> val i=t._2 t._1+i.toString } res: Seq[String] = List(a3, b4) {} は複数⾏の無名関数を作る tuples.map{t=> t match { case (s,i)=>s+i.toString } } 同上(パターンマッチング) tuples.map{case (s,i)=>s+i.toString} 同上(パターンマッチング) import scala.math math.max(1,2) res: Int = 2 パッケージのインポート import scala.math._ max(1,2) パッケージから全てインポート s.split("l") res: Array[String] = Array(he, "", o) s(0) res: String = he 配列のインデックスアクセス nums.mkString(",") res: String = 1,2,3 t1.mkString(",") //間違い error: value mkString is not a member of (String, Int) s"${t1._1},${t1._2}" res: String = a,1 ⽂字列への変数の埋め込み Scala Cheat Sheet statement result in the REPL 補⾜
  38. HDFSクラスタ node7 HDFS 73 node1 node2 node3 node4 node5 node6

    node8 block3 block1 block2 登録 128MBごとの 固まりに 分ける
  39. HDFSクラスタ node7 HDFS 74 node1 node2 node3 node4 node5 node6

    node8 block3 block1 block2 登録 block1 block1 block1 block2 block2 block2 block3 block3 block3 3重に複製されてクラスタに保管される
  40. HDFS p なぜ128MBごとに分割するか? p MapReduceではデータが分散されている必要が ある p MapReduceでは各ワーカーの処理がメモリーに 乗りきる必要がある p

    なぜ複製を作るのか? p 障害耐性が⾼くなる p 分散処理の効率がよくなる (データの移動が少なくなる) 75 Sparkでも同じ!
  41. よくつかうコマンド p hdfs dfs 〜 p hdfsの操作 p hdfs dfs

    –ls 〜 p hdfs dfs –rm 〜 p hdfs dfs –rm –r 〜 p hdfs dfs –du –s –h 〜 p hdfs dfs –mkdir 〜 p hdfs dfs –cp 〜 〜 p hdfs dfs –mv 〜 〜 78
  42. よくつかうコマンド p hdfs dfs –put ローカルエントリ hdfsエントリ p HDFSにファイルを送る p

    hdfs dfs –get hdfsエントリ ローカルエントリ p hdfsからファイルを持ってくる 79
  43. a partition 1 partition 0 a.reduce(sub) 95 0 1 2

    3 4 5 ((0 - 1) - 2) = reduce ((3 - 4) - 5) = reduce -3 -6 reduce 3