reactive webアプリケーション - そしてspring 5へ #jjug_ccc #ccc_ef3

Post on 07-Jan-2017

26.362 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

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

プログラムの並列化による速度の向上は、 プログラムの逐次処理部分によって制限される

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 を使っていきましょう

top related