Quarkus リアクティブアーキテクチャ
Quarkusはリアクティブです。それだけではありません。Quarkusは、リアクティブ型プログラミングと命令型プログラミングを統合しています。リアクティブ型なコンポーネントと命令型のコンポーネントを実装し、 同じ アプリケーション内で組み合わせることができます。異なるスタック、ツール、APIを使用する必要はありません。Quarkusは両方の世界の架け橋です。
このページでは、 リアクティブ とは何を意味するのか、またQuarkusがどのようにそれを可能にするのかを説明します。また、実行モデルとプログラミングモデルについても説明します。最後に、リアクティブな機能を提供するQuarkusのエクステンションの一覧を示します。
リアクティブ とは?
リアクティブ という言葉は多重定義 (オーバーロード) されていて、バックプレッシャー、モナド、イベントドリブンアーキテクチャなど、多くの概念と関連付けられています。そこで、我々の リアクティブ の意味を明確にしましょう。
リアクティブ とは、応答性の高い分散システムやアプリケーションを構築するための原則とガイドラインのセットです。 リアクティブマニフェストでは、 リアクティブシステム を4つの特徴を持つ分散システムとしています。
-
応答性 (Responsive) - タイムリーに対応してくれること
-
伸縮性 (Elastic) - 変動する負荷に適応すること
-
回復性 (Resilient) - 障害をグレースフルに処理すること
-
非同期メッセージパッシング (Asynchronous message passing) - リアクティブシステムのコンポーネントがメッセージで相互作用すること
これに加えて、 リアクティブ原則ホワイトペーパーには、リアクティブシステムの構築に役立つ一連のルールとパターンが記載されています。
リアクティブシステムとQuarkus
リアクティブシステムは、「分散システムを正しく実現する」という言葉で表現されるアーキテクチャスタイルです。非同期のメッセージパッシングに頼ることで、異なるコンポーネント間の(空間的にも時間的にも)疎結合を実現しています。メッセージは仮想的な宛先に送られます。受信者はどこにでもいることができますし、メッセージ送信時にはまだ存在していないこともあります。弾力性の柱は、負荷に応じて個々のコンポーネントをスケールアップまたはスケールダウンすることができます。また、弾力性は冗長性を提供し、これは回復性の柱に役立ちます。失敗は避けられません。リアクティブシステムを構成するコンポーネントは、障害をグレースフルに処理し、障害の連鎖を回避し、自己適応しなければなりません。
レスポンシブなシステムは、障害に直面しても、負荷が変動しても、リクエストを処理し続けることができます。Quarkusはそのために調整されています。リアクティブなシステムの設計、実装、運用に役立つ機能を備えています。
リアクティブ・アプリケーション
Quarkusは、リアクティブなシステムの構築を支援するだけではありません。また、各構成要素がリアクティブ原則を実行し、非常に効率的であることも担保します。
特にクラウドやコンテナ化された環境では、効率化が不可欠です。CPUやメモリなどのリソースは、複数のアプリケーションで共有されます。メモリを大量に消費する貪欲なアプリケーションは非効率的であり、兄弟アプリケーションにペナルティを与えます。より多くのメモリ、CPU、またはより大きな仮想マシンを要求する必要があるかもしれません。これは、毎月のクラウド料金を増加させるか、デプロイ密度を低下させるかします。
I/Oは、ほぼすべての現代のシステムに不可欠な要素です。リモートサービスの呼び出し、データベースとのやりとり、ブローカーへのメッセージ送信など、全てI/Oベースの操作です。それらを効率的に処理することは、貪欲なアプリケーションを避けるために重要です。このため、QuarkusではノンブロッキングI/Oを使用しています。これにより、少ない数のOSスレッドで多数の同時I/Oを管理できます。その結果、Quarkusのアプリケーションは、より高い同時実行性を可能にし、より少ないメモリしか使用せず、デプロイ密度が向上します。
Quarkusはどのようにしてリアクティブを実現するのですか?
Quarkusの中身にはリアクティブエンジンがあります。このエンジンは、Eclipse Vert.xとNettyを搭載しており、ノンブロッキングI/Oインタラクションを処理します。
Quarkusのエクステンションとアプリケーションコードは、このエンジンを使用して、I/Oインタラクションのオーケストレーション、データベースとのインタラクション、メッセージの送受信などを行うことができます。
リアクティブ実行モデル
ノンブロッキングI/Oを使用することは非常に大きなメリットがありますが、それは無料ではありません。実際、ノンブロッキングI/Oを使用すると、従来のフレームワークで使用されていたものとはまったく異なる新しい実行モデルが導入されます。
従来のアプリケーションは、ブロッキングI/Oと命令型(シーケンシャル)の実行モデルを採用しています。そのため、HTTPエンドポイントを公開するアプリケーションでは、各HTTPリクエストが1つのスレッドに関連付けられています。一般的には、そのスレッドはリクエスト全体を処理することになり、スレッドはそのリクエストの間、そのリクエストだけにサービスを提供することに縛られます。処理の中でリモートサービスとのやり取りが必要な場合は、ブロッキングI/Oを使用します。スレッドはブロックされ、I/Oの結果を待つことになります。このモデルは、すべてが逐次処理であるため、開発は簡単ですが、いくつかの欠点があります。同時リクエストを処理するには、複数のスレッドが必要になるため、ワーカースレッドプールを導入する必要があります。このプールのサイズは、アプリケーションの並行性を制約します。また、各スレッドにはメモリとCPUのコストがかかります。スレッドプールが大きいと、貪欲なアプリケーションになってしまいます。
上で見てきたように、ノンブロッキングI/Oはその問題を回避します。数個のスレッドで多数の同時I/Oを処理することができます。HTTPエンドポイントの例に戻ると、リクエスト処理はこのI/Oスレッドの1つで実行されます。I/Oスレッドは数が少ないので、賢く使いこなす必要があります。リクエスト処理でリモートサービスを呼び出す必要がある場合は、もうスレッドをブロックすることはできません。I/Oをスケジューリングし、継続(continuation、I/Oが完了した後に実行するコード)を渡します。
このモデルははるかに効率的ですが、これらの継続性を表現するためのコードを書く方法が必要です。
リアクティブ・プログラミング・モデル
ノンブロッキングI/OとメッセージパッシングをベースにしたQuarkusのアーキテクチャでは、継続性の表現方法が異なる複数のリアクティブ開発モデルをサポートしています。Quarkusでリアクティブコードを書くには、主に以下の2つの方法があります。
-
Mutinyを用いたリアクティブ・プログラミング、および
-
Kotlinのコルーチン
まず、 Mutinyは直感的なイベント駆動型のリアクティブ・プログラミング・ライブラリです。Mutinyでは、イベント駆動型のコードを書きます。あなたのコードは、イベントを受け取り、それを処理するパイプラインです。パイプラインの各ステージは継続と見ることができ、パイプラインの上流部分がイベントを発するとMutinyがそれらを呼び出すからです。
MutinyのAPIは、コードベースの読みやすさとメンテナンス性を向上させるために調整されています。Mutinyは、同時実行を含む非同期アクションを組織化するために必要なすべてを提供します。また、個々のイベントやイベントのストリームを操作するための大規模な演算子のセットも提供しています。
Mutinyと、Quarkusでの使用に関する詳細は、 Mutinyサポートドキュメントを参照してください。 |
コルーチンは、非同期のコードを逐次的に記述する方法です。I/O時にコードの実行を一時停止し、残りのコードを継続として登録します。Kotlinコルーチンは、Kotlinで開発する場合に最適であり、シーケンシャルコンポジション(相互依存する非同期タスクのチェーン)を表現するだけで済みます。
命令型とリアクティブ型の統一
開発モデルの変更は簡単ではありません。再学習および、ノンブロッキングでコードを再構築する必要があります。幸いなことに、その必要はありません。
Quarkusは、リアクティブエンジンを搭載しているため、本質的にリアクティブです。しかし、アプリケーション開発者としては、リアクティブなコードを書く必要はありません。Quarkusはリアクティブ型と命令型を統合しています。つまり、従来のブロッキングアプリケーションをQuarkusで書くことができるのです。しかし、I/Oスレッドをブロックしないようにするにはどうすればいいのでしょうか?Quarkusでは、必要に応じてワーカースレッドに切り替える プロアクターパターンを実装しています。
コード内のヒント( @Blocking
や @NonBlocking
アノテーションなど)のおかげで、Quarkus エクステンションはアプリケーションロジックがブロッキングかノンブロッキングかを判断できます。
先ほどのHTTPエンドポイントの例に戻ると、HTTPリクエストは常にI/Oスレッドで受信されます。そして、リクエストをコードにディスパッチするエクステンションは、スレッドスイッチを回避するためにI/Oスレッドで呼び出すか、ワーカースレッドで呼び出すかを決定します。この判断はエクステンションによって異なります。たとえば、Quarkus REST (旧 RESTEasy Reactive) エクステンションは、 @Blocking
アノテーションを使用して、メソッドをワーカースレッドで呼び出す必要があるか、I/O スレッドで呼び出せるかを判断します。
Quarkusは実用的で多機能です。アプリケーションをどのように開発し、実行するかはユーザーが決定します。命令型の方法やリアクティブ型の方法を使用することもできますし、それらを混在させて、並行性の高いアプリケーションの部分にリアクティブ型の方法を使用することもできます。
リアクティブを可能にするQuarkusエクステンション
Quarkusは、多くのリアクティブAPIと機能を提供しています。このセクションでは、最も重要なものをリストアップしていますが、網羅的なリストではありません。Quarkusはリリースごとに新しい機能を追加しており、 Quarkiverseでは リアクティブ を有効にする多くのエクステンションが提案されています。
HTTP
-
Quarkus REST: Quarkusアーキテクチャに合わせたJakarta RESTの実装です。 リアクティブファーストアプローチに従いますが、
@Blocking
アノテーションを使用した命令型コードも可能です。 -
Reactive Routes: HTTPリクエストをメソッドにルーティングするためにQuarkusが使用するVert.xルーターに直接HTTPルートを登録する宣言的な方法です。
-
RESTクライアント:HTTPエンドポイントを消費できます。 QuarkusのノンブロッキングI/O機能を使用しています。
-
Qute - Quteテンプレートエンジンは、テンプレートをノンブロッキングでレンダリングするリアクティブAPIを公開しています。
データ
-
Hibernate Reactive: Hibernate ORMの、非同期、ノンブロッキングのクライアントを使用してデータベースと対話するバージョンです。
-
Hibernate Reactive with Panache: Hibernate Reactiveの上でアクティブレコードとリポジトリのサポートを提供します。
-
リアクティブPostgreSQLクライアント:PostgreSQLデータベースと対話する非同期かつノンブロッキングなクライアントで、高い並行性を実現します。
-
リアクティブ MySQL クライアント: MySQL データベースと対話する非同期かつノンブロッキングなクライアント
-
MongoDBエクステンション: MongoDBと対話するための命令型およびリアクティブ型(Mutiny)のAPIを公開しています。
-
Mongo with Panacheは、命令型APIとリアクティブ型APIの両方でアクティブレコードサポートを提供します。
-
Cassandraエクステンション: Cassandraと対話するための命令型およびリアクティブ型(Mutiny)のAPIを公開しています。
-
Redisエクステンション: Redisキーバリューストアからデータを保存・取得するための命令型およびリアクティブ型(Mutiny)のAPIを公開しています。
イベント駆動型アーキテクチャ
-
リアクティブ・メッセージング: リアクティブ型のコードと命令型のコードを使って、イベントドリブンなアプリケーションを実装することができます。
-
Kafka Connector for Reactive Messaging: Kafkaのレコードを消費したり書いたりするアプリケーションを実装できます。
-
AMQP 1.0 Connector for Reactive Message: AMQPメッセージを送受信するアプリケーションを実装できます。