EIP / Competing Consumers


EIP

Competing Consumers

一言要約

複数のメッセージを同時に処理できるよう、一つのチャネルに複数のCompeting Consumersを作成する

要約

アプリケーションはMessagingを利用しているが、チャネル追加ほど速くはメッセージを処理できない。

メッセージクライアントはどのように複数のメッセージを同時に処理しているのか?

MessageMessage Channelを通じてシーケンシャルに処理するが、ボトルネックにもなりアプリケーション全体のスループットに影響する。原因は複数のsenderによるものか、ネットワーク障害にてメッセージのバックログが一斉に同時発生したか、receiver障害がバックログを引き起こしたか、各メッセージが生成&送信よりも消費&処理にかなり時間がかかっているかである。

アプリケーションは複数のチャネルを利用出来るが、1つのチャネルがボトルネックになっていて別が空であっても、senderはどのチャネルを利用するかは分からない。しかしながら、複数のチャネルには複数のコンシューマ(チャネル毎に1つ)を可能にし同時にメッセージを処理できる利点を持っている。しかしこれが上手くいったとしてもスループットはアプリケーションが定義したチャネル数に制限される。

必要なのは、チャネルが複数のコンシューマを持つ方法である。

単一のチャネルに複数のCompeting Consumersを作成することで、コンシューマは複数のメッセージを同時に処理できる

CompetingConsumers.gif

Competing Consumersは複数のコンシューマであり、単一のPoint-to-Point Channelからメッセージを受け取るために全て作られる。チャネルがメッセージを配信する際に、コンシューマのいずれかが潜在的にそれを受け取ることが出来る。メッセージングシステムの実装は、どのコンシューマが実際にメッセージを受け取るかを決定するが、実際はコンシューマはreceiverであるために互いに競合する。コンシューマがメッセージを受け取ると、メッセージを処理するためアプリケーションの他の部分を委ねることができる。(このソリューションはPoint-to-Point Channelsでのみ動作する。1つのPublish-Subscribe Channel上にある複数のコンシューマは各メッセージのコピーを作成するだけである。)

それぞれのCompeting Consumers自身のスレッドで実行するので全て並行してメッセージを消費できる。チャネルがメッセージを配信すると、メッセージングシステムのトランザクション制御がコンシューマの1つだけがメッセージを正常に受け取っていることを確認する。コンシューマがメッセージを処理している間、チャネルは他のメッセージを配信でき、他のコンシューマは並行して消費や処理が行える。チャネルはコンシューマを調整し、互いに別のメッセージを受け取ることを確認するのでコンシューマは互いに調整する必要はない。

各コンシューマは異なるメッセージを同時に処理するので、ボトルネックはコンシューマがメッセージを処理する時間の代わりに、チャネルがメッセージをコンシューマにいかに早く送るかによる。限られた数のコンシューマは依然としてボトルネックだが、コンシューマの数を増やせば計算リソースがある限り制約を緩和できる。

同時に実行するには各コンシューマは自身のスレッドで実行しなければならない。Polling Consumersについて、各コンシューマはポーリングを並行で実行するため自身のスレッドを持つ必要があることを意味する。Event-Driven Consumersについて、メッセージングシステムはコンシューマごとに1つのスレッドを持つ必要があり、スレッドはコンシューマにメッセージを渡すために使われたりメッセージを処理するためにコンシューマによって使われる。

洗練されたメッセージングシステムはチャネル上の競合するコンシューマを検出し、各メッセージが単一のコンシューマにのみ配信されていることを保証するMessage Dispatcherを内部的に提供する。これは複数のコンシューマがそれぞれ単一のメッセージのコンシューマと思っていた場合に生じる競合を避けることが出来る。あまり洗練されていないメッセージングシステムは複数のコンシューマが同じメッセージを消費しようとすることがある。これが起きると、どれかのコンシューマが最初に勝ち取ったトランザクションをコミットすると、他のコンシューマはコミットに成功できずトランザクションをロールバックしなければならない。

複数のコンシューマが同じメッセージを消費しようとするメッセージングシステムはTransactional Clientを非常に非効率にしてしまう。クライアントはメッセージを持ち、消費し、メッセージを処理する手間を費やし、コミットしようとすると(メッセージが既に競合者によって消費されているために)できないと思っている。スループットを向上させるのがソリューションのポイントに対して、頻繁にロールバックを行うのは痛い。よって競合するトランザクションのコンシューマのパフォーマンスは注意して測定すべきであり、別のメッセージングシステムの実装や構成によって大きく変動する可能性がある。

Competing Consumersは単一のアプリケーションにおいて複数のコンシューマスレッド間で不可を分散させるだけでなく、複数のアプリケーション間で消費(consumption)負荷を分散することもできる。この方法は、1つのアプリケーションが十分な早さでメッセージを消費できない場合、複数のコンシューマアプリケーションは(おそらくそれぞれが複数のコンシューマスレッドを使用することで)この問題を解決できる。メッセージを消費するために複数のスレッドを使って複数のコンピュータ上で実行する複数のアプリケーションを持つということは事実上無制限のメッセージ処理能力を提供する。唯一の制限は、コンシューマにチャネルからメッセージを配信するメッセージングシステムの能力である。

競合するコンシューマの調整は各メッセージングシステムの実装に依存する。もしクライアントがこの調整自体を実装したい場合はMessage Dispatcherを使ったほうがよい。Competing ConsumersはPolling ConsumersEvent-Driven Consumers、またはそれらの組み合わせになりうる。Transactional Clientsと競合することは、受信操作が正常にコミットせずロールバックしなければならないメッセージを処理する多大な労力を費やしてしまう。

例: シンプルなJMS Competing Consumers

以下にJavaでCompeting Consumersのシンプルな実装例を示す。(ここでは示さない)外部ドライバー/マネージャオブジェクトはそれぞれ独自スレッドで実行し、stopRunning()で止まる。

JMSセッションはシングルスレッドである必要がある。単一のセッションはメッセージ消費の順番をシリアライズする。よって、それぞれCompeting Consumersが独自スレッドで正しく動作し、コンシューマがへ並列でメッセージを消費するようにするには、各コンシューマが自身のSession(と自身のMessageConsumer?)を持つ必要がある。JMS仕様では、並行するQueueReceivers?(例: Competing Consumers)がどのように動作するかのセマンティクスを規定しておらず、このアプローチがちゃんと動作することさえ必要としない。よって、この手法を使うアプリケーションは移植可能であることを想定しておらず、異なるJMSプロバイダでは動作が異なる場合がある。

コンシューマクラスはRunnableをimplementsし自身のスレッドで実行するので、コンシューマは並行して実行出来る。コンシューマの全ては同じConnectionを共有するがそれぞれがSessionを生成する。重要なのは、各セッションは単一のスレッドのみサポートする。各コンシューマは繰り返しキューからメッセージを受け取り処理する。

シンプルはCompeting Consumersを実装するのは簡単である。主な留意点は、コンシューマをRunnableにし、自身のスレッドで実行することである。

関連パターン: Event-Driven ConsumerMessageMessage ChannelMessage DispatcherMessagingPoint-to-Point ChannelPolling ConsumerPublish-Subscribe ChannelTransactional Client

担当者のつぶやき

  • 文章だけ読むと、スレッドが多くなって実際の運用上ゾンビなスレッドが残ったりとかしてリソース問題を抱えそうな気がするけど、そんな事ないのかな?

みんなの突っ込み