reactive webアプリケーション - そしてspring 5へ #jjug_ccc #ccc_ef3
TRANSCRIPT
Reactive Webアプリケーション - そしてSpring 5へ
Toshiaki Maki (@making) JJUG CCC 2015 Fall
2015-11-30 #jjug_ccc #ccc_ef3
聞いたことあります?• Reactive Programming? • Reactive Manifest? • Reactive Streams? • Reactive Extensions? • Reactive ... ?
• Reactiveが重要視されてきた背景 • Reactive Streams • Reactive Extensions • Spring 5
今日話すこと
Reactive Extensions(Rx)
Reactive Programming
Reactive Streams Reactor
RxJava
Spring 5
プロラミング パラダイム 仕様(?)
今日話すこと実装
ライブラリSpring
Framework
元ネタ
http://www.slideshare.net/SpringCentral/reactive-web-applications-53170985
http://www.slideshare.net/SpringCentral/introduction-to-reactive-programming
Reactiveが重要視されてきた背景
Amdalの法則
https://en.wikipedia.org/wiki/Amdahl%27s_law
プログラムの並列化による速度の向上は、 プログラムの逐次処理部分によって制限される
Amdalの法則
https://en.wikipedia.org/wiki/Amdahl%27s_law
プログラムの並列化による速度の向上は、 プログラムの逐次処理部分によって制限される
プログラム中の並列実行可能部分が50%だと、いくら並列度を増やしても2倍の性能向上し
か見込めない
スケールアウト戦略
• メモリばかり消費してCPUに空き…
✦ 大量のスレッドプール
✦ブロッキングI/O
ブロッキングコードを 書いていては
ハードにいくら金をかけても性能向上に限界がある
Blocking is evil• ブロッキングコードは並列化の妨げ
• 2大ブロッキング
• リクエストの受信待ち/レスポンスの送信待ち
• DBクエリの結果待ち
Blocking is evil• ブロッキングコードは並列化の妨げ
• 2大ブロッキング
• リクエストの受信待ち/レスポンスの送信待ち
• DBクエリの結果待ち
InputStream OutputStream
JDBC
• IoT
•マイクロサービス
今後のトレンド
• IoT
•マイクロサービス
低速で膨大な リクエスト が予想される
今後のトレンド
• IoT
•マイクロサービス
低速で膨大な リクエスト が予想される
今後のトレンド
Blocking == 💴
Non-Blocking!• 命令型ではなくリスナー/コールバックスタイル
• スレッド数を少なく • →省メモリ • →コンテキストスイッチを減らす • →CPUを有効活用
Reactiveプログラミング• 命令型と異なるプログラミングパラダイム
• 連続的なデータをイベントハンドラで処理する
• データフロー(データ同士の関係性)に着目したイベントドリブンなプログラミング
Reactiveプログラミング• 命令型と異なるプログラミングパラダイム
• 連続的なデータをイベントハンドラで処理する
• データフロー(データ同士の関係性)に着目したイベントドリブンなプログラミング
ノンブロッキングコードを書くのに向いている
最も身近なReactiveプログラミング?
最も身近なReactiveプログラミング?
最も身近なReactiveプログラミング?
最も身近なReactiveプログラミング?
最も身近なReactiveプログラミング?
A, Bの変更に伴い Cも変わる
データフロー
A
B ×2
+ C
A, Bの変更に伴い Cも変わるhttps://speakerdeck.com/okapies/reactive-streams-ru-men-number-jjug
イベントストリーム
x2
+
1 2 4 3
9 6 8 6 5 9
8 4 2 64 2 1 3
A
B
C
イベントストリーム
x2
+
1 2 4 3
9 6 8 6 5 9
8 4 2 64 2 1 3
A
B
C
イベントストリーム
x2
+
1 2 4 3
9 6 8 6 5 9
8 4 2 64 2 1 3
A
B
C
イベントストリーム
x2
+
1 2 4 3
9 6 8 6 5 9
8 4 2 64 2 1 3
A
B
C
イベントストリーム
x2
+
1 2 4 3
9 6 8 6 5 9
8 4 2 64 2 1 3
A
B
C
イベントストリーム
x2
+
1 2 4 3
9 6 8 6 5 9
8 4 2 64 2 1 3
A
B
C
イベントストリーム
x2
+
1 2 4 3
9 6 8 6 5 9
8 4 2 64 2 1 3
A
B
C
命令型
int A = 3;int B = 3;int C = A + B * 2; // 9B = 1;
A, Bが変更されても Cは変わらない
JavaFXの場合@FXML Spinner<Integer> valueA; @FXML Spinner<Integer> valueB; @FXML Label valueC;
private void bind() { IntegerProperty a = new SimpleIntegerProperty(0); IntegerProperty b = new SimpleIntegerProperty(0); NumberBinding c = a.add(b.multiply(2)); // c = a + 2 * b a.bind(this.valueA.valueProperty()); b.bind(this.valueB.valueProperty()); valueC.textProperty().bind(c.asString()); }
http://aoe-tk.hatenablog.com/entry/2015/07/12/173402
A, Bの変更に伴い Cも変わる
Reactive Webアプリケーション
Repository
DB
Service
Controller
Reactive Webアプリケーション
Repository
DB
Service
Controller
Reactive Webアプリケーション
Repository
DB
Service
Controller
Blocking Blocking
Reactive Webアプリケーション
Repository
DB
Service
Controller
Blocking BlockingBlocking他サービス呼び出し
Reactive Streams
Reactive Streams
• http://www.reactive-streams.org/
• 非同期ストリームの標準仕様
• BackPressure付き
Reactive Streamsの動機1. 技術面: データを送り側の勢いが受け取り側の処理速度より速い場合に、データが溢れることを防ぎたい(BackPressure)
2. 開発面: 似たような機能(API)を持つ製品群の相互互換性を持たせたい
関わっている会社• Typesafe • Pivotal • Netflix • RedHat • など
関わっている会社• Typesafe ... Akka Streams • Pivotal ... Reactor • Netflix ... RxJava • RedHat ... Vert.x • など
Reactive Streamsが提供するもの
• 4つのインターフェース
• 仕様ドキュメント
• TCK (仕様をテストするJUnitの親クラス)
public interface Publisher<T> { public void subscribe(Subscriber<? super T> s);}public interface Subscription { public void request(long n); public void cancel();}public interface Subscriber<T> { public void onSubscribe(Subscription s); public void onNext(T t); public void onError(Throwable t); public void onComplete();}public interface Processor<T, R> extends Publisher<T>, Subscriber<R> {}
org.reactivestreams
public interface Publisher<T> { public void subscribe(Subscriber<? super T> s);}public interface Subscription { public void request(long n); public void cancel();}public interface Subscriber<T> { public void onSubscribe(Subscription s); public void onNext(T t); public void onError(Throwable t); public void onComplete();}public interface Processor<T, R> extends Publisher<T>, Subscriber<R> {}
org.reactivestreams
BackPressure
呼び出し順
onSubscribe onNext* (onError | onComplete)?
Subscription SubscriberPublisher Application
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)onNext(●)request(4)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)onNext(●)request(4)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)onNext(●)request(4)onNext(●)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)onNext(●)request(4)onNext(●)onNext(●)onComplete()
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(Long.MAX_VALUE)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(Long.MAX_VALUE)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(Long.MAX_VALUE)onNext(●)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(Long.MAX_VALUE)onNext(●)onNext(●)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(Long.MAX_VALUE)onNext(●)onNext(●)onNext(●)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(Long.MAX_VALUE)onNext(●)onNext(●)onNext(●)onNext(●)onNext(●)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(Long.MAX_VALUE)onNext(●)onNext(●)onNext(●)onNext(●)onNext(●)onComplete()
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)onNext(●)request(4)
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)onNext(●)request(4)💣💥
Subscription
subscribe(Subscriber)
SubscriberPublisher Application
onSubscribe(Subscription)request(1)onNext(●)request(2)onNext(●)onNext(●)request(4)onError(Throwable)
💣💥
RxJavaからinspired
RxJava Reactive StreamsObservable<T> Publisher<T>Observer<T> Subscriber<T>Subscription Subscription
Non BlockingなAPI• TやList<T>の代わりにPublisher<T>を返す
Blocking Non BlockingUser
get(String name)Publisher<User>get(String name)
List<User> allFriends(User user)
Publisher<User> allFriends(User user)
実装例 (Publisher)public class IntPublisher implements Publisher<Integer> { @Override public void subscribe(Subscriber<? super Integer> s) { s.onSubscribe(new Subscription() { Iterator<Integer> it = IntStream.range(0, 10) .boxed().iterator(); @Override public void request(long n) { for (int i = 0; i < n; i++) { if (it.hasNext()) { s.onNext(it.next()); } else { s.onComplete(); break; }}} @Override public void cancel() {} });}}
実装例 (Subscriber)public class IntSubscriber implements Subscriber<Integer> { Subscription subscription; int req = 1; // AtomicIntegerにすべき @Override public void onSubscribe(Subscription s) { subscription = s; s.request(req); } @Override public void onNext(Integer integer) { System.out.println("Next -> " + integer); if (--req == 0) { req = new Random().nextInt(10) + 1; subscription.request(req); } } @Override public void onError(Throwable t) { /**/ } @Override public void onComplete() { System.out.println("Complete!"); } }
使用例Publisher<Integer> publisher = new IntPublisher();Subscriber<Integer> subscriber = new IntSubscriber();publisher.subscribe(subscriber);// Next -> 1// Next -> 2// Next -> 3// Next -> 4// Next -> 5// Next -> 6// Next -> 7// Next -> 8// Next -> 9// Next -> 10// Complete!
使用例Publisher<Integer> publisher = new IntPublisher();Subscriber<Integer> subscriber = new IntSubscriber();publisher.subscribe(subscriber);// Next -> 1// Next -> 2// Next -> 3// Next -> 4// Next -> 5// Next -> 6// Next -> 7// Next -> 8// Next -> 9// Next -> 10// Complete!
IntPublisher/IntSubscriberはインターフェースを実装しているだけ
で、実は仕様を満たしていない (TCKを通らない)
Hot / Cold• Hot Stream
• ずっと続いていて途中から見はじめるもの • 株価情報、タイムライン、マウスイベント、タイマーイベント、WebSocket、Server-Sent Events、メッセージキュー
• Cold Stream • 再現可能で、最初から最後まで見れるもの • 配列・リスト、ファイル、データベース、計算結果(Future)
Reactive Streamsの仕様
• Subscriberは非同期でも同期でもよい。ただし、ノンブロッキングでないといけない
実装プロダクト(フレームワーク)
• Reactor (Pivotal) • RxJava (Netflix) • Akka Streams (Typesafe) • Vert.x (Redhat) • Ratpack • Quasarhttp://www.reactive-streams.org/announce-1.0.0
実装データアクセスライブラリ• MongoDB (公式) • Apache Kafka • Rabbit MQ (Scalaのみ) • Slick3 (Scala) • CouchDB (RxJava only) • PostgreSQL (RxJava only、開発中?) • Spark (検討中? SPARK-10420) • など
MongoDB
• http://mongodb.github.io/mongo-java-driver-reactivestreams/
• MongoDB公式でReactive Streams APIがサポートされている
• チュートリアルがあるのでわかりやすい
MongoDBMongoClient client = MongoClients.create( "mongodb://localhost"); MongoDatabase db = client.getDatabase("mydb"); MongoCollection<Document> collection = db.getCollection("test");// InsertDocument doc = new Document("message", "Hello!"); Publisher<Success> insert = collection.insertOne(doc); insert.subscribe(new PrintSubscriber<>("result=%s"));
// SelectPublisher<Document> docs = collection.find(); docs.subscribe(new PrintDocumentSubscriber());
MongoDB// Bulk OperationPrintSubscriber<BulkWriteResult> subscriber = new PrintSubscriber<>("Bulk write results: %s"); collection.bulkWrite(Arrays.asList( new InsertOneModel<>(new Document("_id", 4)), new InsertOneModel<>(new Document("_id", 5)), new InsertOneModel<>(new Document("_id", 6)), new UpdateOneModel<>(new Document("_id", 1), new Document("$set", new Document("x", 2))), new DeleteOneModel<>(new Document("_id", 2)), new ReplaceOneModel<>(new Document("_id", 3), new Document("_id", 3).append("x",4)))) .subscribe(subscriber);
Apache Kafka
• https://github.com/softwaremill/reactive-kafka
• Scalaで実装されている。Java用APIも用意されている。
Apache KafkaReactiveKafka kafka = new ReactiveKafka(); ActorSystem system = ActorSystem.create("ReactiveKafka");
Publisher<MessageAndMetadata<byte[], String>> topicA = kafka.consume(new PropertiesBuilder.Consumer( broker, zk, "topicA","group", decoder) .build(), system);
topicA.subscribe(subscriber);
PostgreSQL
• https://github.com/alaisi/postgres-async-driver
• https://github.com/mauricio/postgresql-async
PostgreSQLDb db = new ConnectionPoolBuilder().hostname("localhost").port(5432).database("demo").username("postgres").password("").poolSize(20).build();// RxJava's ObservableObservable<Row> result = db.queryRows("select id,value from demo");result.subscribe(...);// to Reactive StreamsPublisher<Row> publisher = RxReactiveStreams.toPublisher(result);publisher.subscribe(...);
Publisher単体だと使いにくい• Publisherはただのコールバック
• map、flatMap、merge、filer、take、zipといっった合成(compose)や変換(transform)のためのAPIがない → コールバック地獄に
• Publisherはインターフェースでしかないので実装ライブラリ側で便利なAPIを提供可能。
コールバック地獄
• もっと命令的に書きたい
• ComposableなAPIが欲しい(Reactive Streamsの範疇外)
Reactive Extensions
Reactive Extensions(Rx)
• 元々はMicrosoftのでErik Meijerによって.NET(LINQ)向け開発されたライブラリ
• Observerパターンでイベントストリームを扱う • イベントストリームを操作(変換、フィルタ、合成など)する豊富なAPI群
• 多言語対応(Java, JavaScript, .NET, Swiftなど)
http://reactivex.io/
map
flatMap
filter
take
merge
zip
combileLatest
buffer
RxJava• Netflix社が開発したRxのJava版 • Observable/Observer • Observableに対して豊富なOperation APIを提供
Observable.just('a', 'b', 'c') .take(2) .map(Character::toUpperCase) .consume(System.out::print); // AB
RxJava• バージョン1.0ではReactive Streamsをnativeサポートしていない。Publisher <-> Observableアダプターが必要
• バージョン2.0でReactive Streams対応予定
// Publisher <-> ObservablePublisher<String> publisher = RxReactiveStreams.toPublisher(observable);Observable<String> observable = RxReactiveStreams.toObservable(publisher);
Reactor• Pivotal社が開発
• LMAX Disruptorの高性能なRing Bufferを使用している • Spring Frameworkの一部の機能で使用されている
• Reactive StreamsのPublisher + Composable API → reactor.rx.Stream
Streams.just('a', 'b', 'c') .take(2) .map(Character::toUpperCase) .consume(System.out::print); // AB
Reactor Projectshttp://projectreactor.io/docs/reference/#_about_the_project
Reactive StreamsとReactor Streamsの関係
BroadCaster
Action
Stream
Processor
Subscriber Publisher
Consumer
ClassInterface
extends implements
Reactive Streams
Reactor
Promise
Reactor Streamsへの変換import reactor.rx.Stream;import reactor.rx.Streams;
// Streamの要素を直接指定(Cold)Stream<String> stream = Streams.just("a", "b");
// T[]やIterable<T>からStream<T>への変換(Cold)Stream<String> stream = Streams.from(value);
// Publisher<T>からStream<T>へ変換Stream<String> stream = Streams.wrap(publisher);
// Subscription.request(Long.MAX_VALUE)相当// consumeに渡すラムダ式がonNextで実行されるstream.consume(s -> System.out.println(s));
// Subscription.request(2)を繰り返すstream.capacity(2) .consume(s -> System.out.println(s));
// デバッグログ出力stream.log() .consume(s -> System.out.println(s));
Reactive Streamsの仕様を簡単に満たすヘルパー
List<Integer> items = /* ... */;Publisher<Integer> publisher = PublisherFactory.forEach((subscriber) -> { Iterator<Integer> iterator = subscriber.context(); if (iterator.hasNext()) { subscriber.onNext(iterator.next()); } else { subscriber.onComplete(); } }, subscriber -> items.iterator());
reactor.rx.Streamを使うことで Reactive Streamsを Reactive Extensionsで操作できる
Kafka + Reactorで足し算Publisher<MessageAndMetadata<byte[], String>> topicA = kafka.consume(new PropertiesBuilder.Consumer( broker, zk, "a", "group", decoder) .build(), system); Publisher<MessageAndMetadata<byte[], String>> topicB = kafka.consume(new PropertiesBuilder.Consumer( broker, zk, "b", "group", decoder) .build(), system);Publisher<Integer> a = Streams.wrap(topicA) .map(x -> Integer.valueOf(x.message())); Publisher<Integer> bx2 = Streams.wrap(topicB) .map(x -> Integer.valueOf(x.message())) .map(x -> x * 2);Streams.combineLatest(a, bx2, tpl -> tpl.getT1()+tpl.getT2()) .consume(c -> { System.out.println("a + b * 2 = " + c); });
Kafka + Reactorで足し算
$ echo '3' | kafkacat -P -t a -b localhost:9092$ echo '3' | kafkacat -P -t b -b localhost:9092
$ echo '1' | kafkacat -P -t b -b localhost:9092
a + b * 2 = 5
a + b * 2 = 9
Reactorでコナミコマンド// Create a "Hot" streamBroadcaster<String> stream = Broadcaster.create(Environment.get());stream.filter(x -> x.length() == 1) .buffer(10, 1) // 10イベント分を1つずらしでまとめる .map(s -> "↑".equals(s.get(0)) && "↑".equals(s.get(1)) && """.equals(s.get(2)) && """.equals(s.get(3)) && "←".equals(s.get(4)) && "#".equals(s.get(5)) && "←".equals(s.get(6)) && "#".equals(s.get(7)) && "A".equals(s.get(8)) && "B".equals(s.get(9))) .consume(isKonami -> { System.out.println(isKonami ? "Konami!" : "NG"); });
Reactorでコナミコマンドstream.onNext("A"); stream.onNext("↑"); stream.onNext("↑"); stream.onNext("""); stream.onNext("""); stream.onNext("←"); stream.onNext("#"); stream.onNext("←"); stream.onNext("#"); stream.onNext("A"); stream.onNext("B"); stream.onNext("←");
NGKonami!NG
コナミコマンド
HotStreamをエミュレート
外部サービス呼び出し (Blocking)
public interface LinkedInService {List<String> findUsers(String keyword);LinkedInProfile getConnection(String id);
}
public interface TwitterService {TwitterProfile getUserProfile(String id);
}
public interface FacebookService {FacebookProfile getUserProfile(String id);
}
外部サービス呼び出し (NonBlocking)
public interface LinkedInService {Stream<String> findUsers(String keyword);Stream<LinkedInProfile> getConnection(String id);
}
public interface TwitterService {Stream<TwitterProfile> getUserProfile(String id);
}
public interface FacebookService {Stream<FacebookProfile> getUserProfile(String id);
}
flatMaplinkedInService.findUsers("JJUG") .take(5) .flatMap(userName -> linkedInService.getConnections(userName) .take(3) .flatMap(connection -> { String twitterId = connection.getTwitterId(); Publisher<TwitterProfile> twitterProfile = twitterService.getUserProfile(twitterId); String facebookId = connection.getFacebookId(); Publisher<FacebookProfile> facebookProfile = facebookService.getUserProfile(facebookId); return Streams.zip(twitterProfile, facebookProfile, (tp, fp) -> new UserConnectionInfo( userName, connection, tp, fp)); }) ) .subscribe(System.out::println);
http://www.slideshare.net/SpringCentral/introduction-to-reactive-programming/60
Reactor or RxJava ?Reactor RxJava
• Reactive StreamsをNativeサポート
• Spring連携 • Reactive Extensionsのサブセット
•Reactive Exensionsをフルセットでサポート
•他言語対応 • Reactive Streamsはアダプター経由
Reactive Streamsのメリット• 非同期サポートライブラリを作りやすい
Reactive Streamsのメリット• 非同期サポートライブラリを作りやすい• async-db-driver
• Publisher/Subscriberのみを使ったコアライブラリ • async-db-driver-reactor
• ReactorのStreamにラップ • async-db-driver-rxjava
• RxJavaのObservableにラップ • ...
https://github.com/ReactiveX/RxJava/wiki/Reactive-Streams#recommended-approach
package com.example.db.driver;import org.reactivestreams.Publisher;public class Database {public Publisher getValue(String key) { /* ... */ }
}
package com.example.db.driver.reactor;public class Database {public Stream getValue(String key) {
return Streams.wrap(coreDatabase.getValue(key));}
}package com.example.db.driver.rxjava;public class Database {public Observable getValue(String key) { return RxReactiveStreams.toObservable( coreDatabase.getValue(key));}
}
RxJava用
Reactor用
Core API
すべてがPublisherで繋がる
Repository
DB
Service
Controller
Publisher Publisher Publisher
PublisherPublisherPublisher
すべてがPublisherで繋がる
Repository
DB
Service
Controller
Publisher Publisher Publisher
PublisherPublisherPublisher
すべてがPublisherで繋がる
Repository
DB
Service
Controller
Publisher Publisher Publisher
PublisherPublisherPublisher
ここは?
Spring 5
Spring 5• Q4 2016 • JDK 9サポート • Java 8がベースライン • HTTP 2対応 • Reactive対応 • JUnit5対応
Spring 5• Q4 2016 • JDK 9サポート • Java 8がベースライン • HTTP 2対応 • Reactive対応 • JUnit5対応
Java WebアプリケーションのReactive対応?
• Servlet APIは至るところでBlocking • InputStream/OutputStream • getParameters/getParts • sendError• ...
昨今のNoBlocking対応 フレームワーク
• Play Framework • Vert.x • Ratpack • Reactor Net • RxNetty
脱Servlet API組 ↓
Netty利用
Servletはダメな子なのか
Servletはダメな子なのか
• Servlet 3.0 … 非同期処理サポート
Servletはダメな子なのか
• Servlet 3.0 … 非同期処理サポート
• Servlet 3.1 … Non-Blocking APIサポート
Servlet 3.0 非同期処理サポート
void doGet(req, res) { OutputStream out = res.getOutputStream(); AsyncContext ctx = req.startAsync(); doAsyncREST(req).thenAccept(json -> { out.write(json); ctx.complete(); });}
http://www.slideshare.net/SimoneBordet/servlet-31-async-io
Servlet 3.0 非同期処理サポート
void doGet(req, res) { OutputStream out = res.getOutputStream(); AsyncContext ctx = req.startAsync(); doAsyncREST(req).thenAccept(json -> { out.write(json); // Blocking!! ctx.complete(); });}
http://www.slideshare.net/SimoneBordet/servlet-31-async-io
Servlet 3.1 Non-Blocking APIサポート
• リクエストの読み込み/レスポンスの書き込みをNon-Blockingに行うためのAPIが追加された
• 以下のシーンで効果的 • ファイルアップロード/ダウンロード • 低速ネットワークからの大量アクセス(モバイル、IoT)
void doGet(req, res) { OutputStream out = res.getOutputStream(); AsyncContext ctx = req.startAsync(); out.setWriteListener(new WriteListener() { void onWritePossible() { while (on.isReady()) { // ... ctx.complete(); } } });}
http://www.slideshare.net/SimoneBordet/servlet-31-async-io
Hello Worldサーブレット@WebServlet(urlPatterns = "/hello" , asyncSupported = true)public class HelloServlet extends HttpServlet { @Override public void service(HttpServletRequest req, HttpServletResponse resp) { AsyncContext ctx = req.startAsync(req, resp); ServletInputStream input = req.getInputStream(); ReadListener readListener = new ReadListenerImpl(input, resp, ctx); input.setReadListener(readListener);}
class ReadListenerImpl implements ReadListener { // ... private final StringBuilder sb = new StringBuilder(); public ReadListenerImpl(ServletInputStream input, HttpServletResponse resp, AsyncContext ctx) {/* ... */} public void onDataAvailable() throws IOException { int len = 0; byte b[] = new byte[1024]; while (input.isReady() && !input.isFinished() && (len = input.read(b)) != -1) { sb.append(new String(b, 0, len)); } } public void onAllDataRead() throws IOException { ServletOutputStream output = resp.getOutputStream(); WriteListener writeListener = new WriteListenerImpl(output, ctx); output.setWriteListener(writeListener); } public void onError(Throwable th) { ctx.complete(); th.printStackTrace(); }}
class WriteListenerImpl implements WriteListener { private final ServletOutputStream output; private final AsyncContext ctx; public WriteListenerImpl(ServletOutputStream output, AsyncContext ctx) { this.output = output; this.ctx = ctx; } public void onWritePossible() throws IOException { output.print("Hello World!"); output.flush(); ctx.complete(); } public void onError(Throwable th) { th.printStackTrace(); }}
Block
Servlet
Servlet
ReadListener
WriteListener
Servlet
ReadListener
WriteListener
書くのが辛い
• Non-Blockingの方が5倍コード量が多い
• isReady, isFinishedの制御が難しい
• ついgetParameterするとBlock...
http://www.slideshare.net/SimoneBordet/servlet-31-async-io
Spring Reactive
• Spring 5のReactive対応向け実験プロジェクト • 開発メンバー
• Rossen Stoyanchev (Spring MVC) • Stephane Maldini (Reactor, Reactive Streams) • Arjen Poutsma (MVCのREST対応, Spring WS) • Sébastien Deleuze (MVCのScriptEngine対応) • Brian Clozel (MVCの静的リソースハンドリング対応)
https://github.com/spring-projects/spring-reactive
Servlet31HttpHandlerAdapter
• HttpServlet実装
• ReadListener/WriteListenerをObserverパターンで実装
• Reactive Streams(Publisher/Subscriber)を使用
• ReadListener→Publisher
• WriteListener→Subscriber
Servlet31HttpHandlerAdapter
https://github.com/spring-projects/spring-reactive/tree/master/src/main/java/org/springframework/http/server/servlet31
Instead of ...
interface ServerHttpRequest extends HttpRequest { InputStream getBody();}
interface ServerHttpResponse extends HttpMessage { OutputStream getBody();}
Use Publisher
interface ReactiveServerHttpRequest extends HttpRequest { Publisher<ByteBuffer> getBody();}
interface ReactiveServerHttpResponse extends HttpMessage { Publisher<Void> setBody(Publisher<ByteBuffer> pub);}
Spring MVCのアーキテクチャ
DispatcherServlet
<<IF>> HandlerAdapter
<<IF>> HandlerMapping
Handler (Object)
フレームワーク アプリケーション
org.springframework.web.servletパッケージ
これまでの
Spring MVCのアーキテクチャ
DispatcherServlet
RequestMappingHandlerAdapter
RequestMappingHandlerMapping
@RequestMapping
HttpMessageConverter
org.springframework.web.servlet.mvc.method.annotationパッケージ
これまでの
Spring MVCのアーキテクチャ
DispatcherServlet
RequestMappingHandlerAdapter
RequestMappingHandlerMapping
@RequestMapping
HttpMessageConverter
org.springframework.web.servlet.mvc.method.annotationパッケージ
所謂、 Controllerクラス
これまでの
Spring Reactiveのアーキテクチャ
<<IF>> HandlerAdapter
<<IF>> HandlerMapping
Handler (Object)
フレームワーク アプリケーション
org.springframework.web.reactiveパッケージ
Servlet31HttpHandlerAdapter
<<IF>> Reactive
HttpHandeler
Servlet31HttpHandlerAdapter RequestMapping
HandlerAdapter
RequestMappingHandlerMapping
@RequsetMapping
Encoder/Decoder
Spring Reactiveのアーキテクチャorg.springframework.web.reactive.method.annotationパッケージ
DispatcherHandler
RequestMappingHandlerAdapter
RequestMappingHandlerMapping
@RequsetMapping
Encoder/Decoder
Spring Reactiveのアーキテクチャorg.springframework.web.reactive.method.annotationパッケージ
Servlet31HttpHandlerAdapter
DispatcherHandler
Spring MVCと同じプログラミングモデルで アプリケーションを記述できる
Spring Reactiveのアプローチ
Reactor Net ReactorHttpHanderAdapter
RxNetty RxNettyHttpHandlerAdapter
Undertow UndertowHttpHandlerAdapter
Servlet31HttpHandlerAdapter
Reactive Streams
@Controller
Jetty
Tomcat Publisher
ReactiveHttpHandeler
Spring Reactiveのアプローチ
Reactor Net ReactorHttpHanderAdapter
RxNetty RxNettyHttpHandlerAdapter
Undertow UndertowHttpHandlerAdapter
Servlet31HttpHandlerAdapter
Reactive Streams
@Controller
Jetty
Tomcat Publisher
ReactiveHttpHandeler
Servletコンテナ以外の選択肢もある
Spring MVCのController
@RequestMapping("/")@ResponseBodyPerson hello(@RequestBody Person req) { // ...}
これまでの
Spring ReactiveのController
@RequestMapping("/")@ResponseBodyPublisher<Person> hello( @RequestBody Publisher<Person> req) { // ...}
Reactive Webアプリケーション
Repository
DB
Service
Controller
Publisher Publisher Publisher
PublisherPublisherPublisher
Reactive Webアプリケーション
Repository
DB
Service
Controller
Publisher Publisher Publisher
PublisherPublisherPublisher
Spring Reactive
Reactive Extensions
Reactive Streams
絶賛開発中
• Reactive Clientの実装
• Filter相当の実装
• Progressive HTML renderingの検討
Springは Java EEの代替ではありません
落穂拾い
java.util.concurrent.Flow
• Reactive StreamsがJDK 9に入ります • Flow.Publisher• Flow.Subscriber• Flow.Subscription• Flow.Processor
• http://openjdk.java.net/jeps/266 • http://gee.cs.oswego.edu/dl/jsr166/dist/docs/java/util/concurrent/Flow.html
@author Doug Lea
j.u.c.Flow <-> Reactive Streams• ブリッジ作成を検討中 • https://github.com/reactive-streams/reactive-streams-jvm/issues/294
import java.util.concurrent.Flow;import org.reactivestreams.Publisher;
// j.u.c.Flow -> Reactive StreamsPublisher<T> toReactive(Flow.Publisher<>T);// Reactive Streams -> j.u.c.FlowFlow.Publisher<T> toFlow(Publisher<T>);
Servlet 4
• HTTP2とReactive Streams (Back Pressure)の連携案
• JDK9にj.u.c.Flowが入ったため、御蔵入りの模様
https://java.net/projects/servlet-spec/lists/jsr369-experts/archive/2015-08/message/1
まとめ• Blocking ==💴な時代がきている • Non-Blocking化を助けるReactiveなパラダイム • 非同期ストリーム標準 Reactive Streams • イベントストリームの変換・合成 Reactive Extensions
• Reactive Webアプリケーション向けフレームワークSpring ReactiveがSpring 5に向けて開発中
まとめ• Blocking ==💴な時代がきている • Non-Blocking化を助けるReactiveなパラダイム • 非同期ストリーム標準 Reactive Streams • イベントストリームの変換・合成 Reactive Extensions
• Reactive Webアプリケーション向けフレームワークSpring ReactiveがSpring 5に向けて開発中
Spring Framework を使っていきましょう