RabbitMQ's Java API docs を読んだだけで、非常に有益でわかりやすいことがわかりました。発行用の簡単なChannel
を設定する方法の例/ consumptionは非常に簡単に理解できますが、非常に単純/基本的な例であり、重要な質問が残されました:1+ Channels
を設定するにはどうすればよいですか複数のキューとの間でパブリッシュ/消費しますか?
logging
、security_events
、customer_orders
の3つのキューを持つRabbitMQサーバーがあるとします。したがって、3つのキューすべてに対してパブリッシュ/消費するための単一のChannel
が必要であるか、3つの個別のChannels
があり、それぞれが単一のキュー専用です。
さらに、RabbitMQのベストプラクティスでは、コンシューマスレッドごとに1 Channel
を設定する必要があります。この例では、security_events
はコンシューマスレッドを1つだけ使用しても問題ありませんが、logging
とcustomer_order
は両方ともボリュームを処理するために5つのスレッドを必要とします。だから、私が正しく理解していれば、それは私たちが必要とすることを意味しますか:
Channel
およびsecurity_events
との間でパブリッシュ/コンシュームするための1つのコンシューマスレッド。そしてChannels
およびlogging
との間でパブリッシュ/コンシュームするための5つのコンシューマスレッド。そしてChannels
およびcustomer_orders
との間でパブリッシュ/コンシュームするための5つのコンシューマスレッドここで私の理解が誤っている場合、私を修正することから始めてください。いずれにせよ、戦闘に疲れたRabbitMQのベテランが、ここで私の要件を満たすパブリッシャー/コンシューマーをセットアップするためのまともなコード例で「点をつなぐ」のを助けてくれますか? 前もって感謝します!
最初の理解にはいくつかの問題があると思います。率直に言って、私は次を見て少し驚いています:both need 5 threads to handle the volume
。その正確な番号が必要だとどうやって特定しましたか? 5つのスレッドで十分であるという保証はありますか?
RabbitMQは調整および時間テストされているため、適切な設計と効率的なメッセージ処理がすべてです。
問題を確認して、適切な解決策を見つけてみましょう。ところで、メッセージキュー自体は、本当に優れたソリューションがあるという保証を提供しません。何をしているのかを理解し、さらに追加のテストを行う必要があります。
間違いなくご存知のように、多くのレイアウトが可能です。
B
を使用して、1
プロデューサーN
コンシューマーの問題。スループットがとても心配なので。ご想像のとおり、RabbitMQの動作は非常に良好です( source )。 prefetchCount
に注意してください。後で対処します。
したがって、十分なスループットを確保するには、メッセージ処理ロジックが適切な場所である可能性があります。当然、メッセージを処理する必要があるたびに新しいスレッドにまたがることができますが、最終的にそのようなアプローチはシステムを殺します。基本的に、取得するレイテンシーの大きいスレッドが多くなります(必要に応じて Amdahlの法則 を確認できます)。
( アムダールの法則の説明 を参照)
ヒント#1:スレッドに注意し、ThreadPoolsを使用する( details )
スレッドプールは、Runnableオブジェクト(ワークキュー)のコレクションおよび実行中のスレッドの接続として説明できます。これらのスレッドは常に実行されており、新しい作業の作業クエリをチェックしています。実行する新しい作業がある場合、このRunnableを実行します。 Threadクラス自体がメソッドを提供します。 execute(Runnable r)は、新しいRunnableオブジェクトを作業キューに追加します。
public class Main {
private static final int NTHREDS = 10;
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(NTHREDS);
for (int i = 0; i < 500; i++) {
Runnable worker = new MyRunnable(10000000L + i);
executor.execute(worker);
}
// This will make the executor accept no new threads
// and finish all existing threads in the queue
executor.shutdown();
// Wait until all threads are finish
executor.awaitTermination();
System.out.println("Finished all threads");
}
}
ヒント2:メッセージ処理のオーバーヘッドに注意してください
これは明らかな最適化手法です。小さくて簡単に処理できるメッセージを送信する可能性があります。全体のアプローチは、継続的に設定および処理される小さなメッセージについてです。大きなメッセージは最終的に悪い冗談を言うので、それを避ける方が良いです。
それで、小さな情報を送る方が良いのですが、処理はどうですか?ジョブを送信するたびにオーバーヘッドが発生します。バッチ処理は、受信メッセージ率が高い場合に非常に役立ちます。
たとえば、単純なメッセージ処理ロジックがあり、メッセージが処理されるたびにスレッド固有のオーバーヘッドが発生したくないとしましょう。その非常に単純なCompositeRunnable can be introduced
:
class CompositeRunnable implements Runnable {
protected Queue<Runnable> queue = new LinkedList<>();
public void add(Runnable a) {
queue.add(a);
}
@Override
public void run() {
for(Runnable r: queue) {
r.run();
}
}
}
または、処理するメッセージを収集して、わずかに異なる方法で同じことを行います。
class CompositeMessageWorker<T> implements Runnable {
protected Queue<T> queue = new LinkedList<>();
public void add(T message) {
queue.add(message);
}
@Override
public void run() {
for(T message: queue) {
// process a message
}
}
}
このようにして、メッセージをより効率的に処理できます。
ヒント#3:メッセージ処理を最適化する
あなたが知っているという事実にもかかわらず、メッセージを並行して処理することができます(Tip #1
)処理のオーバーヘッドを削減(Tip #2
)すべてを迅速に行う必要があります。冗長な処理ステップ、重いループなどは、パフォーマンスに大きな影響を与える可能性があります。興味深い事例研究をご覧ください。
適切なXMLパーサーを選択することでメッセージキューのスループットを10倍向上
ヒント#4:接続とチャネル管理
( ソース )
すべてのヒントは完全に連携して機能します。追加の詳細が必要な場合は、お気軽にお知らせください。
完全な消費者の例( source )
次の点に注意してください:
prefetchCount
は非常に便利かもしれません:このコマンドを使用すると、コンシューマは受信準備ができている未確認メッセージの量を指定するプリフェッチウィンドウを選択できます。プリフェッチカウントをゼロ以外の値に設定すると、ブローカーはその制限に違反するメッセージをコンシューマに配信しません。ウィンドウを前方に移動するには、コンシューマーはメッセージ(またはメッセージのグループ)の受信を確認する必要があります。
例:
static class Worker extends DefaultConsumer {
String name;
Channel channel;
String queue;
int processed;
ExecutorService executorService;
public Worker(int prefetch, ExecutorService threadExecutor,
, Channel c, String q) throws Exception {
super(c);
channel = c;
queue = q;
channel.basicQos(prefetch);
channel.basicConsume(queue, false, this);
executorService = threadExecutor;
}
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
Runnable task = new VariableLengthTask(this,
envelope.getDeliveryTag(),
channel);
executorService.submit(task);
}
}
以下を確認することもできます。
スレッドとチャネルを使用して実装できます。必要なのは、ものを分類する方法、つまり、ログインからのすべてのキュー項目、security_eventsからのすべてのキュー要素などです。分類はroutingKeyを使用して達成できます。
例:アイテムをキューに追加するたびに、ルーティングキーを指定します。プロパティ要素として追加されます。これにより、特定のイベントから値を取得できますlogging。
次のコードサンプルは、クライアント側でそれを行う方法を説明しています。
例:
ルーティングキーは、チャネルのタイプを識別し、タイプを取得するために使用されます。
たとえば、ログインタイプに関するすべてのチャネルを取得する必要がある場合、ルーティングキーをログインまたはその他のキーワードとして指定して、それを識別する必要があります。
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
string routingKey="login";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
あなたは見ることができます ここ 分類の詳細については..
公開部分が終了すると、スレッド部分を実行できます。
この部分では、カテゴリに基づいて公開データを取得できます。すなわち;あなたの場合、logging、security_events、customer_ordersなどのルーティングキー.
スレッドでデータを取得する方法については、例を参照してください。
例:
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//**The threads part is as follows**
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String queueName = channel.queueDeclare().getQueue();
// This part will biend the queue with the severity (login for eg:)
for(String severity : argv){
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
}
boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "myConsumerTag",
new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String routingKey = envelope.getRoutingKey();
String contentType = properties.contentType;
long deliveryTag = envelope.getDeliveryTag();
// (process the message components here ...)
channel.basicAck(deliveryTag, false);
}
});
これで、タイプlogin(routing key)のキュー内のデータを処理するスレッドが作成されます。この方法により、複数のスレッドを作成できます。それぞれが異なる目的を果たします。
look here スレッド部分の詳細については..