reactive systems と back pressure
TRANSCRIPT
Reactive Systems と Back Pressure最先端情報吸収研究所(AIAL) 勉強会
2016年8月19日
自己紹介
• 池添明宏 (いけぞえ あきひろ)
• Twitter: @zoetro
• 昔はロボットとか、C#とか、AngularJSとか。
• 最近はJavaとかScalaを書くことが多い。
本日の内容
• Reactive Systems
• 対障害性
• RxJavaのBack Pressure実装
• Back Pressureの活用
REACTIVE SYSTEMS
なぜReactiveが必要なのか
• さまざまな非同期イベントを扱う機会が増えている。
− GUI
− マイクロサービス
− ビッグデータ解析
− ノンブロッキングI/O
• 複雑になりがちな非同期処理をきれいに書きたい。
→ Reactive Programming
• 性能がよく、柔軟性・耐障害性の高いシステムをつくりたい。
→ Reactive Systems
Promise/Futureではダメなのか?
• ReactiveもPromise/Futureも非同期処理を扱うための手段。
• Promise/Futureが主に1回きりのイベントを取り扱うのに対して、
Reactiveではイベントストリームを扱う。
• Promise/Futureの強み
− 言語によっては async/await が利用できる。
− 標準で利用できるケースが多い。
• Reactiveの強み
− 様々なオペレータが用意されていて、複数のストリームを組み合わせたり、時間を
考慮した処理が簡単に書けたりする。
Webサーバのアーキテクチャ
• マルチプロセス/マルチスレッドモデル (Servletなど)
− 1つのリクエストを1つのプロセス/スレッドで捌く。
− リクエスト数が増えた場合にメモリ使用量が大幅に増える (C10K問題)
• イベント駆動モデル (Node.jsなど)
− 複数のリクエストを1つのスレッドで捌く。
− I/O処理でのブロックは禁止。すべてノンブロッキングI/Oを利用する。
• ハイブリッドモデル (Vert.x, Play Framework, Akka HTTPなど)
− イベント駆動モデルのノードを複数個用意し、メッセージをやり取りしながら連携する。
− マルチスレッドモデルとイベント駆動モデルの両方の利点を持つ。
同期処理でOK
Promise/FutureでOK
Reactiveが欲しい
Reactiveを取り入れたフレームワーク
• Webアプリケーションフレームワーク
− Spring Framework 5, Vert.x 3
• データベース
− Slick 3, MongoDB
• ライブラリ
− Akka Streams, RxJava, Reactor
• JVM系以外でも多数のライブラリ・フレームワークでReactiveな概念が
取り入れられている。
Reactive Systemの歴史
• Reactive Programmingとは
• Rx (Reactive Extensions)
• ReactiveX
• The Reactive Manifesto
• Reactive Streams
• 時間や外部の入力と共に変化する値を、反応的 (reactive) に処理するプロ
グラミングパラダイム。
• アニメーション、GUIプログラミング、センサやロボット制御プログラム
などを実現するときに役立つ。
• 2種類の入力の概念を扱う
− Behavior: 時間に伴い連続的に変化する値 (温度、株価など)
− Signal: 時間順に並ぶ離散的なイベント (マウスクリック、人物検知センサなど)
• Haskell界隈ではFRP (Functional Reactive Programming) として、古くか
ら利用されている。
Reactive Programmingとは
Reactive Programmingとは
• コード例
• イベントストリーム
var a = 1var b = a + 1a = 10 // aを書き換えるprint b // => 11
時間[t]
xs:[e1, e2, e3,e4, e5]
非同期に発生するイベントを無限リストのように扱う
Rx (Reactive Extensions)
• Microsoft Research社でErik Meijer氏が中心となり開発。
• 2009年にC#向けのライブラリとして公開された。2014年にはOSS化。
• 非同期に流れてくるデータに対して関数を適用するスタイルのライブラ
リ。
• FRPとLINQのコンセプトをベースに、シンプルかつ柔軟で実用性の高い
ライブラリとなっている。
Rx (Reactive Extensions)
• Rxの特徴
− Signalのみに特化
− エラーハンドリング
− リソース管理
− スケジューラ
− テストのための機能
− Hot Observable, Cold Observable
− LINQライクなAPI
− 豊富な関数群
Rx (Reactive Extensions)
• イベントを時間的に流れてくるデータの無限リストとして扱う。
• イベントに対しても、普通のリストと同じようにmap, reduce, filterな
どの処理が使える。
observable.filter(x -> x > 5).map(x -> x * x).subscribe(x -> out.println(x));
list.stream().filter(x -> x > 5).map(x -> x * x).forEach(x -> out.println(x));
Stream API RxJava
Rx (Reactive Extensions)
• コードはそっくりだが、データの
流れが違う。
• Iterator
− Action側からデータソースに対して
データを取りにいく (Pullスタイル)
• Observable
− データソース側からActionに対して
データを通知する (Pushスタイル)
Iterator<T>
Observable<T>
Action Action
T next() onNext(T)
Pullスタイル Pushスタイル
ReactiveX
• Rxを気に入った開発者たちが、次々と他の言語へ移植していった。
• Netflix社などが中心となり、各種言語でのRx実装をとりまとめている。
− RxJava, RxJS, RxSwift が人気。
− RxCpp, RxScala, Rx.rb, RxPy, RxKotlin, RxPHP などの実装もある。
− UniRx, RxAndroid, RxCocoa など、特定のフレームワーク向け実装もある。
• ドキュメントが充実している。
Reactive Streams
• JVMにおける非同期ストリーム処理のAPIの標準化
• Akka Streams, Reactor, RxJava, Ratpack, Vert.xなどが対応。
• Reactive Programmingをおこなう際には、上流から下流までインタ
フェースが統一されていることが望ましい。
• Java 9でReactive Streamsの標準インタフェースとしてFlow APIの導入
が検討されている。
The Reactive Manifesto
http://www.reactivemanifesto.org/
• Scalaを開発しているTypesafe社 (現Lightbend社) が提唱
• Reactiveをプログラミングモデルだけでなく、システムのアーキテクチャ
に対して適用。
• 下記の特徴を持ったアーキテクチャをReactive Systemsと呼ぶ。
− 即応性: システムは可能なかぎり素早く応答を返すこと。
− 耐障害性: システムは障害が発生しても即応性を保ち続けること。
− 弾力性: システムは処理量が変動しても即応性を保ち続けること。
− メッセージ駆動: 上記を達成するため、コンポーネント間の通信に非同期なメッセー
ジパッシングを利用する。
Reactive Systemsとは
ComponentComponent
Component• コンポーネントは分散配置可能。各コンポーネント
間では非同期なメッセージのやりとりをおこなう。
• コンポーネント内ではReactive Programmingのモ
デルを利用する。
対障害性
Reactive Systemsで起きがちな問題
• Reactive Systemsでは非同期のメッセージパッシングで
コンポーネント間の通信をおこなう。
• 下流のコンポーネントよりも上流のコンポーネントの処
理速度が早い場合、下流側のバッファがあふれてしまう。
Producer Consumer
処理が早い 処理が遅い
バッファのあふれ
対障害性を高めるために
• 大きなバッファを用意する。
• 下流のバッファがあふれないように流量を調整する。
• バッファがあふれたらデータをドロップする。時間をおいて再送する。
• コンポーネントの障害を他のコンポーネントに伝わらないようにする。
• リソースを増やして負荷分散をおこなう。
Reactive Manifesto & Reactive Streams
• Reactive Manifestoでは、下記の手段によってシステムの耐障害性を実
現すると記述している
− レプリケーション
− 障害の起きたコンポーネントの隔離
− Back Pressureによるフロー制御
• Reactive Streamsでは、Back Pressureを実現するためのインタフェー
スが規定されている。
分散メッセージングサービス
• 大きなバッファを用意して、メッセージをあふれにくく
する。
• Apache Kafka, Amazon Kinesis Streams
• 一時的な負荷上昇や、一時的なコンポーネントの障害に対応可能。
Producer Consumer
Consumerの速度に応じてメッセージを流す
下流は気にせずメッセージを流す
障害の伝搬
• いずれかのコンポーネントに障害が発生した時、それが他のコンポーネ
ントに伝搬してしまう可能性がある。
Component
Component
バッファのあふれ
ムリ…
thread
thread
thread
まだ?
まだ?
まだ?
リクエスト
リクエスト
リクエスト
障害の伝搬
• リクエストごとにスレッドを立てる場合、スレッドプールが枯渇して呼
び出し元のコンポーネントまでクラッシュしてしまう。
Component
Component
バッファのあふれ
ムリ…
thread
thread
thread
ムリ…
ムリ…
ムリ…
リクエスト
リクエスト
リクエスト
障害の隔離 (Circuit Breaker)
• 処理の失敗が連続した場合、Circuit Openな状態へと遷移。
• Openな場合は、即座にエラーを返したりキャッシュを返したりする。
• 時間をおいて復旧した場合は、Closed状態に遷移。
Component
Component
バッファのあふれ
ムリ…thread
thread
thread
リクエスト
リクエスト
リクエスト
ムリっぽいのでしばらく切断します
Back Pressure
• 下流から上流のコンポーネントに対して、受け入れ可能な
個数を通知する。
• 上流のコンポーネントでは、下流の速度にあわせてゆっく
りメッセージを送信したり、間に合わない分は捨てたりな
どの対策をおこなう。
Producer Consumer
バッファあと1個なら
大丈夫了解!
request(1)
Back Pressureでどこまで遡るの?
Component
Component Component
Component
• 最上流まで遡る
− 例えばユーザインタフェース
• 対処できるところまで遡る
− データサイズが小さい所
− DBやファイルの読み込みなど、待ちのつくれる所
ちょっと待って
ちょっと待って
待とう…
待とう…ちょっと待って
Back Pressureでどこまで遡るの?
• 各コンポーネントが速度を調整してバランスをとる
− 上流のスループットは落ちるが、システム全体として安定して動くようになる。
Component Component
ゆっくりお願いします
Component
本当はもっと速く処理できるけど、
ゆっくり送信
本当はもっと速く処理できるけど、
ゆっくり送信
RxJavaのBACK PRESSURE実装
RxJavaのBack Pressure実装
• 基本動作
• onBackpressureオペレータ
• merge
• publish
RxJavaのBack Pressure
• SubscriberがObservableをsubscribeする。
• このときSubscriberにProducerをセットする。
• SubscriberはProducerに受け取り可能なデータの数を伝える。
• ObservableはProducerを介して送信可否を判断する。
• 送信可能であれば、onNextを呼び出してデータを送信する。
Subscriber
Observable
Producer
onNext(x)
request(n)
RxJavaのBack Pressure: onBackpressure
• request()の呼び出しに応じて振る舞いを変更するためのオペレータが用
意されている。
• onBackpressureBuffer()
− データをバッファリング。キャパシティの設定、あふれた時に実行されるコール
バック処理、あふれた後にどうするかを指定できる。
• onBackpressureDrop()
− requestが0の間に受け取ったデータはすべて捨てる。
• onBackpressureLatest()
− requestが0の間に受け取ったデータは、最新のデータ以外すべて捨てる。
RxJavaのBack Pressure: onBackpressure
SubscriberObservable
onNext(x)
request(n)
onBackpressureDrop
onNext(x)
好きなタイミングでデータを送信する
requestが0でなければデータを送信する
受け入れ可能な数を通知する
requestが0の時に受け取ったデータは捨てる
RxJavaのBack Pressure: merge
• 複数のObservableをmergeした場合でも、Subscriberがrequestした数だけデータが
送信される。
• 例えばSubscriberがrequest(1)を送信した場合、Observable1かObservable2のどちら
かが1つデータを送信することができる。早い者勝ち。
Subscriber
Observable1onNext(x)
request(n)
merge
onNext(x)
Observable2
request(n)
onNext(x)
request(n)
RxJavaのBack Pressure: publish
• 複数のSubscriberにpublishする場合、Subscriberがrequestした数の最小値が上流へ
と要求される。
• 例えばSubscriber1がrequest(1), Subscriber2がrequest(2)を送った場合、Observable
はデータを1つだけ送信し、そのデータはpublishによって全Subscriberに分配される。
Subscriber1
Observable
onNext(x)
request(n)publish
onNext(x)
Subscriber2
request(n)
onNext(x)
request(n)
BACK PRESSUREの活用
背景
• ログ管理システムを開発
− 顧客環境から取得したログファイルを、社内のログ管理システムにアップロード
− ログデータはパースしてElasticsearchに登録
− Kibanaでログの分析をおこなう
• 利用ユーザ数が増えて一度に大量の登録処理が実行されると、一部のロ
グデータが失われる事態が発生した。
ログ管理システム
Elasticsearch
登録画面
Kibana
Log Files
upload
parse
bulkinsert
グラフの生成
一度に大量のログを登録すると、Elasticsearchのキューがあふれてしまう
Elasticsearch
• Bulk API: インデックスの作成・削除・更新などの処理を一括で処理する
仕組み。リクエストはキューに蓄えられ、順次処理される。
• キューがあふれたときに受け取ったデータはドロップされる。
• キューのサイズを変更することで、ドロップされにくくすることは可能。
− threadpool.bulk.queue_size: デフォルト50。-1で無制限。
− ただしサイズを大きく設定すると、サーバのスペックによってはメモリ不足になる
ので注意が必要。
Back Pressureを利用した改善策
• ElasticsearchのNodes Stats APIのでキューの状態を監視し、上流に受
け入れ可能な数を通知する。
• 上流では、受け入れ可能な数に応じて登録処理を実行。
• 登録タスクはサイズが小さいため、バッファがあふれる心配はない。
Elasticsearch
request(n)
受け入れ可能な数を通知
SubscriberObservable
キューをチェック
bulkinsertonNext(x)
受け入れ可能ならば次のタスクを処理
登録タスクのバッファ
Back Pressureを利用した改善策
• Back Pressureを利用することで、キューがあふれることなく、データ
を登録することができるようになった。
• Back Pressureを利用しなくても、キューをチェックしながら登録すれ
ばいいだけなのでは?
− Reactive Streamsの仕様に従っていると、上流側・下流側の実装を柔軟に他のもの
に変更しやすい。
− データの加工やバッファリングなど中間に様々な処理をはさんだり、コンポーネン
トを分散させたりもしやすい。
まとめ
• 非同期処理の記述性を向上させるReactive Programmingや、システム
の安定性を向上させるためのReactive Systemsが注目を集めている。
• Reactive Systemsの対障害性を向上させる方法の1つとして、Back
Pressureがある。
• RxJavaにおけるBack Pressureの実装を紹介した。
• ログ管理システムにおけるBack Pressureの活用例を紹介した。