web-dev-qa-db-ja.com

Springではどのような「EventBus」を使用しますか?組み込み、Reactor、Akka?

数週間後に新しいSpring 4アプリケーションを開始します。そして、イベント駆動型のアーキテクチャを使用したいと思います。今年、私はあちこちで「Reactor」について読みましたが、ウェブ上でそれを探していると、「Akka」に出会いました。

したがって、現時点では3つの選択肢があります。

それらの本当の比較を見つけることができませんでした。


今のところ、次のようなものが必要です。

  • Xは、_Event E_をリッスンするために登録します
  • Yは、_Event E_をリッスンするために登録します
  • Zは_Event E_を送信します

そして、XYはイベントを受信して​​処理します。

ほとんどの場合これを非同期で使用しますが、確かにいくつかの同期シナリオもあります。そして、ほとんどの場合、常にクラスをイベントとして送信します。 (Reactorサンプルは主に文字列と文字列パターンを使用しますが、オブジェクトもサポートしています)。


私の知る限り、ApplicationEventはデフォルトで同期的に動作し、Reactorは非同期的に動作します。また、Reactorでは、await()メソッドを使用して、同期させることもできます。 AkkaReactorとほぼ同じですが、リモート処理もサポートしています。

Reactorのawait()メソッドに関して:複数のスレッドが完了するのを待つことができますか?それとも、それらのスレッドの一部でさえありますか?上記の例を見ると:

  • Xは、_Event E_をリッスンするために登録します
  • Yは、_Event E_をリッスンするために登録します
  • Zは_Event E_を送信します

XandY」が完了するのを待って、同期させることは可能ですか?そして、Xだけではなく、Yだけ待つようにすることは可能ですか?


たぶんいくつかの選択肢もありますか?たとえばJMSはどうですか?

たくさんの質問がありますが、うまくいけばいくつかの答えを提供できます!

ありがとうございました!


編集:ユースケースの例

  1. 特定のイベントが発生した場合、10000件のメールを作成したいと思います。すべての電子メールは、ユーザー固有のコンテンツで生成される必要があります。そのため、メールを作成し、呼び出し元のスレッドをブロックしない多くのスレッド(最大=システムCPUコア)を作成します。これには数分かかることがあります。

  2. 特定のイベントが発生した場合、不明な数のサービスから情報を収集したいと思います。各フェッチには約100msかかります。ここでは、Reactorのawaitを使用することを想像できます。「メインスレッドで作業を続けるためにこれらの情報が必要だからです。

  3. 特定のイベントが発生した場合、アプリケーションの構成に基づいていくつかの操作を実行したいと思います。そのため、アプリケーションは、消費者/イベントハンドラを動的に(アン)登録できる必要があります。彼らはイベントで彼ら自身のことをしますが、私は気にしません。したがって、これらのハンドラーごとにスレッドを作成し、メインスレッドで作業を続けます。

  4. 単純な分離:基本的にすべてのレシーバーを知っていますが、コード内のすべてのレシーバーを呼び出したくないだけです。これは主に同期的に行われます。

ThreadPoolまたはRingBufferが必要なようですね。これらのフレームワークには動的なRingBufferがあり、必要に応じてサイズが大きくなりますか?

38
Benjamin M

この小さなスペースであなたの質問に適切に答えられるかどうかはわかりません。しかし、私はそれを試してみます! :)

SpringのApplicationEventシステムとReactorは、機能に関してはまったく別物です。 ApplicationEventルーティングは、ApplicationListenerによって処理されるタイプに基づいています。それより複雑なものはすべて、自分でロジックを実装する必要があります(必ずしも悪いことではありません)。ただし、Reactorは、非常に軽量で完全に拡張可能な包括的なルーティングレイヤーを提供します。イベントのサブスクライブとパブリッシュの能力における2つのエンド間の機能の類似性は、イベント駆動型システムの機能です。また、Spring 4の新しい_spring-messaging_モジュールを忘れないでください。これは、Spring Integrationで使用可能なツールのサブセットであり、イベント駆動型アーキテクチャを構築するための抽象化も提供します。

Reactorは、そうでなければ自分で管理しなければならないいくつかの重要な問題を解決するのに役立ちます。

Selector matching:ReactorはSelectorマッチングを実行します。これは、単純な.equals(Object other)呼び出しからの一致範囲を含みます、プレースホルダーの抽出を可能にする、より複雑なURIテンプレートマッチへ。独自のカスタムロジックで組み込みセレクタを拡張して、リッチオブジェクトを通知キー(たとえば、ドメインオブジェクトなど)として使用することもできます。

ストリームAPIとプロミスAPI.await()メソッドを参照して既にPromise AP​​Iに言及しました。ブロック動作を期待する既存のコード向けです。 Reactorを使用して新しいコードを作成する場合、コンポジションとコールバックを使用して、スレッドをブロックしないことでシステムリソースを効果的に利用するほどストレスをかけることはできません。大量のタスクを実行するために少数のスレッドに依存するアーキテクチャでは、呼び出し側をブロックすることはほとんど決して良い考えではありません。先物はクラウドスケーラブルではないため、最新のアプリケーションは代替ソリューションを活用しています。

アプリケーションはStreamsまたはPromisesのいずれかで設計できますが、正直なところ、Streamの方が柔軟だと思います。主な利点は、APIの構成可能性です。これにより、ブロックせずにアクションを依存関係チェーンにまとめることができます。メールのユースケースに基づいた完全にオフカフの例として、次のことを説明します。

_@Autowired
Environment env;
@Autowired
SmtpClient client;

// Using a ThreadPoolDispatcher
Deferred<DomainObject, Stream<DomainObject>> input = Streams.defer(env, THREAD_POOL);

input.compose()
  .map(new Function<DomainObject, EmailTemplate>() {
    public EmailTemplate apply(DomainObject in) {
      // generate the email
      return new EmailTemplate(in);
    }
  })
  .consume(new Consumer<EmailTemplate>() {
    public void accept(EmailTemplate email) {
      // send the email
      client.send(email);
    }
  });

// Publish input into Deferred
DomainObject obj = reader.readNext();
if(null != obj) {
  input.accept(obj);
}
_

Reactorは Boundary も提供します。これは基本的に、任意のコンシューマーをブロックするためのCountDownLatchです(したがって、Promiseを構築する必要はありません。 Consumer完了のブロック)。その場合は生のReactorを使用し、on()およびnotify()メソッドを使用してサービスステータスチェックをトリガーできます。

ただし、いくつかの点では、Futureから返されるExecutorServiceが欲しいようです。なぜ物事を単純にしないのですか? Reactorは、スループットのパフォーマンスとオーバーヘッドの効率が重要な場合にのみ真のメリットがあります。呼び出しスレッドをブロックしている場合、おそらくReactorがもたらす効率の向上を一掃することになります。その場合は、より伝統的なツールセットを使用する方がよいでしょう。

Reactorのオープン性についての良いところは、2つが相互作用するのを止めるものは何もないということです。 FuturesConsumersを静的なしで自由に混在させることができます。その場合は、最も低速なコンポーネントと同じくらい高速になることを覚えておいてください。

30
Jon Brisbin

SpringのApplicationEventは無視するようにしましょう。これは、あなたの要求に合わせて設計されていないためです(Beanのライフサイクル管理についての詳細)。

把握する必要があるのは、やりたいかどうかです

  1. オブジェクト指向の方法(つまり、実行中に登録されるアクター、ダイナミックコンシューマー)[〜#〜] or [〜#〜]
  2. サービスの方法(静的コンシューマー、起動時に登録)。

XYの例を使用すると、次のようになります。

  1. 一時インスタンス(1)またはそれら
  2. 長寿命シングルトン/サービスオブジェクト(2)?

消費者をその場で登録する必要がある場合は、Akkaが良い選択です(私は一度も使用したことがないため、リアクタについてはわかりません)。一時オブジェクトでの消費を行いたくない場合は、JMSまたはAMQPを使用できます。

また、これらの種類のライブラリが2つの問題を解決しようとしていることを理解する必要があります。

  1. 並行性(つまり、同じマシンで並行して物事を行う)
  2. 配布(つまり、複数のマシンで並行して物事を行う)

ReactorとAkkaは主に#1に焦点を合わせています。 Akkaは最近クラスターサポートを追加し、アクターの抽象化により#2の実行が容易になりました。メッセージキュー(JMS、AMQP)は#2に焦点を当てています。

私自身の作業では、サービスルートを実行し、大幅に変更されたGuava EventBusとRabbitMQを使用します。 Guava Eventbus に似た注釈を使用しますが、バスで送信されるオブジェクトの注釈もありますが、POCとして非同期モードでGuavaのEventBusを使用して、自分のように独自の注釈を作成できます。

動的なコンシューマーが必要だと思うかもしれませんが(1)、ほとんどの問題は単純なpub/subで解決できます。また、動的なコンシューマーの管理は難しい場合があります(そのため、アクターは適切な選択です。なぜなら、アクターモデルにはこのためのあらゆる種類の管理があるからです)。

6
Adam Gent

フレームワークに必要なものを慎重に定義します。フレームワークに必要以上の機能がある場合、常に良いとは限りません。機能が増えると、バグが増え、学習するコードが増え、パフォーマンスが低下します。

考慮すべき機能は次のとおりです。

  • アクターの性質(スレッドまたは軽量オブジェクト)
  • マシンクラスターで動作する機能(Akka)
  • 永続メッセージキュー(JMS)
  • 信号(情報のないイベント)、遷移(異なるポートからのメッセージを複雑なイベントに結合するオブジェクト、ペトリネットを参照)などの特定の機能。

Awaitのような同期機能に注意してください-スレッド全体をブロックし、アクターがスレッドプールで実行されると危険です(スレッド不足)。

見るべき他のフレームワーク:

Fork-Join Pool -場合によっては、スレッド不足のないawaitを許可します

Scientific workflow systems

Javaのデータフローフレームワーク -シグナル、遷移

ADD-ON:2種類のアクター。

一般に、並列作業システムは、アクティブノードが互いにメッセージを送信するグラフとして表すことができます。 Javaでは、他のほとんどの主流言語と同様に、アクティブノード(アクター)は、スレッドまたはスレッドプールによって実行されるタスク(実行可能または呼び出し可能)として実装できます。通常、アクターの一部はスレッドであり、一部はタスクです。どちらのアプローチにも長所と短所があるため、システム内の各アクターに最適な実装を選択することが重要です。簡単に言うと、スレッドはブロック(およびイベントを待機)できますが、スタックのために多くのメモリを消費します。タスクはブロックせず、共有プール(プール内のスレッド)を使用できます。

タスクがブロッキング操作を呼び出す場合、プールされたスレッドはサービスから除外されます。多くのタスクがブロックすると、すべてのスレッドが除外され、デッドロックが発生します。ブロックされたタスクのブロックを解除できるタスクは実行できません。この種のデッドロックはthread starvationと呼ばれます。スレッドの枯渇を防ぐために、スレッドプールを無制限に構成する場合、タスクをスレッドに変換するだけで、タスクの利点が失われます。

タスク内のブロック操作の呼び出しを排除するには、タスクを2つ(またはそれ以上)に分割する必要があります。最初のタスクはブロック操作を呼び出して終了し、残りはブロック操作の終了時に開始される非同期タスクとしてフォーマットされます。もちろん、ブロッキング操作には代替の非同期インターフェースが必要です。したがって、たとえば、ソケットを同期的に読み取る代わりに、NIOまたはNIO2ライブラリを使用する必要があります。

残念ながら、標準のJavaライブラリには、キューやセマフォなどの一般的な同期機能に対応する非同期のツールがありません。幸いなことに、Javaの Dataflow framework たとえば)。

そのため、純粋に非ブロッキングタスクで計算を行うことは可能ですが、コードのサイズが大きくなります。可能な限りスレッドを使用し、タスクは単純で大規模な計算にのみ使用することをお勧めします。

3