Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Oracle Cloud Hangout Cafe - 挑戦! JavaでReactiveプロ...

Oracle Cloud Hangout Cafe - 挑戦! JavaでReactiveプログラミング / reactive-java

Oracle Cloud Hangout Cafe(おちゃかふぇ)のセッションスライドです。

(セッションの録画)
https://youtu.be/XjMz9bACM2c

(イベントページ)
https://ochacafe.connpass.com/event/189340/

oracle4engineer

October 28, 2020
Tweet

More Decks by oracle4engineer

Other Decks in Technology

Transcript

  1. 挑戦! JavaでReactiveプログラミング Oracle Cloud Hangout Cafe - Season 3 #4

    Tadahisa Kotegawa Senior Director Oracle Corporation Japan Oct 28th, 2020
  2. 自己紹介 Copyright © 2020, Oracle and/or its affiliates 3 古手川

    忠久 日本オラクル株式会社 ソシューションアーキテクト本部 デブサミ2020 セッションレポート 読んで下さい! https://codezine.jp/article/detail/12054 [CodeZine] これからのマイクロサービスに必要な 最新技術が凝縮した「Helidon」を徹底解説 @tkotegaw tkote / oracle-japan
  3. Reactive Programingのフレームワークを使うと色々と良いことがあるのか? 「ノンブロッキング/非同期通信」の仕組みを 「簡単に」取り入れたい データストリームを「パフォーマンス良く」「負 荷集中を避けながら」処理したい 今回のテーマで深堀りしたいところ Copyright © 2020,

    Oracle and/or its affiliates 4 Microservice Microservice Microservice B Microservice C Microservice Microservice Microservice A Microservice Microservice Microservice 複数の独立した Microserviceを 呼び出した結果を元に レスポンスを作成 A B C A B C メッセージの送信先の 負荷を考慮しながら効 率的に送信
  4. 5 Copyright © 2020, Oracle and/or its affiliates 1. Reactive

    Programing 101 2. Reactive Streams 3. Reactive Programingフレームワーク - java.util.concurrent.Flow - ReactiveX RxJava - Helidon Reactive Operators 4. MicroProfile Reactive Streams Operators 5. MicroProfile Reactive Messaging 6. Reactive Messaging in Practice 7. まとめ Agenda
  5. 6 Copyright © 2020, Oracle and/or its affiliates 本日の Reactiveプログラミング

    は 全部 です… 今日はJavaのソースをたくさん読みますので 最初にちょっとだけ準備体操しておきます…
  6. ラムダ式 = メソッドを変数のように扱えるようにし、コード量を減らし読みやすくする ラムダ式、関数型インタフェース、メソッド参照、 Stream API Copyright © 2020, Oracle

    and/or its affiliates 7 ラムダ式 関数型インタフェースを実装する際に、処理の記述を簡潔にするための文法 (引数) -> { 処理; ... } 関数型インタフェース 実装すべきメソッドが1つしかないインタフェース java.util.function パッケージに汎用の関数型インタフェースがある - Supplier ( T get () ) - Consumer ( void accept (T) ) - Function ( R apply(T) ) メソッド参照 処理内容がメソッド呼び出し1つのみで、かつそのメソッドの引数が一意に決まる場合、 引数そのものを省略する記法 list.forEach(i -> System.out.print(i)); // Consumerを実装した匿名クラス list.forEach(System.out::print); // メソッド参照 Stream API データ構造に対してStreamを「生成する」「操作する」「まとめる」の処理を連続して行 うことで、目的の結果を得られるよう記述できるAPI List.of(1, 2, 3).stream().map(n -> n * 2).forEach(System.out::println); // 2¥n4¥n6¥n
  7. Copyright © 2020, Oracle and/or its affiliates 8 import java.util.function.Function;

    public class Test { public void someFunction(int x, Function<Integer, Double> f){ System.out.println(f.apply(x)); } public void test(){ class MySqrt implements Function<Integer, Double> { // local innter class @Override public Double apply(Integer x){ return Math.sqrt(x); } } someFunction(2, new MySqrt()); someFunction(2, new Function<Integer, Double>(){ // anonymous inner class @Override public Double apply(Integer x){ return Math.sqrt(x); } }); someFunction(2, Math::sqrt); // lambda with method reference someFunction(2, x -> Math.pow(x, 3)); // cube } public static void main(String[] args){ new Test().test(); } } クラスをまず定義してから、 そのインスタンスを引数に 匿名インナークラスを使って インスタンス作成時に定義 ラムダ式&メソッド参照ですっきり & 異なる実装も同様に簡潔に書ける y = f(x) の x と f を 引数で受け取って y を出力
  8. Javaで非同期計算を扱う時に登場する「将来起きる結果」を表すもの Future, CompletionStage , CompletableFuture Copyright © 2020, Oracle and/or

    its affiliates 9 interface Future<T> T get() boolean isDone() boolean cancel(boolean mayInterruptIfRunning) … CompletableFuture<T> boolean complete​(T value) boolean completeExceptionally​(Throwable ex) T join() static CompletableFuture<Void> runAsync​(Runnable runnable) static <U> CompletableFuture<U> supplyAsync​(Supplier<U> supplier) … interface CompletionStage<T> <U> CompletionStage<U> thenApply​(Function<? super T,​? extends U> fn) CompletionStage<Void> thenAccept​(Consumer<? super T> action) CompletionStage<Void> thenRun​(Runnable action) CompletableFuture<T> toCompletableFuture() … 非同期計算の連鎖を行う 非同期計算の結果そのもの Future と CompletionStage を継承した便利クラス Future を生成するメソッドを 提供するインターフェースが ExecutoreService + submit(task) Function, Consumer, Runnable, Supplier… はみんな 関数型インターフェース なのでラムダ式が使える!
  9. 複数のタスクを同時実行する際に、複数のスレッドを効率よく使う仕組み 複数のスレッドをあらかじめ作成して待機させておく タスクが来たら待っているスレッドにタスクを割り当てて処理を開始させる 実行させるスレッドに空きがない場合は、タスクをキューに入れてスレッドに空きができるまで待つ Javaでは、 ExecutorService というインターフェースを持った、色々なバリエーションのスレッドプー ルの仕組みを提供している • AbstractExecutorService,

    ForkJoinPool, ScheduledThreadPoolExecutor, ThreadPoolExecutor 通常はExecutors というユーティリティクラスを使って ExecutorService のインスタンスを取得する • Executors.newSingleThreadExecutor() • Executors.newCachedThreadPool() • Executors.newFixedThreadPool​(int nThreads) • Executors.newScheduledThreadPool​(int corePoolSize) • etc. スレッドプール (Thread Pool) Copyright © 2020, Oracle and/or its affiliates 10
  10. 複数のバックエンドを並列(≒非同期)に呼び出す方法を探る ここからはReactive Programingに入る前の練習問題… Copyright © 2020, Oracle and/or its affiliates

    11 client JAX-RS resource processor processor processor “abc,lmn,xyz” “CBA,NML,ZYX” “abc” “CBA” “lmn” “NML” “xyz” “ZYX” どう作る? REST API On the Same JVM return measure(() -> { // wrapper return Arrays.stream(str.split(“,”)) // .map(processor::process) // .collect(Collectors.joining(“,”)); // }); * デモでは実際のprocessor のインスタンスは1つです ストリームを使った基本形 (これは同期型)
  11. Streamの並列処理機能 java.util.stream.Stream#parallel() • ForkJoinPool.commonPool() の並列度に依存 Javaの並行処理機能 java.util.concurrent.CompletableFuture#supplyAsync(Supplier) • JDKが提供する ExecutorService

    を使うパターン • コンテナ(Helidon)が提供する ExecutorService を使うパターン MicroProfileが提供する非同期処理機能 org.eclipse.microprofile.faulttolerance @Asynchronous メソッドを非同期に呼び出そうと思ったときに、まず思いつく方法 Copyright © 2020, Oracle and/or its affiliates 12 “OCHaCafe2#4 Cloud Native時代のモダンJavaの世界” http://tiny.cc/ochacafe-mp-slide 注: 「順次(sequential)ストリーム」「並列(parallel)ストリーム」と 「順序付け(ordering)」 → 順序付けされたストリームでは並列ストリームでも検出順に処理するよう制約される
  12. 非同期RESTクライアントはどう作る? バックエンドがRESTサービスだったら? Copyright © 2020, Oracle and/or its affiliates 13

    client JAX-RS resource REST processor REST processor REST processor “abc,lmn,xyz” “CBA,NML,ZYX” “abc” “CBA” “lmn” “NML” “xyz” “ZYX” どう作る? REST API REST API Network * デモでは実際のprocessor のインスタンスは1つです
  13. JAX-RS Async Client JAX-RS Reactive Client MicroProfile Async Rest Client

    非同期RESTクライアントの選択肢 Copyright © 2020, Oracle and/or its affiliates 14 Future<String> response = ClientBuilder.newClient() .target(uriInfo.getBaseUri()).path("/async-client/process") .queryParam("str", x) .request() .async() // AsyncInvoker .get(String.class); CompletionStage<String> response = ClientBuilder.newClient() .target(uriInfo.getBaseUri()).path("/async-client/process") .queryParam("str", x) .request() .rx() // CompletionStageRxInvoker .get(String.class); CompletionStage<String> response = RestClientBuilder.newBuilder() .baseUri(uriInfo.getBaseUri()) .build(ProcessClient.class) // interface with path info .process(x); // interface method
  14. https://www.reactivemanifesto.org/ 日本語バージョン:「リアクティブ宣言」 (https://www.reactivemanifesto.org/ja) The Reactive Manifesto Copyright © 2020, Oracle

    and/or its affiliates 16 即応性と、耐障害性と、弾力性と、メッセージ駆動とを備えた システムをリアクティブシステム (Reactive Systems) と呼ぶ。 • 即応性 (Responsive) • 耐障害性 (Resilient) • 弾力性 (Elastic) • メッセージ駆動 (Message Driven) リアクティブシステムは、Reactive Programingを使 わなくても実現可能なので注意!
  15. Responsive, Resilient, Elastic, Message Driven 即応性 (Responsive) • システムはできる限り速やかに反応する。問題は即座に検知され効果的に対処される。迅速かつ一貫 性のある応答時間に主眼を置く。

    耐障害性 (Resilient) • 障害発生時でも即応性を維持する。障害はそれぞれのコンポーネントに封じ込められ、コンポーネン トは互いに隔離されるので、システムの部分的障害は全体に影響を与えることなく回復することが保 証される。 弾力性 (Elastic) • システムはどのような負荷の下においても即応性を維持する。入力量の変化に対して、それに対応す るリソースを増減させる。競合するポイントや大きなボトルネックが存在しないようシステムはデザ インされる(コンポーネントの共有やレプリケーション、入力の分散)。 メッセージ駆動 (Message Driven) • コンポーネント間の境界を作るために非同期なメッセージ・パッシングを使い、疎結合、隔離性、位 置透過性を確保する。明示的なメッセージ・パッシングは負荷の管理、弾力性、フロー制御(メッ セージ・キューの作成・管理とバック・プレッシャーの適用)を可能とする。 即応性、耐障害性、弾力性、メッセージ駆動 Copyright © 2020, Oracle and/or its affiliates 17
  16. データをストリームとして認識 • データを受け取るたびにプログラムが反応して処理を行う=リアクティブ • 必要なデータを自ら取得して処理をするスタイルではない データの生産側はデータの消費側がどういう処理をしているのかを意識しない • データの消費側の処理を待つ必要がなくなる →非同期 •

    データを通知をした後は、すぐに別の次の処理を行うことができる →ノンブロッキング • データの消費者の負荷状況に関わらず一方的にデータを通知し続ける状況への対応手段の提供 →バックプレッシャー Reactive Programing とは? Copyright © 2020, Oracle and/or its affiliates 18 データ 生産者 データ 消費者A データ 消費者B Reactive Programing Framework publish subscribe subscribe 負荷 非同期 非同期 back pressure
  17. http://www.reactive-streams.org/ https://github.com/reactive-streams/reactive-streams-jvm 非同期ストリーム処理を実現するため、Reactive Streams Special Interest Group(SIG)が策定した仕様 Javaだけでなく、JavaScriptや.NET向けのインタフェースも定義 インターフェースを定義、Java実装は以下のものがあり •

    Akka Streams <https://www.lightbend.com/akka-platform> • ReactiveX RxJava <https://github.com/ReactiveX/RxJava> • Project Reactor <https://projectreactor.io/> 以下の4つの主要コンポーネント(インターフェース) Reactive Streams とは? Copyright © 2020, Oracle and/or its affiliates 21 コンポーネント 説明 Publisher データを通知するコンポーネント Subscriber 受け取った通知をもとに処理を行うコンポーネント Subscription PublisherとSubscriberを介在し、購読のキャンセルや通知するデータ数をリ クエストするインターフェースを提供するコンポーネント Processor PublisherとSubscriberの両方のインターフェスを持ち、Publisherと Subscriberの中間に入ってデータを処理するコンポーネント
  18. loop cancel end normal org.reactivestreams パッケージ Reactive Steams - 処理シーケンス

    Copyright © 2020, Oracle and/or its affiliates 22 Publisher<T> subscribe(Subscriber) Subscriber<T> onSubscribe(Subscription) onNext(T) onError(Throwable) onComplete() Subscription request(long) cancel() 1 n 1 1 n 1 << create >> Publisher Subscription Subscriber subscribe() new() onSubscribe(Subscription) request(long) onNext() request(long) << generate message >> onError() cancel() onComplete() OR OR << consume >> * onNext()はsingle threadでシーケンシャルに呼び出される 全部 interface
  19. 過負荷の下でシステムを安定稼働させるための仕組み SubscriberからPublisherに対して、自身が過負荷状態であることを伝える • Subscriberは request(long) * を使ってその時点で処理可能なメッセージ数を知らせる • Publisherは (最新のrequest数)

    – (最新のrequest数を受領後に送信したメッセージ数) でSubscriberの処理可能なメッセージ数が推定できる 過負荷だと伝えられたPublisherは次に送信しようとしているメッセージをどう処理すべきか? • 気にせず送信するか、バッファするか、破棄する (エラーにする) か → 実装判断 • バッファしたとしても、バッファーがオバーフローする可能性は依然あり • オーバーフローする前に、(もしあれば)さらに上流に対して過負荷状態を伝えるか?… • Subscriberの処理が捗るまでwaitする(or 上流からのリクエストを拒否する) か?… Reactive Steams – Backpressure Copyright © 2020, Oracle and/or its affiliates 23 * request(n) の n = Long.MAX_VALUE → 無制限を表す、以降request()をcallする必要なし
  20. …と言ってもReactive Steamsはインターフェースなので実装しないと動かない Reactive Steams – デモ Copyright © 2020, Oracle

    and/or its affiliates 24 client JAX-RS resource processor processor processor “abc,lmn,xyz” “CBA,NML,ZYX” “abc” “CBA” “lmn” “NML” “xyz” “ZYX” どう作る? Reactive Streams * デモでは実際のprocessor のインスタンスは1つです もし自分の実装を世の中に広めたいのであれば、 Reactive Streams TCK(Technology Compatibility Kit) があるのでそれを使ってバリデーションする RsMessage(String request) + String getRequest() + complete(String response) + completeExceptionally(Throwable t) + CompletableFuture<String> getResponse() RsSubscriber RsPublisher 自作のPublisher 自作のSubscriber 自作の送受信メッセージ・クラス PublisherからSubscriberの実行結果を確認 できるように細工している! 実行結果を セット リクエストをラップ fire & forget Subscriberの実行結果を知る術なし
  21. Java 9で導入されたAPI で、Reactive Streams仕様に対応 以下のインターフェースを定義 • Flow.Publisher • Flow.Subscriber •

    Flow.Subscription • Flow.Processor とは言え、Reactive Streamsとは仕様的に同値なだけ • Java 9+の環境で、Reactive Streams APIとFlow APIを混在して使いたいケースあり • Reactive Streams API → 稼働環境をJava 9+に制限したくない開発フレームワークを実装する場合の選択肢 • Flow API → Java 9+の環境では積極的に活用したいけど開発フレームワークがFlow対応していない… Reactive Streams側に両者のインターフェースを橋渡しするアダプターが存在する • org.reactivestreams.FlowAdapters • Reactive Streams → Flow: toFlowProcessor(), toFlowPublisher(), toFlowSubscriber() • Flow → Reactive Streams: toProcessor(), toPublisher(), toSubscriber() java.util.concurrent.Flow Copyright © 2020, Oracle and/or its affiliates 26
  22. デモ java.util.concurrent.Flow Copyright © 2020, Oracle and/or its affiliates 27

    client JAX-RS resource processor processor processor “abc,lmn,xyz” “CBA,NML,ZYX” “abc” “CBA” “lmn” “NML” “xyz” “ZYX” どう作る? Flow Reactive Style FlowMessage(String request) + String getRequest() + complete(String response) + completeExceptionally(Throwable t) + CompletableFuture<String> getResponse() FlowSubscriber java.util.concurrent. SubmissionPublisher<FlowMessage> + subscribe(FlowSubscriber) + submit(FlowMessage) + estimateMaximumLag() + estimateMinimumDemand() JDKが提供しているbuffer装備の便利なPublisher 自作のSubscriber * デモでは実際のprocessor のインスタンスは1つです 実行結果を セット リクエストをラップ
  23. AndroidもサポートするReactive Programing ライブラリ 元々はObservable と Observer (名前はObserverパターンに由来) Reactive Streamsに対応 •

    Flowable (implements Publisher) と Subscriber • flatMapを使った並列処理を記述できる! → Publisherをネストするイメージ <R> Flowable<R> flatMap(Function<? super T,? extends Publisher<? extends R>> mapper) ReactiveX RxJava Copyright © 2020, Oracle and/or its affiliates 28 final String[] args = Optional.ofNullable(str).orElse(defaultStr).split(","); final ConcurrentHashMap<Object, String> dict = new ConcurrentHashMap<>(); // return measure(() -> { Flowable .fromArray(args) //.flatMap(x -> Flowable.just(x).subscribeOn(Schedulers.from(es)).map(processor::process)) .flatMap(x -> Flowable.just(x) .subscribeOn(Schedulers.from(es)) .doOnNext(s -> dict.put(s, processor.process(s))) ) .blockingSubscribe(); return Arrays.stream(args).map(dict::get).collect(Collectors.joining(",")); }); 普通にflatMap()を使うと 結果が順不同になる 後で順序を正すため一旦辞書に保管
  24. 外部のReactive系モジュールとは独立しており、Helidonのecosystemだけに依存 io.helidon.common.reactive.Multi io.helidon.common.reactive.Single Helidon (SE) の Reactive Operators Copyright ©

    2020, Oracle and/or its affiliates 29 final String[] args = Optional.ofNullable(str).orElse(defaultStr).split(","); final ConcurrentHashMap<Object, String> dict = new ConcurrentHashMap<>(); // return measure(() -> { Multi .just(args) .flatMap(x -> Single.just(x).observeOn(es).peek(s -> dict.put(s, processor.process(s)))) .collectList().toCompletableFuture().join(); // return Arrays.stream(args).map(dict::get).collect(Collectors.joining(",")); }); HelidonもflatMap()で並列処理を書ける
  25. Sandbox Project “REACTIVE” ReactiveワーキンググループがMicroProfileアプリケーションをよりイベント・ドリブン、非同期的にす るための仕様を作成 MicroProfile Reactive Streams Operators •

    Java Streams API を Reactive Streams にマッピング • ストリーム・データ処理にReactive Programingの要素を付加 MicroProfile Reactive Messaging • Reactive Streams/Flowをベースに、メッセージの送受信を扱うAPIを提供 • Java EE MDB(Message Driven Bean)より軽量で使いやすい MicroProfile と Reactive Programing Copyright © 2020, Oracle and/or its affiliates 32
  26. 33 Copyright © 2020, Oracle and/or its affiliates Helidon オープン・ソース

    (github.com/oracle/helidon) • 2019/02/15 に1.0.0をリリース • 現在 バージョン 2.1.0, MicroProfile 3.3準拠 マイクロサービスを開発するためのJavaライブラリの集合体 JVM上の単体アプリケーションとして動作 従来からの一般的なツール・基盤で開発&デプロイ可能 • Java SE, Maven/Gradle, Docker, Kubernetes, etc. ※ Helidonは ギリシア語でつばめ(swallow)を意味します Docker Image Kubernetes Helidon jars OS base image Library jars App classes Pod Pod Pod Replica Set Java SE runtime
  27. Helidon SE と MP Copyright © 2020, Oracle and/or its

    affiliates 34 • マイクロ・フレームワーク • 超軽量フットプリント • 関数型 • Reactive Web Server • GraalVM native image • MicroProfile 3.2 • 軽量フットプリント • 宣言型 • Java EEサブセット + マイクロサービス関連機能 • GraalVM JIT compiler Helidon MP Helidon SE CDI Extensions フットプリント重視 機能性重視 互換性(MicroProfile準拠)重視
  28. Helidon MP Components Copyright © 2020, Oracle and/or its affiliates

    35 MP Config Metrics Health Check Fault Tolerance JWT Auth JAX-RS CDI JSON-P / B Open API Open Tracing REST Client SE SE SE JPA gRPC JTA SE + MicroProfile = Reactive Streams Operators Reactive Streams Messaging Kafka Connector experimental SE SE
  29. https://github.com/eclipse/microprofile-reactive-streams-operators java.util.stream API のReactive Streams版 • forEach(), filter(), map(), collect(),

    reduce(), etc. … • 非同期に処理を行う • 最終ステージ(collect, findFirst, reduce, etc.)はCompletionStage<T>を返す • CompletionStageが完了しないと、結果を取得できない • 実行中の例外もCompletionStage経由 • parallel(), findAny()は提供されない Publisher, Subscriber, Processor のステージを連結して完結する処理を組み立てる MicroProfile Reactive Streams Operators Copyright © 2020, Oracle and/or its affiliates 36 of(…) Filter() org.eclipse.microprofile.reactive.streams.operators PublisherBuilder map() toList() org.eclipse.microprofile.reactive.streams.operators SubscriberBuilder .to( )
  30. デモ MicroProfile Reactive Streams Operators Copyright © 2020, Oracle and/or

    its affiliates 37 client JAX-RS resource processor processor processor “abc,lmn,xyz” “CBA,NML,ZYX” “abc” “CBA” “lmn” “NML” “xyz” “ZYX” どう作る? MP Reactive Streams Operators return ReactiveStreams .of(str.split(",")) // PublisherBuilder .map(processor::process) // PublisherBuilder .collect(Collectors.joining(",")) // CompletionRunner .run() // CompletionStage .toCompletableFuture() // CompletableFuture .get(); // String * デモでは実際のprocessor のインスタンスは1つです
  31. https://github.com/eclipse/microprofile-reactive-messaging Reactiveな非同期メッセージングのためのMicroProfile規格 マイクロサービスでしばしば用いられる”CQRS and Event-Sourcing”パターンにも利用できる Bean のメソッドに @Incoming もしくは @Outgoing

    のアノテーションをつけて、Channel 間の Message の受け渡しを定義する Single Subscriberモデル = Publisherが接続するSubscriberは1つのみ • 同一の Channel に複数の @Incoming をつけてはならない • これにより Publisher は Subscriber の Backpressure を適切に処理することができる MicroProfile Reactive Messaging Copyright © 2020, Oracle and/or its affiliates 39 channel-A channel-B Publisher Bean (Processor Bean) Subscriber Bean @Outgoging(“channel-A”) pub(){ } @Incoming(“channel-B”) sub(){ } @Incoming(“channel-A”) @Outgoging(“channel-B”) process(){ } 1 1 1 1
  32. メソッドの定義方法 – バラエティがある ざっくり言うと、Publisher/Subscriber/Processorに自分の実装を使うか、FW提供のものを使うか MicroProfile Reactive Messaging Copyright © 2020,

    Oracle and/or its affiliates 40 @Outgoing("channel") Publisher<Message<O>> method() @Outgoing("channel") Publisher<O> method() @Outgoing("channel") PublisherBuilder<Message<O>> method() @Outgoing("channel") PublisherBuilder<O> method() @Outgoing("channel") Message<O> method() @Outgoing(“channel”) O method() // publisher @Outgoing(“channel”) CompletionStage<Message<O>> method() @Outgoing(“channel”) CompletionStage<O> method() @Incoming(“channel”) Subscriber<Message<I>> method() @Incoming(“channel”) Subscriber<I> method() @Incoming(“channel”) SubscriberBuilder<Message<I>> method() @Incoming(“channel”) SubscriberBuilder<I> method() @Incoming(“channel”) void method(I payload) // subscriber @Incoming("channel") CompletionStage<?> method(Message<I> msg) @Incoming("channel") CompletionStage<?> method(I payload) @Incoming("in") @Outgoing("out") PublisherBuilder<Message<O>> method(PublisherBuilder<Message<I>> pub) @Incoming(“in”) @Outgoing(“out”) O method(I payload) // processor ...
  33. デモ #1 – @Outgoing & @Incoming 実装方法は色々あり MicroProfile Reactive Messaging

    Copyright © 2020, Oracle and/or its affiliates 41 // Publisher // @Outgoing("messaging-demo") public Publisher<FlowMessage> preparePublisher() { logger.info("@Outgoing(¥"messaging-demo¥")"); return ReactiveStreams .fromPublisher(FlowAdapters.toPublisher(publisher)) .buildRs(); } // Subscriber // // Subscriber onNext() @Incoming("messaging-demo") public Subscriber<FlowMessage> prepareSubscriber() { logger.info("@Incoming(¥"messaging-demo¥")"); return FlowAdapters.toSubscriber(new FlowSubscriber()); } // (Subscriber FW ) // onNext() @Incoming("messaging-demo") public void consume(FlowMessage message) { logger.info("@Incoming(¥"messaging-demo¥")"); try{ final String response = processor.process(message.getRequest()); message.complete(response); // }catch(Throwable t){ message.completeExceptionally(t); // } } messaging-demo OR OR… OR… // – // @Outgoing("messaging-demo") public FlowMessage publish() { logger.info("@Outgoing(¥"messaging-demo¥")"); try{ return queue.take(); // block! }catch(Exception e){ throw new RuntimeException();} } OR こちらがおすすめ • Publisher→Subscription経由でbackpressureに対応できる backpressure関係ない場合はこれもアリ Subscriberからbackpressureを制御できる → request(n)
  34. デモ #2 – チャネルの連結: Publisher – Processor - Subscriber MicroProfile

    Reactive Messaging Copyright © 2020, Oracle and/or its affiliates 42 @Outgoing("channel-1") public KeyValueMessage publish() { logger.info("publish() is being called."); try{ KeyValue kv = queue.take(); // logger.info("Publishing [channel-1]: " + kv); return KeyValueMessage.of(kv); }catch(Exception e){ throw new RuntimeException("cannot get message from the queue", e);} } @Incoming("channel-1") @Outgoing("channel-2") public String process(KeyValueMessage message) { logger.info("Processing [channel-1 -> channel-2]: " + message.getPayload()); KeyValue kv = message.getPayload(); return processor.process(kv.getKey() + "=" + kv.getValue()); } @Incoming("channel-2") public void consume(String message) { logger.info(String.format("Consuming [channel-2]: %s", message)); }
  35. デモ #3 – Helidonが提供するexample MicroProfile Reactive Messaging Copyright © 2020,

    Oracle and/or its affiliates 43 https://github.com/oracle/helidon/tree/master/examples/microprofile/messaging-sse @Outgoing("multiplyVariants") public Publisher<String> preparePublisher() { // Publisher } @Incoming("multiplyVariants") @Outgoing("wrapSseEvent") public ProcessorBuilder<String, String> multiply() { // // , , } @Incoming("wrapSseEvent") @Outgoing("broadcast") public OutboundSseEvent wrapSseEvent(String msg) { // SSE } @Incoming("broadcast") public void broadcast(OutboundSseEvent sseEvent) { // SSE SSE } * ブラウザから試せます
  36. Channel を介して外部のメッセージング技術/システムと接続する • Publisherとして振る舞う → @incoming のメソッドにメッセージを送る → org.eclipse.microprofile.reactive.messaging.connector.IncomingConnectorFactory の実装クラス

    • PublisherBuilderを供給する • Subscriberとして振る舞う→ @Outgoing のメソッドからメッセージを受け取る → org.eclipse.microprofile.reactive.messaging.connector.OutgoingConnectorFactory の実装クラス • SubscriberBuilderを供給する • 設定情報は、MicroProfile Configファイルに記述 • mp.messaging.[incoming/outgoing].[channel-name].[attribute]=[value] • mp.messaging.connector.[connector-name].[attribute]=[value] MicroProfile Reactive Messaging - Connector Copyright © 2020, Oracle and/or its affiliates 44 channel channel IncomingConnectorFactory + getPublisherBuilder(Config) META-INF/ microprofile-config.properties OutgoingConnectorFactory + getSubscriberBuilder(Config) Connector (Publisher) Connector (Subscriber) 外部 システム 外部 システム ユーザ コード @Incoming(チャネル名) @Outgoing (チャネル名) 参照 参照 ユーザ コード @Connector(コネクタ名) @Connector(コネクタ名)
  37. デモ #1 – File Connectorを自作してみる Publisher, Subscriberさえ実装できれば、あとはConnectorのお作法に従って組み立てるだけ • Publisherはディレクトリをポーリングして新規ファイルを監視する •

    Message#ack() をオーバーライドしてAckのタイミングでファイルをアーカイブディレクトリに移す • ファイルをPath渡しにする場合、ack()のタイミングを考慮しないとreadする前にmoveされてエラーになる MicroProfile Reactive Messaging - Connector Copyright © 2020, Oracle and/or its affiliates 45 @Acknowledgment のタイプと動作 @Acknowledgement @Incomingメッセージ NONE Ack処理は行われない MANUAL ユーザーが Message#ack() を呼 び出す必要がある PRE_PROCESSING メソッドが呼び出される前にAck される POST_PROCESSING (1) 送出したメッセージがAckさ れたタイミングか、(2) メッセー ジが送出されずにメソッドが終 了したタイミングで、Ackされる Input Output Archive File Connector IN File Connector OUT In() out() file-in file-process file-out ack watch read move write *メソッドのシグナチュアによって デフォルトが異なるので注意 Path or 内容
  38. デモ #1 – File Connectorを自作してみる – 使用例 MicroProfile Reactive Messaging

    - Connector Copyright © 2020, Oracle and/or its affiliates 46 /** * File Connector */ @Incoming("file-in") @Outgoing("file-process") @Acknowledgment(Acknowledgment.Strategy.MANUAL) public Message<byte[]> in(InFileMessage<Path> message) throws IOException{ logger.info("--- in() ---"); return Message.of(Files.readAllBytes(message.getPayload()), message::ack); } /** * File Connector */ @Incoming("file-process") @Outgoing("file-out") public OutFileMessage<String> out(Message<byte[]> message) throws IOException{ logger.info("--- out() ---"); return OutFileMessage.of(new String(message.getPayload()).toUpperCase()); } # File Connector mp.messaging.incoming.file-in.connector=file-connector mp.messaging.incoming.file-in.dir=/tmp/file/in mp.messaging.incoming.file-in.pass-by-reference=true mp.messaging.outgoing.file-out.connector=file-connector mp.messaging.outgoing.file-out.dir=/tmp/file/out mp.messaging.outgoing.file-out.prefix='file-out'-yyyyMMddHHmmssSSS'.dat' mp.messaging.connector.file-connector.polling-interval=1000 mp.messaging.connector.file-connector.archive-dir=/tmp/file/archive microprofile-config.properties
  39. デモ#1 – MicroProfile Reactive Messaging 準拠のKafka Connector Helidon Kafka Connector

    Copyright © 2020, Oracle and/or its affiliates 47 Helidon Kafka Connector Producer Helidon Kafka Connector Consumer Kafka-pub Kafka-sub KafkaPublisher + submit() + @Outgoing("kafka-pub") preparePublisher() KafkaSubscriber + @Incoming("kafka-sub") consume() KafkaResource /kafka/publish MicroProfile Reactive Messaging MicroProfile Reactive Messaging microprofile-config.properties mp.messaging.incoming.kafka-sub.connector=helidon-kafka mp.messaging.incoming.kafka-sub.topic=ochacafe-demo mp.messaging.incoming.kafka-sub.auto.offset.reset=latest mp.messaging.incoming.kafka-sub.enable.auto.commit=true mp.messaging.incoming.kafka-sub.group.id=GROUP_01 mp.messaging.outgoing.kafka-pub.connector=helidon-kafka mp.messaging.outgoing.kafka-pub.topic=ochacafe-demo mp.messaging.connector.helidon-kafka.bootstrap.servers=localhost:9092 mp.messaging.connector.helidon-kafka.key.serializer=org.apache.kafka.common.serialization.StringSerializer mp.messaging.connector.helidon-kafka.value.serializer=org.apache.kafka.common.serialization.StringSerializer mp.messaging.connector.helidon-kafka.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer mp.messaging.connector.helidon-kafka.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer REST endpoint
  40. デモ#2 – OCHaCafe「Cloud Native × Streaming はじめの一歩」のデモをアップグレード Kafka client APIでガリガリ書いてい

    た箇所をKafka Connectorに変更 Helidon Kafka Connector Copyright © 2020, Oracle and/or its affiliates 48 Helidon Kafka Connector Helidon Kafka Connector • ソースから org.apache.kafka.* を排除(接続まわりのコーディング不要) • Configに接続情報を定義 https://github.com/oracle-japan/ochacafe-spark-streaming @Incoming("from-kafka") public void consumeKafka(KafkaMessage<String, String> message) { try{ if(slackAlerterEnabled) sendToSlack(message); }catch(Exception e){ logger.log(Level.WARNING, “…", e); } }
  41. リアクティブ・プログラミングとパフォーマンスの関係を確認したい! 2020年4月実施「OCHaCafe Premium」で実施したパフォーマンス検証を再検証 CQRS + Event Sourcing (的な)構成をOCI Streaming(Kafka相当)で実装したが、… •

    非同期&イベント・ドリブン = Kafka って安直過ぎないか? メリット/デメリット確かめよう • Kafka自体がNWサービス、それを扱うために構成も複雑化、運用含めてすぐに大掛かりになる Reactive Messaging in Practice Copyright © 2020, Oracle and/or its affiliates 51
  42. 「OCHaCafe Premium」ではどんな構成でパフォーマンス測定したか? Copyright © 2020, Oracle and/or its affiliates 52

    今回はこれを Reactiveに改良した 構成にして パフォーマンス測定する
  43. async-eshop-order 【Order】 async-relay-order 【relay】 Copyright © 2020, Oracle and/or its

    affiliates キャッシュと非同期連携を組み合わせて負荷と処理時間を切り離し キャッシュ+非同期・パターン async-eshop-mall 【Mall】 PRODUCT INVENTORY ORDER ORDER_ITEM redis-mall PRODUCT INVENTORY kProductStream kOrderStream async-relay-mall 【relay】 async-eshop-cart 【Cart】 async-relay-cart 【relay】 redis-cart PRODUCT INVENTORY CART CART_ITEM 53 OCI Streaming OCI Streaming
  44. OCI Streaming OCI Streaming async-eshop-order 【Order】 async-relay-order 【relay】 Copyright ©

    2020, Oracle and/or its affiliates サービス間連携にOCI Streamingを入れず、代わりにReactive Messagingを使う キャッシュ+非同期・パターン → アーキテクチャ修正 async-eshop-mall 【Mall】 PRODUCT INVENTORY ORDER ORDER_ITEM redis-mall PRODUCT INVENTORY kProductStream kOrderStream async-relay-mall 【relay】 async-eshop-cart 【Cart】 async-relay-cart 【relay】 redis-cart PRODUCT INVENTORY CART CART_ITEM ✖ ✖ ✖ ✖ ✖ Reactive Messaging Reactive Messaging P P S S S 54 P
  45. async-eshop-order 【Order】 Copyright © 2020, Oracle and/or its affiliates サービス間連携にStreamingを入れず、代わりにReactive

    Messagingを使う キャッシュ+非同期・パターン → アーキテクチャ修正 async-eshop-mall 【Mall】 PRODUCT INVENTORY ORDER ORDER_ITEM redis-mall PRODUCT INVENTORY async-eshop-cart 【Cart】 redis-cart PRODUCT INVENTORY CART CART_ITEM Reactive Messaging Reactive Messaging P P S S S 55 P REST REST REST
  46. パフォーマンス測定結果 – 全体 Copyright © 2020, Oracle and/or its affiliates

    737 1933 556 401 1693 5392 1823 1621 0 1000 2000 3000 4000 5000 6000 モノリシック 機能分類 キャッシュ+非同期 Reactive Streams Messaging 応答速度 (ms) 100 VUでの限界性能 (応答速度) 平均 最大 113.1 43.0 149.8 222.7 0.0 50.0 100.0 150.0 200.0 250.0 モノリシック 機能分類 キャッシュ+非同期 Reactive Streams Messaging スループット (tps) 100 VUでの限界性能 (スループット) 平均スループット +48% -28% 56 Reactive Messaging Reactive Messaging
  47. レスポンスタイムが28%改善、スループットが48%改善 パフォーマンス測定結果 – キャッシュ+非同期 v.s. Reactive改良版 Copyright © 2020, Oracle

    and/or its affiliates 57 556 401 1823 1621 0 200 400 600 800 1000 1200 1400 1600 1800 2000 キャッシュ+非同期 Reactive Streams Messaging 応答速度 (ms) 100 VUでの限界性能 (応答速度) 平均 最大 149.8 222.7 0.0 50.0 100.0 150.0 200.0 250.0 キャッシュ+非同期 Reactive Streams Messaging スループット (tps) 100 VUでの限界性能 (スループット) 平均スループット +48% -28% Reactive Messaging Reactive Messaging
  48. メリット • 非同期&イベントドリブンはOCI Streaming (≒Managed Kafka)を使わずフレームワーク レベル(今回はMicroProfile Reactive Messaging)で実現することが可能 •

    構成単純化、ポッド数も削減 • サービス間の通信はRESTのみ、トレーシング も容易 考慮点 • メッセージが永続化されているKafkaの方が、 障害発生時の再処理等が簡単そうに見える? • バックプレッシャー対応&異常系処理のベスト プラクティスの蓄積が課題 • 「Kafkaは絶対落ちない」前提なら正、Kafkaの 障害を考慮しなくてよいのはメリット レスポンスタイムが28%改善 • リクエスト受信 → OCI Streaming送信の際の 接続&通信処理の時間が削減されたと推察 • 改良版では受信したリクエストをPublisherが即 publishしてリターン → 後続の通信処理は別スレッドで実行 スループットが48%改善 • OCI Streaming送受信に関わる処理負荷により 発生していたスレッド滞留が解消され、全体 のスループットが改善されたと推察 その他 • Pubisher → (RM) → Kafka Connector → Kafka の折衷パターンは考慮の価値あり パフォーマンス測定 – 考察 Copyright © 2020, Oracle and/or its affiliates 58
  49. async-eshop-order 【Order】 Copyright © 2020, Oracle and/or its affiliates あれ?

    もっと簡単に出来るのでは? → アーキテクチャ再修正 async-eshop-mall 【Mall】 PRODUCT INVENTORY ORDER ORDER_ITEM redis-mall PRODUCT INVENTORY async-eshop-cart 【Cart】 redis-cart PRODUCT INVENTORY CART CART_ITEM MP RS Messaging MP RS Messaging P P S S S 59 P 非同期 REST 非同期 REST 非同期 REST MicroProfile使わなくても JAX-RS Reactive Client 使えば良くね? ✖ ✖
  50. Reactive REST Client よりも Reactive Messaging の方が良い結果となる パフォーマンス追加測定&比較結果 Copyright ©

    2020, Oracle and/or its affiliates 60 556 401 423 1823 1621 1605 0 200 400 600 800 1000 1200 1400 1600 1800 2000 キャッシュ+非同期 Reactive Streams Messaging Reactive REST Client 応答速度 (ms) 100 VUでの限界性能 (応答速度) 平均 最大 149.8 222.7 211.4 0.0 50.0 100.0 150.0 200.0 250.0 キャッシュ+非同期 Reactive Streams Messaging Reactive REST Client スループット (tps) 100 VUでの限界性能 (スループット) 平均スループット +48% -28% -24% +41% Reactive Messaging Reactive Messaging
  51. リアクティブ・システム – 即応性、耐障害性、弾力性、メッセージ駆動 Reactive Programing –リアクティブ・システムを構築するための手法 Reactive Streams / Flow

    –非同期ストリーム処理を実現するためフレームワーク MicroProfile Reactive Streams Operators – Publisher, Subscriber, Processor のステージを連結する仕組み MicroProfile Reactive Messaging – チャネルを経由したメッセージの受け渡し MicroProfile Reactive Messaging/Connector – 外部システムとの連携をチャネル経由で実現 Reactive Programing フレームワークを活用してハイ・パフォーマンスなシステムを構築できる! まとめ Copyright © 2020, Oracle and/or its affiliates 62
  52. デモのソースコード(GitHub) - https://github.com/oracle-japan/ochacafe-reactive Oracle Cloud Hangout Cafe 過去会のスライド • OCHaCafe2#4

    Cloud Native時代のモダンJavaの世界 http://tiny.cc/ochacafe-mp-slide • OCHaCafe Premium #2 クラウド・アプリケーションのパフォーマンス http://tiny.cc/ochacafe-premium-20200403 Helidon プロジェクト → helidon.io Reactive Streams → https://github.com/reactive-streams/reactive-streams-jvm Eclipse MicroProfile Reactive Project • https://download.eclipse.org/microprofile/microprofile-reactive-streams-operators-1.0/ • https://download.eclipse.org/microprofile/microprofile-reactive-messaging-1.0/ 参照情報など Copyright © 2020, Oracle and/or its affiliates 63