Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JVM上でのストリーム処理エンジンの変遷

6,740 views

Published on

2016/05/21 JJUG CCC 2016 Springでの発表資料

Published in: Engineering

JVM上でのストリーム処理エンジンの変遷

  1. 1. JVM上の ストリーム処理エンジンの変遷 2016/05/21 JJUG CCC 2016 Spring 木村宗太郎(@kimutansk) https://www.flickr.com/photos/mattridings/9138233663
  2. 2. 自己紹介 • 木村 宗太郎(Sotaro Kimura) • ビッグデータ界隈に生息する何でも屋 • バックエンドからフロントエンド、技術検証から運用、 ドキュメント書きまで色々 • Scala Matsuri運営スタッフ • 元Storm使い • StormのためだけにClojureを勉強してましたよ・・・ • 現Spark Streaming使い • この発表ではそれ自体にはあまり触れません。 • Twitter他 : @kimutansk 1
  3. 3. アジェンダ 1. ストリーム処理とは? 2. ストリーム処理系プロダクトの変遷 3. どのプロダクトを使うべきか? 4. プロダクト選定時に考えるべきこと 5. サービス開発時に考えるべきこと 6. 実際のストリーム処理の組み方例 2
  4. 4. 3 1. ストリーム処理とは? バッチ処理 対話型クエリ ストリーム処理 実行タイミング 手動起動 定期実行 手動起動 定期実行 常時実行 処理単位 保存済みデータを 一括処理 保存済みデータを 一括処理 1~少数の フローデータを処理 実行時間 分~時間 秒~分 永続実行 データサイズ TBs~PBs GBs~TBs Bs~KBs(1件あたり) 処理時間 分~時間 秒~分 ミリ秒~秒 主な用途 ETL ビジネスレポート生成 機械学習モデリング インタラクティブBI 分析 異常/不正検知 レコメンド 可視化 代表的 OSSプロダクト MapReduce Spark Tez Impala Drill Presto (後述) • ビッグデータの処理モデルは主に3つある。
  5. 5. 4 1. ストリーム処理とは? • バッチ処理 • データストアに蓄積したデータを 一括変換、レポート/モデル生成を行うモデル 生成したデータの出力先は 主にデータストア
  6. 6. 5 1. ストリーム処理とは? • 対話型クエリ • データストアに蓄積したデータに 対してクエリを実行し、結果を取得するモデル クエリの実行結果は実行元 で取得するケースが多い
  7. 7. 6 1. ストリーム処理とは? • ストリーム処理 • 「連続発生データを常時処理し続ける」モデル • データの発生元は多岐にわたる センサー データ ログ アプリ 履歴 データ発生元 メッセージキュー ストリーム処理部 データ利用先
  8. 8. 7 1. ストリーム処理とは? バッチ処理 対話型クエリ ストリーム処理 実行タイミング 手動起動 定期実行 手動起動 定期実行 常時実行 処理単位 保存済みデータを 一括処理 保存済みデータを 一括処理 1~少数の フローデータを処理 実行時間 分~時間 秒~分 永続実行 データサイズ TBs~PBs GBs~TBs Bs~KBs(1件あたり) 処理時間 分~時間 秒~分 ミリ秒~秒 主な用途 ETL ビジネスレポート生成 機械学習モデリング インタラクティブBI 分析 異常/不正検知 レコメンド 可視化 代表的 OSSプロダクト MapReduce Spark Tez Impala Drill Presto (後述) • 今回の対象となるのは「ストリーム処理」
  9. 9. 8 2. ストリーム処理系プロダクトの変遷 • ストリーム処理部を実現するプロダクトを ここでは「ストリーム処理エンジン」と呼ぶ • 元は2011年のStorm公開を機に発展 • 最近多数のプロダクトが公開 • 下記のような派生パターン有 • UIでDataflow定義 • 処理を定義可能なUIを保持するパターン • DSL • 同一の記述で複数のストリーム処理エンジン上で アプリケーションが実行可能
  10. 10. 9 2. ストリーム処理系プロダクトの変遷 古 新公開時期 DSL UIで Dataflow定義 純ストリーム処理エンジン (Heron?) Storm Gearpump Summingbird NiFiSpring Cloud Data Flow Cask Hydrator Beam ここ数年で公開
  11. 11. 10 2. ストリーム処理系プロダクトの変遷 古 新公開時期 DSL UIで Dataflow定義 純ストリーム処理エンジン (Heron?) Storm Gearpump Summingbird NiFiSpring Cloud Data Flow Cask Hydrator Beam ここ数年で公開 最近多くプロダクトが公開 全部は説明できないため、 元祖と、最近のものをいくつか紹介
  12. 12. 2. ストリーム処理系プロダクトの変遷 • Storm • 2011年公開 by Twitter • 実装言語:Clojure • Clojureが読み書き出来ないと深い問題は追えない。 • 広まった初のOSSストリーム処理エンジン • At Least Onceが可能になったのが大きい。 • 初期のプロダクトのため問題も多かった。 • データ取得側の性能が高いとあふれて死ぬ。 • デフォルトのスレッド配置が非効率。 • At Least OnceのAckの処理効率が非効率。 • 以後のストリーム処理プロダクトに大きく影響。 11
  13. 13. 2. ストリーム処理系プロダクトの変遷 • Spark(Spark Streaming) • 2013年公開 by amplab • 実装言語:Scala • バッチ処理フレームワークSpark上で 小バッチを連続実行し、ストリーム処理を実現 • マイクロバッチと呼ばれる。 • スループットは大きいが、レスポンスは遅い。 • Sparkエコシステム上で実行可能なのが大きい。 • 機械学習ライブラリの利用 • SQLによるデータ操作 • 開発手法も同様のものが利用可能 12
  14. 14. 2. ストリーム処理系プロダクトの変遷 • NiFi • 2014年に公開 by NSA(当時はNiagrafiles) • 実装言語:Java • 画面上でデータフローを定義し、 複数サーバにデプロイして実行可能 • HTTPでデータ取得>変換>HDFSに投入 etc… • コンポーネント間のキュー毎に 優先度設定やQoS設定が定義可能 13
  15. 15. 2. ストリーム処理系プロダクトの変遷 14
  16. 16. 2. ストリーム処理系プロダクトの変遷 • Flink • 2014年に公開 • 2011年からStratosphereとして公開 • 実装言語:Scala • バッチ処理とストリーム処理両方のAPIを 提供するストリーム処理エンジン • 障害対応のための状態の自動保存が強力 • 自動的に各コンポーネントの状態を保存 • Exactly Onceを謳っているが、それは後述 • 高レベルAPIと低レベルAPIの両方を提供 • 簡易なものは簡単に組める。 15
  17. 17. 2. ストリーム処理系プロダクトの変遷 • Apex • 2015年に公開 by DataTorrent • 実装言語:Java • 元々金融アプリケーション用プロダクトで 可用性、耐障害性重視の設計 • 運用時の問題切り分けも容易な構成 • メッセージバッファリングで障害発生時の影響を低減 • ただし、その分性能が多少犠牲になっている模様 • NiFiと同じく画面上からDataFlowを定義可能 • スケーラビリティにも優れた構成 • ストリーム処理中にAutoScalingが可能 16
  18. 18. 2. ストリーム処理系プロダクトの変遷 • Gearpump • 2015年に公開 by Intel • 実装言語:Scala • Googleの「MillWheel」に影響を受けたプロダクト • ストリーム処理モデルの論文 • Actorベースの「薄い」構成で拡張性が高い • その分、状態管理なども自前で準備する必要有 • Reactive Streamsに準拠しており、 標準化されたBack Pressure機構を保持 • 独特の記述により直観的にグラフを定義可能 • 後ほど紹介 17
  19. 19. 2. ストリーム処理系プロダクトの変遷 • Beam • 2016年に公開 by Google • 実装言語:Java • バッチ処理/ストリーム処理を抽象化し、 複数の実行エンジン上にデプロイ可能なDSL • Google Cloud Dataflow、Spark、Flinkにデプロイ可能 • つまりはストリーム処理対応版Asakusa Framework • ただ、当然ながら各エンジンの機能を 全て使いこなせるわけではない。 • ポータビリティを確保するか、 機能的、性能的な優位性を求めるかの選択となる。 18
  20. 20. 3. どのプロダクトを使うべきか? • 気になるのは、 『これだけプロダクトが乱立している状況で、 どのプロダクトを使うべきか?』 19
  21. 21. 3. どのプロダクトを使うべきか? • 気になるのは、 『これだけプロダクトが乱立している状況で、 どのプロダクトを使うべきか?』 20 むしろ私が一番教えてほしい 教えてください
  22. 22. 3. どのプロダクトを使うべきか? • と、いうのも・・・ • 様々な比較資料はプロダクトを推進する 企業サイドのものであり、バイアスが大きい • 特に、最近はFlink、Apex陣営の資料が多い。 • 個人的には直近はSpark Streaming 将来的にはFlink、Apex、Gearpumpの3択 • 現時点の最良プロダクトは、すぐ時代遅れ。 • Beamを使えば「大失敗」はまずないとは思われるが、 実行エンジンの機能も引き出し切れない。 • 特に、各エンジンの持つ機械学習等の固有ライブラリへの 対応について不安が大きい。 • と、いうことで・・・ 21
  23. 23. 3. どのプロダクトを使うべきか? • ストリーム処理を構築する上で 考えるべきことについて説明します。 • プロダクト選定時 • サービス開発時 • この項目自体も Storm、Spark Streamingから挙げたものです。 • もしFlink、Apex、Gearpump等他プロダクトの 経験者がいれば、是非とも補足を。 22
  24. 24. 4. プロダクト選定に考えるべきこと • プロダクト選定時に考えるべき主要観点 1. 実装言語は何か? 2. インストールの際に何が必要? 3. Back Pressure機構を有しているか? 4. サービス動作中にどこまで更新可能か? 5. 接続用コンポーネントが揃っているか? 6. 【注意】Exactly Once機能の価値は高くない。 23
  25. 25. 4. プロダクト選定に考えるべきこと 1. 実装言語は何か? • ストリーム処理系プロダクトを扱う場合、 発展途上の状態で使うのが基本となるため。 • 情報は少なく、問題が発生した際に どうしても自分で解析せざるを得なくなる。 • メーリングリストに問い合わせても時間はかかる。 • 問題解析ができるレベルの知識を持った 言語で組まれたプロダクトを使うのがおすすめ。 • 幸い、最近のものはJavaとScalaがメインのため、 あまりこれを気にする機会はないが。 • StormはClojureがコアのため、なかなか酷かった・・・ 24
  26. 26. 4. プロダクト選定に考えるべきこと 2. インストールの際に何が必要? • 必然的に複数のマシンに跨る分散構成となり、 事前に何かをインストールするのが困難なため。 • インストールする=何かのDaemonを起動するため、 クラスタ全域に入れるとリソース的な無駄も大きい。 • まっさらなリソースマネージャの上で そのまま動作することが望ましい。 • ストリーム処理系はHadoopクラスタなどと 同居することも多く、その上でそのまま動くのは大きい。 • YARN、Mesos etc... 25
  27. 27. 4. プロダクト選定に考えるべきこと 3. サービス動作中にどこまで更新可能か? • ストリーム処理は「連続データを常時処理」の 関係上、長期にわたって動き続けるため。 • 一定時間で終了するバッチ処理とは明確に サービス要件が違うので、要注意。 • ApexやGearpumpが動作中の更新機能を保持 26 参照:http://www.slideshare.net/BhupeshChawda1/introduction-to-apache-apex-cods-2016
  28. 28. 4. プロダクト選定に考えるべきこと 3. サービス動作中にどこまで更新可能か? • ストリーム処理は「連続データを常時処理」の 関係上、長期にわたって動き続けるため。 • 一定時間で終了するバッチ処理とは明確に サービス要件が違うので、要注意。 • ApexやGearpumpが動作中の更新機能を保持 27 参照:http://www.gearpump.io/releases/latest/features.html
  29. 29. 4. プロダクト選定に考えるべきこと 4. BackPressure機構を有しているか? • 性能のアンバランスが発生した際に システムのダウンが発生するのを防ぐため。 • ※BackPressureとは? • 上流の方が性能が高い場合に下流で溢れるのを 防止する機構全般。Reactive Streamsとして標準化。 28 100メッセージ/秒 処理 10メッセージ/秒 処理
  30. 30. 4. プロダクト選定に考えるべきこと 4. BackPressure機構を有しているか? • 性能のアンバランスが発生した際に システムのダウンが発生するのを防ぐため。 • ※BackPressureとは? • 上流の方が性能が高い場合に下流で溢れるのを 防止する機構全般。Reactive Streamsとして標準化。 29
  31. 31. 4. プロダクト選定に考えるべきこと 4. BackPressure機構を有しているか? • 性能のアンバランスが発生した際に システムのダウンが発生するのを防ぐため。 • ※BackPressureとは? • 上流の方が性能が高い場合に下流で溢れるのを 防止する機構全般。Reactive Streamsとして標準化。 30
  32. 32. 4. プロダクト選定に考えるべきこと 4. BackPressure機構を有しているか? • 性能のアンバランスが発生した際に システムのダウンが発生するのを防ぐため。 • ※BackPressureとは? • 上流の方が性能が高い場合に下流で溢れるのを 防止する機構全般。Reactive Streamsとして標準化。 31 いずれ溢れて障害発生!
  33. 33. 4. プロダクト選定に考えるべきこと 4. BackPressure機構を有しているか? • 性能のアンバランスが発生した際に システムのダウンが発生するのを防ぐため。 • ※BackPressureとは? • 上流の方が性能が高い場合に下流で溢れるのを 防止する機構全般。Reactive Streamsとして標準化。 32 10メッセージ要求 10メッセージ送信
  34. 34. 4. プロダクト選定に考えるべきこと 5. 接続用コンポーネントが揃っているか? • ストリーム処理エンジンは自前では処理を するのみで、外部とのやり取りが必須なため。 33 センサー データ ログ アプリ 履歴 データ発生元 メッセージキュー ストリーム処理部 データ利用先
  35. 35. 4. プロダクト選定に考えるべきこと 5. 接続用コンポーネントが揃っているか? • ストリーム処理エンジンは自前では処理を するのみで、外部とのやり取りが必須なため。 34 センサー データ ログ アプリ 履歴 データ発生元 メッセージキュー ストリーム処理部 データ利用先 Kafka DistributedLog RabbitMQ MQTT Broker Amazon Kinesis Cloud Pub/Sub Azure Event Hub 各種 データストア
  36. 36. 4. プロダクト選定に考えるべきこと 6. 【注意】Exactly Once機能の価値は高くない。 • ストリーム処理エンジン単体では 外部に対するExactly Onceは実現できないため。 • 外部に対するアクセスと、状態保存が 「Atomic」でないため、障害時、重複処理が発生 35 メッセージを通知して アラーム発報するケースを考える
  37. 37. 4. プロダクト選定に考えるべきこと 6. 【注意】Exactly Once機能の価値は高くない。 • ストリーム処理エンジン単体では 外部に対するExactly Onceは実現できないため。 • 外部に対するアクセスと、状態保存が 「Atomic」でないため、障害時、重複処理が発生 36 通知後、ローカル状態を 更新する前に障害が発生すると・・?
  38. 38. 4. プロダクト選定に考えるべきこと 6. 【注意】Exactly Once機能の価値は高くない。 • ストリーム処理エンジン単体では 外部に対するExactly Onceは実現できないため。 • 外部に対するアクセスと、状態保存が 「Atomic」でないため、障害時、重複処理が発生 37 再度通知が行われ、 重複してアラームが発報される!
  39. 39. 5. サービス開発時に考えるべきこと • サービス開発時に考えるべき主要観点 1. 【必要な場合】Exactly Onceの実現方法 2. データがプロセスをまたがないように配置 3. 極力メモリ上に収めるか、並列度を調整 4. ログを集約する機構を準備 38
  40. 40. 5. サービス開発時に考えるべきこと 1. 【必要な場合】Exactly Onceの実現方法 • 重複しても問題ないようサービスを設計する。 • 「ID」+「時刻」をキーにしてデータストアに保存。 重複処理をはじく。 • 重複して処理しても同一データは複数出力されない。 • 「最後のデータ」の値を使用するようにすることで、 一時的にずれても補正がきくようにする。 • 統計値や、合計値を求めるケースに有効。 • アラーム発報のような場合は上記対処の上で 「データストアから取得して発報」する形にする。 39
  41. 41. 5. サービス開発時に考えるべきこと 2. データがプロセスをまたがないように設定 • プロセスをまたぐことによるオーバーヘッドが ストリーム処理では大きく響くため。 • シリアライズ>プロセス間通信>デシリアライズ • ただ、Groupingなどで最低限は必要になる。 • メッセージのルーティングで、 極力プロセスをまたがないよう設定する。 40
  42. 42. 5. サービス開発時に考えるべきこと • StormWordCountにおいて無駄が多い例 41 TestWord Spout Exclamation Bolt Additional Bolt Worker1 Worker2 Worker3 同一“word” でGrouping RoundRobin方式で配分 プロセス
  43. 43. 5. サービス開発時に考えるべきこと • StormWordCountにおいて無駄を削減した例 42 TestWord Spout Exclamation Bolt Additional Bolt Worker1 Worker2 Worker3 同一“word” でGrouping 同一プロセス内の コンポーネントに流す プロセス
  44. 44. 5. サービス開発時に考えるべきこと 3. 極力メモリ上に収めるか、並列度を調整 • ディスクアクセスを行う同期的処理を行うと、 ストリーム処理側に追いつけなくなるため。 • Redis、Infinispan、Ignite等の メモリ上に収まる系統のプロダクトを用いる。 • ディスクにアクセスが必要な場合、 該当コンポーネントの並列度を上げてしのぐ。 43
  45. 45. 5. サービス開発時に考えるべきこと 4. ログを集約する機構を準備 • 処理が複数ホストに分散する関係上、 エラーがどこで発生したかの把握が困難なため。 • LogAppenderで集約サーバに送信 • fluentd等で集約サーバに集約 • ログ出力先をNFSマウントして出力 44
  46. 46. 6. 実際のストリーム処理の組み方例 • Gearpumpを例にして説明 • Gearpumpのアプリケーションは下記で構成 • 各コンポーネントのコード • コンポーネントを組み合わせてデプロイするコード 45 例:WordCountを行うアプリケーション Split Sum 単語ごとに集約 Sum.scala Split.scala HashPartitioner (既存コンポーネント) WordCount.scala
  47. 47. 6. 実際のストリーム処理の組み方例 • Split.scala 46 class Split(taskContext : TaskContext, conf: UserConfig) extends Task(taskContext, conf) { import taskContext.{output, self} // 1. 自分自身にStartメッセージを通知。 override def onStart(startTime : StartTime) : Unit = { self ! Message("start") } // 2. 文章を単語に分割し、空文字を除去した上で下流に送信 override def onNext(msg : Message) : Unit = { Split.TEXT_TO_SPLIT.lines.foreach { line => line.split("[s]+").filter(_.nonEmpty).foreach { msg => output(new Message(msg, System.currentTimeMillis())) } } // 3. 次メッセージを自分に対して送信するタスクを仕掛ける import scala.concurrent.duration._ taskContext.scheduleOnce(Duration(100, TimeUnit.MILLISECONDS))(self ! Message("continue", System.currentTimeMillis())) } }
  48. 48. 6. 実際のストリーム処理の組み方例 • Sum.scala 47 class Sum (taskContext : TaskContext, conf: UserConfig) extends Task(taskContext, conf) { private[wordcount] val map : mutable.HashMap[String, Long] = new mutable.HashMap[String, Long]() private[wordcount] var wordCount : Long = 0 private var snapShotTime : Long = System.currentTimeMillis() private var snapShotWordCount : Long = 0 private var scheduler : Cancellable = null override def onStart(startTime : StartTime) : Unit = { // 1. 起動時に状態出力タスクを仕掛ける。 scheduler = taskContext.schedule(new FiniteDuration(5, TimeUnit.SECONDS), new FiniteDuration(30, TimeUnit.SECONDS))(reportWordCount) } override def onNext(msg : Message) : Unit = { if (null == msg) { return } // 2. 受信したメッセージから単語を取得し、総受信回数と単語ごとの受信回数をカウント val current = map.getOrElse(msg.msg.asInstanceOf[String], 0L) wordCount += 1 map.put(msg.msg.asInstanceOf[String], current + 1) }
  49. 49. 6. 実際のストリーム処理の組み方例 • WordCount.scala 48 object WordCount extends AkkaApp with ArgumentsParser { private val LOG: Logger = LogUtil.getLogger(getClass) val RUN_FOR_EVER = -1 // 1. 起動時のCLIから読み込む項目と形式、注釈、必須/オプショナル、デフォルト値を定義 override val options: Array[(String, CLIOption[Any])] = Array( "split" -> CLIOption[Int]("<how many split tasks>", required = false, defaultValue = Some(1)), "sum" -> CLIOption[Int]("<how many sum tasks>", required = false, defaultValue = Some(1)) ) def application(config: ParseResult) : StreamApplication = { // 2. CLIから読み込んだ設定項目を用いてProcessorを生成 val splitNum = config.getInt("split") val sumNum = config.getInt("sum") val split = Processor[Split](splitNum) val sum = Processor[Sum](sumNum) // 3. メッセージのProcessor間の割り振りを行うPartitionerを生成 val partitioner = new HashPartitioner // 4. ProcessorとPartitionerを用いてDAGを作成 val app = StreamApplication("wordCount", Graph(split ~ partitioner ~> sum), UserConfig.empty) app } 直観的なグラフの組み方が 可能
  50. 50. まとめ • ビッグデータは主に下記3つの処理モデル 1. バッチ処理 2. 対話的クエリ 3. ストリーム処理 • プロダクトが多く、ベストは選びにくい状況 • プロダクトは異なっても共通点は多い 1. 性能特性/ボトルネックとなるポイント 2. データ設計 3. 解析を行うための準備 etc • 上記とプロダクト成熟度を踏まえ構築 49
  51. 51. Enjoy stream processing! https://www.flickr.com/photos/allan_harris/2422708123

×