前回の記事では、作業キューの作成に気づきました。作業キューの背後にあるrabbitMQは、実際には各タスクメッセージを1つのコンシューマにのみ送信します。この記事では、メッセージを複数のコンシューマーにプッシュする方法を研究します。このモデルは、パブリッシュ/サブスクライブと呼ばれます。
このパターンを説明するために、簡単なロギングシステムを構築します。これにはプログラムの2つの部分が含まれます。最初の部分はログメッセージを送信することで、2番目の部分はそれらを受信して印刷します。
ロギングシステムでは、実行中のすべてのコンシューマプログラムがメッセージを受信できます。そこで、レシーバーを実行してログをディスクに書き込むと同時に、別のコンシューマーを実行してログメッセージを画面に出力します。
基本的に、すべてのコンシューマにログメッセージをプッシュします。
1.メッセージ交換
前回の記事では、キューにメッセージを送信し、キューからメッセージを取り出しました。次に、RabbitMQの完全なメッセージモデルを紹介しましょう。
以前の内容を簡単に確認してみましょう。
- プロデューサーアプリケーションはメッセージを送信します。
- メッセージキューは、メッセージの保存とキャッシュに使用されます。
- コンシューマープログラムがメッセージを受信する
RabbitMQのメッセージ送信モデルの中心的な考え方は、プロデューサーがメッセージをメッセージキューに直接送信しないことです。実際、プロデューサーは自分のメッセージがどのキューにキャッシュされるかわかりません。
実際、プロデューサーはエクスチェンジにメッセージを送信できます(メッセージエクスチェンジ)。Exchangeは非常に単純な問題であり、一方の側でプロデューサーからメッセージを受信し、もう一方の側でメッセージキューにメッセージをプッシュします。Exchangeは、メッセージを受信したときに何をすべきかを知っている必要があります。このメッセージを指定されたメッセージキューにプッシュする必要がありますか?または、すべてのキューにメッセージをプッシュしますか?またはニュースを捨てますか?これらのルールは、交換タイプを使用して定義できます。
直接、トピック、ヘッダー、ファンアウトなど、いくつかの交換タイプがあります。ここでは、主に最後のファンアウトを見ていきます。ここでは、logsという名前のエクスチェンジを作成し、fanoutと入力します。
channel.exchangeDeclare( "logs"、 "fanout");
ファンアウト型の交換は非常に簡単です。それは、受信できるすべてのメッセージを、知っているすべてのキューにブロードキャストするということです。これがまさに私たちのロギングシステムに必要なものです。
リスト交換:
サーバーでrabbitmqctlコマンドを使用して、RabbitMQサーバー上のすべてのメッセージ交換をリストできます。
rabbitmqctl list_exchanges
Listing exchanges ...
direct
amq.direct direct
amq.fanout fanout
amq.headers headers
amq.match headers
amq.rabbitmq.log topic
amq.rabbitmq.trace topic
amq.topic topic
logs fanout
このリストには、amp。*の形式でいくつかの交換があり、デフォルトの(名前のない)スイッチもあります。これらはデフォルトで作成されます。
名前のない交換:
前の記事では交換の関連知識を使用しませんでしたが、メッセージを送信することはできます。正常に送信できる理由は、デフォルトの交換を使用しているため、( "")を使用して識別しています。
channel.basicPublish("", "hello", null, message.getBytes());
最初のパラメーターは、取引所の名前です。空の文字列のシンボルは、デフォルトまたは名前のない交換を指します。メッセージは、routingKeyに従って指定されたメッセージキューにルーティングされます。
次に、メッセージを名前付き交換にプッシュします。
channel.basicPublish( "logs", "", null, message.getBytes());
2.一時キュー
前の記事では、指定した名前(helloとtask_queue)のメッセージキューを使用しました。対応するプロデューサーとコンシューマーが同じメッセージキュー名を使用することが重要です。
しかし、これは私たちのログシステムの場合ではなく、ログメッセージの一部だけでなく、すべてのログメッセージを受信することを望んでいます。過去の履歴ログに関係なく、現在のログメッセージを処理するだけで済みます。これを実現するには、次の2つの手順を実行する必要があります。
- RabbitMQとの接続を確立するたびに、キューを更新して空にする必要があります。これを実現するために、ランダムな名前でキューを作成するか(ランダムネスは自分で定義できます)、またはサーバーに自動的にランダムキューを作成させることができます。
- コンシューマが切断すると、キューは自動的に削除されます。
Javaクライアントを使用する場合、パラメーターなしでqueueDeclareメソッドを使用して、名前があり、排他的で、自動的に削除されるキューを作成します。
String queueName = channel.queueDeclare().getQueue();
これは、amq.gen-JzTY20BRgKO-HjmUJj0wLgのような形のランダムな名前のキューを取得するためのものです。
3.バインディング
ファンアウトタイプの交換とキューを作成しました。次に、取引所にメッセージをキューに送信させる必要があります。Exchangeとキューの関係はバインドと呼ばれます。
channel.queueBind(queueName, "logs", "");
今後、ログという名前のエクスチェンジは、キュー内のメッセージを忘れます。
バインディングリストを表示します。
rabbitmqctl list_bindingsコマンドを使用して、すべての既存のバインディングを表示します。
4.最終的な実現
ログメッセージを送信するプロデューサープログラムは、前のプログラムと大差ありません。最大の違いは、以前に名前が付けられなかったデフォルトの交換ではなく、名前付き交換にメッセージをプッシュすることです。メッセージを送信するときにroutingKeyを提供する必要がありますが、ファンアウトタイプの交換では無視できます。以下はプロデューサーのコードEmitLog.javaです。
import java.io.IOException;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
public class EmitLog {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv)
throws java.io.IOException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明exchange名字以及类型
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// getMessage的实现请见上篇博文
String message = getMessage(argv);
//指定exchange的名字
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
//...
}
ご覧のとおり、接続を確立した後、交換を宣言しました。存在しない交換にメッセージをプッシュすることは禁止されているため、この手順が必要です。
交換の原因となるキューがない場合、メッセージは失われますが問題ありません。コンシューマが待機していない場合、これらのメッセージは安全に破棄されます。
ReceiveLogs.javaのコードは次のとおりです。
import java.io.IOException;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.QueueingConsumer;
public class ReceiveLogs {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv)
throws java.io.IOException,
java.lang.InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明消息路由的名称和类型
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//声明一个随机消息队列
String queueName = channel.queueDeclare().getQueue();
//绑定消息队列和消息路由
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
//启动一个消费者
channel.basicConsume(queueName, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
}
}
}
- ファイルをコンパイルします。
javac -cp。:amqp-client-3.3.5.jar ReceiveLogs.java EmitLog.java
- ログをファイルに保存します。
java -cp。:amqp-client-3.3.5.jar ReceiveLogs> logs_from_rabbit.log
- 次に、ログファイルを確認します。
尾-10f logs_from_rabbit.log
- ログメッセージを画面に出力します。
java -cp。:amqp-client-3.3.5.jar ReceiveLogs
プロデューサーを開始します:
java -cp .: Amqp-client-3.3.5.jar EmitLog
ReceiveLogsを実行しているときに、rabbitmqctl list_bindingsコマンドを使用して、RabbitMQで交換を表示します。
rabbitmqctl list_bindings
Listing bindings ...
exchange amq.gen-1Zuyn_44c8IWsdJWrI42Og queue amq.gen-1Zuyn_44c8IWsdJWrI42Og []
exchange amq.gen-rSrGSPWLNTuq1dfXipPfAA queue amq.gen-rSrGSPWLNTuq1dfXipPfAA []
exchange task_queue queue task_queue []
logs exchange amq.gen-1Zuyn_44c8IWsdJWrI42Og queue []
logs exchange amq.gen-rSrGSPWLNTuq1dfXipPfAA queue []
要約:
1.取引所の名前とタイプをプロデューサとコンシューマチャネルで宣言します。
2.プロデューサーのチャネルで送信先の交換を指定します
3.コンシューマチャネルでランダムメッセージキューを宣言してキュー名を取得し、メッセージキューとメッセージルートをチャネルにバインドします。