記事ディレクトリ
1. まずは MQ について知る
1.1. 同期通信と非同期通信
マイクロサービス間の通信には、同期と非同期の 2 つの方法があります。
同期通信: 電話をかけるのと同じように、リアルタイムの応答が必要です。
非同期通信: 電子メールの送信と同様、すぐに返信する必要はありません。
どちらの方法にも一長一短があり、電話をかけるとすぐに応答が得られますが、同時に複数の人と通話することはできません。メール送信は複数人で同時にメールの送受信が可能ですが、返信が遅くなることがよくあります。
1.1.1. 同期通信
以前学習した Feign 呼び出しは同期メソッドであり、呼び出しはリアルタイムで結果を取得できますが、次のような問題があります。
要約:
同期呼び出しの利点:
- 時間制限があり、結果はすぐに得られます
同期呼び出しに関する問題:
- 高度な結合(開閉の原理に違反)
- パフォーマンスとスループットの低下 (順番に 1 つのリクエスト、合計時間はすべてのマイクロサービス リクエストの応答時間の合計と等しくなります)
- 追加のリソース消費が発生します (下流のビジネス応答を待っているため、何もできません)
- カスケード障害の問題があります (マイクロサービスがハングアップし、呼び出しチェーン内のすべてのマイクロサービスがここでスタックし、ますますドミノ効果が発生します (同期リクエストが成功しない場合、同期リクエストはここでスタックし、スタックしたリクエストはさらにスタックします)トラブル))
1.1.2. 非同期通信
最も一般的なのはイベント駆動型モデルです
非同期呼び出しにより、上記の問題を回避できます。
商品の購入を例に挙げると、ユーザーは支払い後、注文サービスを呼び出して注文ステータスの変更を完了し、物流サービスを呼び出して、対応する在庫を倉庫から割り当てて配送の準備をする必要があります。
イベント モードでは、支払いサービスはイベント発行者 (パブリッシャー) となり、支払いが完了したら、イベント内の注文 ID を使用して、成功した支払いイベント (イベント) を発行するだけで済みます。
注文サービスと物流サービスはイベント サブスクライバー (消費者) であり、支払いが成功したイベントをサブスクライブし、イベントを聞いた後、自分のビジネスを完了します。
イベント発行者と購読者の間の結合を取り除くために、両者は直接通信せず、仲介者 (ブローカー) を置きます。パブリッシャーは、誰がイベントをサブスクライブするかに関係なく、イベントを Broker にパブリッシュします。サブスクライバーはブローカーからのイベントをサブスクライブし、誰がメッセージを送信するかを気にしません。
ブローカーはデータ バスのようなものです。データを受信および送信するすべてのサービスはこのバスに送信されます。このバスはプロトコルのようなもので、サービス間の通信を標準化して制御可能にします。
支払いサービスが支払いを完了した後は、他の関連サービスを呼び出す必要はありません。
支払い成功イベントをブローカーに送信するだけで済みます。
他の関連マイクロサービスはブローカーからイベント通知を受け取るため、独自の処理を実行できます。現時点
では、新しい関連マイクロサービスを追加したいのですが、このイベントをサブスクライブさせるだけで、支払い関数コードを
変更する必要はありません。
決済完了後、ブローカーにイベントを送信すれば完了です。他のサービスの実行を待つ必要はなく、決済が成功したことをユーザーに直接通知することができます。つまり、すべてのマイクロサービスを実行できます
。前の呼び出し形式は実際にはシリアルです
他のマイクロサービスをまったく呼び出さないので
、どのマイクロサービスがハングしても影響はありません。
多くのユーザーが大量のリクエストを送信します
。
リクエストは最初にブローカーでここにキャッシュされます。
各マイクロサービスは
処理できる限り多くのリクエスト
を処理できます。能力 1 は処理できるだけ多くのリクエストを処理できます]
すべてのプレッシャーはブローカーによって負担されます
(洪水が来ているようで、ここにはそれをせき止めるブローカーと呼ばれるダムがある)
利点:
-
スループットの向上: サブスクライバの処理が完了するまで待つ必要がなく、応答が速くなります。
-
障害の分離: サービスは直接呼び出されず、連鎖的な障害の問題はありません。
-
呼び出し間にブロッキングがないため、無効なリソース占有が発生しません。
-
結合度が極めて低く、各サービスを柔軟に接続・交換可能
-
トラフィックのピーククリッピング: 発行されたイベントのトラフィックがどれほど変動しても、ブローカーによって受信され、サブスクライバーは独自の速度でイベントを処理できます。
欠点:
- 構造が複雑で、明確なプロセスラインがないため、管理が困難です。
- ブローカーの信頼性、セキュリティ、パフォーマンスに依存する必要があります (実際、すべてのプレッシャーはブローカーに伝達されるため、ブローカーに対する要件は特に高くなります)
幸いなことに、クラウド プラットフォーム上のオープン ソース ソフトウェアまたはブローカー ソフトウェアは非常に成熟しており、より一般的なのは今日学習する MQ テクノロジです。
実際、非同期通信には多くの利点がありますが、同期通信が廃止されるわけではありません。
ほとんどの場合、同時実行性がそれほど高くないため、依然として同期通信が使用されます。現時点では、適時性が第一です。同期通信 適時性が大幅に向上し、リアルタイムのフィードバックが得られます
フィードバックを必要としない一部のシナリオ (通知されて完了) では、分離、スループット、および同時シナリオを追求する必要があります (比較的まれです) , 次に非同期通信を使用するので、今度は同期に
なるはずです
通信と非同期通信の組み合わせ
1.2. 技術的な比較:
MQ、中国語はメッセージキュー(MessageQueue)で、文字通りメッセージを格納するためのキューです。つまり、イベント駆動型アーキテクチャのブローカーです。
ここでのニュースは実際にはイベント
MQ がブローカーです
より一般的な MQ 実装:
- アクティブMQ
- ラビットMQ
- ロケットMQ
- カフカ
いくつかの一般的な MQ の比較:
ラビットMQ | アクティブMQ | ロケットMQ | カフカ | |
---|---|---|---|---|
会社/コミュニティ | うさぎ | アパッチ | アリ | アパッチ |
開発言語 | アーラン | ジャワ | ジャワ | スカラ&Java |
プロトコルのサポート | AMQP、XMPP、SMTP、STOMP | OpenWire、STOMP、REST、XMPP、AMQP | カスタムプロトコル | カスタムプロトコル |
可用性 | 高い | 一般的に | 高い | 高い |
スタンドアロンのスループット | 一般的に | 違い | 高い | すごく高い |
メッセージの遅延 | マイクロ秒レベル | ミリ秒 | ミリ秒 | ミリ秒以内 |
メッセージの信頼性 | 高い | 一般的に | 高い | 一般的に |
上の表からわかるように、完璧な解決策はなく、適切な解決策のみが存在します。
可用性の追求: Kafka、RocketMQ、RabbitMQ
信頼性の追求:RabbitMQ、RocketMQ
スループットの追求:RocketMQ、Kafka
低メッセージ遅延の追求: RabbitMQ、Kafka
2. クイックスタート
2.1. RabbitMQ のインストール
リンク: https://pan.baidu.com/s/1Jq2I-kwj3PAUraW76VGLwA
抽出コード: ssah
ウサギ: ウサギ
RabbitMQ をインストールします。クラス前の資料を参照してください。
または、このブログを直接参照してください: RabbitMQ デプロイメント
MQ の基本構造:
RabbitMQ のいくつかの役割:
-
発売元:プロデューサー
-
消費者: 消費者
-
Exchange: スイッチ、メッセージ ルーティングを担当します
-
キュー: キュー、メッセージを保存する
-
virtualHost: 仮想ホスト。さまざまなテナントの交換、キュー、およびメッセージの分離を分離します。
-
まとめ
2.2. RabbitMQ メッセージモデル
RabbitMQ は、さまざまなメッセージ モデルに対応する 5 つの異なるデモ サンプルを公式に提供しています。
公式ドキュメントを確認する
https://www.rabbitmq.com/
https://www.rabbitmq.com/getstarted.html
2.3. デモプロジェクトのインポート
クラス前の資料には、デモ プロジェクト mq-demo が含まれています。
インポート後、次のような構造が表示されます。
次の 3 つの部分で構成されます。
- mq-demo: 親プロジェクト、プロジェクトの依存関係を管理する
- パブリッシャー: メッセージの送信者
- Consumer: メッセージのコンシューマ
2.4. 入門ケース
HelloWorld デモは
RabbitMQ 接続を完全に単独で書き込みます
シンプルキューモードのモデル図:
公式の HelloWorld は、最も基本的なメッセージ キュー モデルに基づいて実装されており、次の 3 つの役割のみが含まれています。
- パブリッシャー: メッセージ パブリッシャー、メッセージをキュー キューに送信します(独自のコードを作成します)
- queue: メッセージキュー、メッセージの受け入れとキャッシュを担当します(RabbitMQ 管理)
- Consumer: キューをサブスクライブし、キュー内のメッセージを処理します(独自のコードを作成します)。
2.4.1. パブリッシャーの実装
アイデア:
- 接続を確立する
- チャンネルの作成
- キューを宣言する
- メッセージを送ります
- 接続とチャネルを閉じる
コード:
IP、ユーザー名、パスワードに注意してください。自分で書かなければなりません
package cn.itcast.mq.helloworld;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class PublisherTest {
@Test
public void testSendMessage() throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.141.100");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("whuer");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.发送消息
String message = "hello, rabbitmq!";
channel.basicPublish("", queueName, null, message.getBytes());
System.out.println("发送消息成功:【" + message + "】");
// 5.关闭通道和连接
channel.close();
connection.close();
}
}
ブレークポイントを使用して実行し、コンソールを表示して完全に理解する
2.4.2.コンシューマ実装
コードのアイデア:
- 接続を確立する
- チャンネルの作成
- キューを宣言する
- ニュースを購読する
コード:
package cn.itcast.mq.helloworld;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConsumerTest {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.141.100");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("whhuer");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
// 5.处理消息
String message = new String(body);
System.out.println("接收到消息:【" + message + "】");
}
});
System.out.println("等待接收消息。。。。");
}
}
2.5. 概要
基本メッセージキューのメッセージ送信プロセス:
-
接続を作成する
-
チャンネルの作成
-
チャネルを使用してキューを宣言する
-
チャネルを使用してメッセージをキューに送信します
基本メッセージキューのメッセージ受信処理は次のとおりです。
-
接続を作成する
-
チャンネルの作成
-
チャネルを使用してキューを宣言する
-
消費者の消費行動を定義します。
-
チャネルを使用してコンシューマーをキューにバインドする
3.スプリングAMQP
Spring の RabbitMQ のカプセル化では、
カプセル化された API が直接使用され、これははるかに便利です。
SpringAMQP は、RabbitMQ パッケージに基づいたテンプレートのセットであり、SpringBoot を使用して自動アセンブリを実現するため、非常に使いやすくなっています。
SpringAmqpの公式アドレス: https://spring.io/projects/spring-amqp
Spring AMQP は 3 つの機能を提供します。
- キュー、エクスチェンジ、およびそれらのバインディングの自動宣言
- メッセージを非同期で受信するためのアノテーションベースのリスナーモード
- メッセージを送信するための RabbitTemplate ツールをカプセル化します。
3.1.Basic Queue シンプルキューモデル
SpringAMQP は公式ケース helloworld を実装します。
親プロジェクト mq-demo に依存関係を導入する
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.1.1. メッセージ送信
まず MQ アドレスを構成し、その構成をパブリッシャー サービスの application.yml に追加します。
spring:
rabbitmq:
host: 192.168.141.100 # rabbitMQ的ip地址 (linux端docker安装的)
port: 5672 # rabbitMQ使用地址
username: whuer # 用户名
password: 123321 # 密码
virtual-host: / # 虚拟主机 (隔离每个用户操作的 浏览器端可以设置)
次に、パブリッシャー サービスにテスト クラス SpringAmqpTest を記述し、RabbitTemplate を使用してメッセージを送信します。
cn.whu.mq.spring.SpringAmqpTest
これは低バージョン (2.3) のブート プログラムである可能性があります。SpringBoot 環境を構築するには @RunWith(SpringRunner.class) を追加する必要があります (Spring コンテナの起動に役立ちます)。
package cn.whu.mq.spring;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class) // 加这一行才会启动Spring容器 才会有spring环境
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage2SimpleQueue(){
String queueName = "simple.queue";
String message="I love you !";
rabbitTemplate.convertAndSend(queueName,message);
}
}
実行完了後、ブラウザ側で表示します
3.1.2. メッセージの受信
依存関係は親プロジェクトに導入され、すべてのサブプロジェクトに継承されるため、繰り返し導入する必要はありません
まず MQ アドレスを構成し、その構成をコンシューマー サービスの application.yml に追加します。
spring:
rabbitmq:
host: 192.168.141.100 # rabbitMQ的ip地址 (linux端docker安装的)
port: 5672 # rabbitMQ使用地址
username: whuer # 用户名
password: 123321 # 密码
virtual-host: / # 虚拟主机 (隔离每个用户操作的 浏览器端可以设置)
次に、コンシューマ サービスパッケージcn.whu.mq.listener
に新しいクラス SpringRabbitListener を作成します。コードは次のとおりです。
package cn.whu.mq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void ListenSimpleQueue(String msg){
//发送方发的是啥 这里参数就写啥 Spring都能处理
System.out.println("消费者接收到simple.queue的消息【"+msg+"】");
}
}
3.1.3. テスト
コンシューマー サービスを開始し、パブリッシャー サービスでテスト コードを実行し、MQ メッセージを送信します。
ConsumerApplication.main を実行し、ブート プログラム全体を実行すると、Spring が動作を開始します。
3.2.ワークキュー
2番目の公式訴訟
ワーク キュー (タスク キュー) とも呼ばれるタスク モデル。簡単に言えば、複数のコンシューマをキューにバインドし、キュー内のメッセージを一緒に消費できるようにすることです。
メッセージの処理に時間がかかる場合、メッセージの作成速度がメッセージの消費速度よりもはるかに速い場合があります。このままでは、メッセージがどんどん溜まって処理が間に合わなくなります。
このとき、ワークモデルを利用することで、複数のコンシューマーが共同してメッセージ処理を処理することができ、大幅に速度を向上させることができます。
その他は上記の BasicQueue とまったく同じです
が、キューの過密を防ぐために 1 つのコンシューマが複数になる点が異なります。
3.2.1. メッセージ送信
今回は、ループで送信して、大量のメッセージの蓄積をシミュレートします。
パブリッシャー サービスの SpringAmqpTest クラスにテスト メソッドを追加します。
// 生产者 每秒产生50条消息 (50*20=1000ms=1s)
@Test
public void testSendMessage2WorkQueue() throws InterruptedException {
String queueName = "simple.queue";
String message = "I love you__";
for (int i = 1; i <= 50; i++) {
rabbitTemplate.convertAndSend(queueName, message + i);
Thread.sleep(20);
}
}
3.2.2. メッセージの受信
同じキューにバインドする複数のコンシューマをシミュレートするために、コンシューマ サービスの SpringRabbitListener に 2 つの新しいメソッドを追加します。
@RabbitListener(queues = "simple.queue") //参数就是队列名称
public void ListenWorkQueue(String msg) throws InterruptedException {
//发送方发的是啥 这里参数就写啥 Spring都能处理
System.out.println("消费者1接收到simple.queue的消息【" + msg + "】" + LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue") // 参数就是队列名称
public void ListenWorkQueue2(String msg) throws InterruptedException {
//发送方发的是啥 这里参数就写啥 Spring都能处理
System.err.println("消费者2接收到simple.queue的消息【" + msg + "】" + LocalTime.now());
Thread.sleep(200);//队列2就处理速度慢10倍
}
コンシューマは 200 秒間スリープし、シミュレーション タスクには時間がかかることに注意してください。
3.2.3. テスト
ConsumerApplicationを起動した後、パブリッシャーサービスに先ほど記述した送信テストメソッドtestWorkQueueを実行します。
コンシューマ 1 が 25 個のメッセージをすぐに完了したことがわかります。Consumer 2 は、自身の 25 個のメッセージをゆっくりと処理しています。
(同時に、デフォルトではメッセージが 2 つのキューに均等に分散されていることもわかります。実行結果から、キュー 1 はすべての偶数番号のメッセージを処理し、キュー 2 はすべての奇数番号のメッセージを処理することがわかります。) : コンシューマが処理を完了したかどうかに関係なく、ポーリング配信)
つまり、コンシューマの処理能力を考慮せずに、メッセージは各コンシューマに均等に配信されます。これには明らかに問題があります。
3.2.4. もっと仕事ができる人
Spring には、この問題を解決できる簡単な構成があります。コンシューマー サービスの application.yml ファイルを変更し、構成を追加します。
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成返回ACK后,才能获取下一个消息
再起動後、再度実行すると、
2 つのコンシューマが 50 個のメッセージを処理するのに 5 秒かかったことがわかります (主にコンシューマ 2 が遅れているため)。今度は機能せず、
50 個のメッセージは 1 秒以上で処理されます。 , なぜなら、遅いコンシューマ 2 には 7 つのメッセージしか割り当てられていなかったためです。
この回線構成により、より多くの作業を実行できる人、つまりメッセージが合理的に配信されることが保証され、全体の効率が大幅に向上します。
3.2.5. 概要
作業モデルの使用:
- 複数のコンシューマがキューにバインドされ、同じメッセージは 1 つのコンシューマによってのみ処理されます。
- プリフェッチを設定してコンシューマによってプリフェッチされるメッセージの数を制御する
上記の 2 つのメソッドでは、メッセージはフェッチされるとすぐにキューから削除されます。つまり、各メッセージは 1 つのコンシューマによってのみ消費されます。
しかし、実際の状況では、注文の支払い完了メッセージを多くのマイクロサービス (倉庫、物流、SMS) に通知する必要があり、明らかに、取得して削除した後ではこの要件を満たすことができないため、次のパブリッシュ/サブスクライブ モデルが存在します
。
3.3. パブリッシュ/サブスクライブ
パブリッシュ/サブスクライブ モデルを次の図に示します。
ご覧のとおり、サブスクリプション モデルには追加の交換ロールがあり、プロセスが若干変更されています。
- パブリッシャー: プロデューサー、つまりメッセージを送信するプログラムですが、キューには送信されず、X (交換) に送信されます。
- 交換:スイッチ、図中のX。一方では、プロデューサーから送信されたメッセージを受信します。一方、メッセージを特定のキューに配信する、すべてのキューに配信する、またはメッセージを破棄するなど、メッセージを処理する方法を知っています。どのように動作するかは、Exchange の種類によって異なります。Exchangeには以下の3種類があります。
- ファンアウト: ブロードキャスト、交換にバインドされたすべてのキューにメッセージを渡します
- 直接: 方向性があり、指定されたルーティング キーに一致するキューにメッセージを配信します。
- トピック: ワイルドカード、ルーティング パターン (ルーティング パターン) に一致するキューにメッセージを送信します。
- コンシューマ: コンシューマは以前と同様にキューにサブスクライブしますが、変更はありません
- キュー: メッセージ キューは以前と同じで、メッセージを受信し、メッセージをバッファリングします。
Exchange (交換) はメッセージの転送のみを担当し、メッセージを保存する機能を持っていないため、Exchange にバインドされたキューがない場合、またはルーティング ルールを満たすキューがない場合、メッセージは失われます。
3.4.ファンアウト
ファンアウト、英語訳はfan out、MQでのブロードキャストと言った方が適切だと思います。
ブロードキャスト モードでは、メッセージ送信プロセスは次のようになります。
- 1) 複数のキューが存在する可能性があります
- 2) 各キューは Exchange (交換) にバインドされている必要があります
- 3) プロデューサーによって送信されたメッセージはスイッチにのみ送信でき、どのキューに送信するかをスイッチが決定し、プロデューサーは決定できません。
- 4) スイッチはすべてのバインドされたキューにメッセージを送信します。
- 5) キューをサブスクライブするコンシューマはメッセージを取得できます
私たちの計画は次のとおりです。
- スイッチ itcast.fanout を作成します。タイプは Fanout です。
- スイッチ itcast.fanout にバインドされた 2 つのキュー fanout.queue1 および fanout.queue2 を作成します。
3.4.1. キューとエクスチェンジの宣言
Spring は、さまざまな種類の交換をすべて表す Exchange インターフェイスを提供します。
Consumer でクラスを作成してキューとスイッチを宣言します。
package cn.whu.mq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutConfig {
//whu.fanout 交换机
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("whu.fanout");
}
//fanout.queue1 队列1
@Bean
public Queue fanoutQueue1() {
// 注意1: 是ringframework.amqp.core.Queue; 2. 方法名称是将来Bean的唯一id,别冲突了
return new Queue("fanout.queue1");
}
// 绑定队列1到交换机
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
//注意是springframework.amqp.core.Binding;
//注意参数类型名称不要写错了 会影响注入
return BindingBuilder
.bind(fanoutQueue1)
.to(fanoutExchange);
}
//fanout.queue2 队列2
@Bean
public Queue fanoutQueue2() {
return new Queue("fanout.queue2");
}
// 绑定队列2到交换机
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
return BindingBuilder
.bind(fanoutQueue2)
.to(fanoutExchange);
}
}
コンシューマ マイクロサービスを再起動し、ブラウザ側で表示します。
その後、スイッチを使用できます (キューの存在を知る必要はなく、RabbitMQ が代わりにキューを使用します)。
メッセージの送受信に変更はありません
3.4.2. メッセージ送信
パブリッシャー サービスの SpringAmqpTest クラスにテスト メソッドを追加します。
// 发布/订阅-Fanout广播方式: 发送给交换机 (交换机怎么发送给队列由RabbitMQ管,程序员不需要管)
@Test
public void testSendFanoutExchange() {
// 交换机名称
String exchangeName = "whu.fanout";
// 消息
String msg = "hello, every one!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "",msg);
// 第二个参数 routingKey先不用管了
}
3.4.3. メッセージの受信
コンシューマ サービスの SpringRabbitListener にコンシューマとして 2 つのメソッドを追加します。
@RabbitListener(queues = "fanout.queue1")
public void ListenFanoutQueue1(String msg){
System.out.println("消费者接收到fanout.queue1的消息【"+msg+"】");
}
@RabbitListener(queues = "fanout.queue2")
public void ListenFanoutQueue2(String msg){
System.out.println("消费者接收到fanout.queue2的消息【"+msg+"】");
}
ConsumerApplication を再起動し、SpringAmqpTest.testSendFanoutExchange メソッドを実行します。
実行結果は次のように表示されます:メッセージ送信者がスイッチにメッセージを送信した後、スイッチはすべてのバインドされたキューにメッセージをブロードキャストし、2 つのコンシューマーがメッセージを読み取ります。キューはこのメッセージを取得します。つまり、同じメッセージが複数の指定されたマイクロサービスに同時に送信され 、複数のコンシューマーがそれを一度に受信できます。
3.4.4. 概要
スイッチの役割は何ですか?
- 発行者から送信されたメッセージを受信する
- ルールに従って、メッセージをバインドされたキューにルーティングします。
- メッセージをキャッシュできない、ルーティングが失敗する、メッセージが失われる
- FanoutExchange はメッセージを各バインドされたキューにルーティングします
キュー、スイッチ、バインディング関係を宣言する Bean は何ですか?
- 列
- ファンアウト交換
- バインディング
3.5.直接
ファンアウト モードでは、メッセージはサブスクライブされたすべてのキューによって消費されます。ただし、シナリオによっては、異なるメッセージを異なるキューで消費する必要があります。このとき、Exchange は Direct タイプが使用されます。
- 各キューはキーにバインドされています
- 複数のキーを各キューにバインドする
直接モデルでは次のようになります。
- キューとスイッチ間のバインディングは任意に設定できませんが、
RoutingKey
(ルーティング キー)を指定する必要があります。 - メッセージの送信者は、Exchange にメッセージを送信するときにメッセージ ID も指定する必要があります
RoutingKey
。 - Exchange は、バインドされた各キューにメッセージを配信するのではなく、
Routing Key
メッセージに基づいて判断し、キューがRoutingkey
メッセージとRouting key
完全に一致する場合にのみ、メッセージを受信します。
スイッチがキューをバインドするとき、バインディング関係に 1 つのキー (または複数のキー) があります
。送信者がメッセージを送信するとき、キーもメッセージにバインドされます。
スイッチがメッセージをルーティングするとき、メッセージは次に従ってルーティングされます。キー (どのキューのバインド関係。まったく同じキーもあり、どのキューにルーティングするか、もちろん複数にルーティングできます) 専門家の言葉: DirectExchange は同じ RountingKey と BindingKey を持つキューにメッセージを送信します
ケースの要件は次のとおりです。
-
@RabbitListener を使用して Exchange、Queue、RoutingKey を宣言します
-
コンシューマ サービスで、direct.queue1 と direct.queue2 をそれぞれリッスンする 2 つのコンシューマ メソッドを作成します。
-
パブリッシャーでテスト メソッドを作成し、whu.direct にメッセージを送信します。
3.5.1. アノテーションベースのキューとスイッチの宣言
@Bean ベースでキューやスイッチを宣言するのは面倒ですが、Spring ではアノテーションベースの宣言も提供しています。
(アノテーションを直接使用して、スイッチ、キュー名、バインディング関係、およびバインディング キーを宣言できます) 大量の Bean を記述する必要はありません。コンシューマの SpringRabbitListener に 2 つのコンシューマを追加し、アノテーションに基づいてキューとスイッチを宣言します
。
// 声明队列: 同时声明 交换机 绑定交换机 绑定key (也即下面的队列需要用到交换机 但是不用额外配置类写交换机bean了)
// 注解写法: 直接写 bindings 里面就包含了上述所有信息
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "whu.direct",type = ExchangeTypes.DIRECT),
key = {
"blue","red"}
))
public void ListenDirectQueue1(String msg) {
System.out.println("消费者1接收到direct.queue1的消息【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "whu.direct",type = ExchangeTypes.DIRECT),
key = {
"yellow","red"}
))
public void ListenDirectQueue2(String msg) {
System.out.println("消费者2接收到direct.queue2的消息【" + msg + "】");
}
再起動するConsumerApplication
とブラウザ側で確認できます
3.5.2. メッセージ送信
パブリッシャー サービスの SpringAmqpTest クラスにテスト メソッドを追加します。
コードは上記のファンアウト ブロードキャストに似ていますが、唯一の違いは、ここで 2 番目のパラメーター routingKey を指定する必要があることです。
// 发布/订阅-Direct定向方式: 发送给交换机 (交换机怎么发送给队列RabbitMQ管,程序员不需要管)
@Test
public void testSendDirectExchange() {
// 交换机名称
String exchangeName = "whu.direct";
// 消息
String msg = "hello, blue!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "blue",msg);
// 第二个参数 routingKey 绑定的key可以指定了 定向发送的唯一标识
}
青、黄、赤をそれぞれ合計3回送信し、コンシューマ側での実行結果は以下のとおりです。
3.5.3. 概要
ダイレクト スイッチとファンアウト スイッチの違いについて説明してください。
- ファンアウト交換機にバインドされている各キューにメッセージをルーティングします。
- ダイレクト スイッチは、RoutingKey に従ってどのキューにルーティングするかを決定します。
- 複数のキューが同じ RoutingKey を持つ場合のファンアウト機能と同様
@RabbitListener アノテーションに基づいてキューとエクスチェンジを宣言するための一般的なアノテーションは何ですか?
- @列
- @交換
3.6.トピック
3.6.1. 説明
Topic
他のタイプExchange
と比較してDirect
、メッセージはRoutingKey
タイプに応じて異なるキューにルーティングできます。Topic
このタイプにより、バインド時にキューでワイルドカードを使用できるようにExchange
なったというだけですRouting key
。
Routingkey
通常、これは 1 つ以上の単語で構成され、複数の単語は「.」で区切られます。次に例を示します。item.insert
ワイルドカードのルール:
#
: 1 つ以上の単語と一致します
*
: 1 つの単語に完全に一致
例:
item.#
:item.spu.insert
または一致する可能性がありますitem.spu
item.*
: 一致のみ可能item.spu
グラフィック:
説明する:
- Queue1: はバインドされている
china.#
ため、china.
で始まるものはすべてrouting key
一致します。china.news と china.weather を含む - Queue2: はバインドされている
#.news
ため、で.news
終わるものはすべてrouting key
一致します。china.news と japan.news を含む
ケースの要件:
実装のアイデアは次のとおりです。
-
@RabbitListener を使用して Exchange、Queue、RoutingKey を宣言します
-
コンシューマ サービスで、topic.queue1 と topic.queue2 をそれぞれリッスンする 2 つのコンシューマ メソッドを作成します。
-
パブリッシャーでテスト メソッドを作成し、whu.topic にメッセージを送信します。
3.6.2. メッセージ送信
パブリッシャー サービスの SpringAmqpTest クラスにテスト メソッドを追加します。
// 发布/订阅-Topic通配符方式: 发送给交换机 (交换机怎么发送给队列RabbitMQ管,程序员不需要管)
@Test
public void testSendTopicExchange() {
// 交换机名称
String exchangeName = "whu.topic";
// 消息
String msg = "武汉大学120周年校庆,现场惊现雷布斯!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "china.news",msg);
// routingKey 写成.的形式 队列那边通配符匹配
}
3.6.3. メッセージの受信
コンシューマ サービスの SpringRabbitListener にメソッドを追加します。
// Topic 通配符路由模式
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "whu.topic",type = ExchangeTypes.TOPIC),
key = {
"china.#"} // 所有china.开头的都匹配 #表示任意多个单词
))
public void ListenTopicQueue1(String msg) {
System.out.println("消费者1接收到topic.queue1的消息【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "whu.topic",type = ExchangeTypes.TOPIC),
key = {
"#.news"} // 所有以.news结尾的都匹配 #表示任意多个单词
))
public void ListenTopicQueue2(String msg) {
System.out.println("消费者2接收到topic.queue2的消息【" + msg + "】");
}
ConsumerApplication を再起動し、SpringAmqpTest.testSendTopicExchange を実行して
実行結果を確認します。
実際、 china.new は china.# と #.news の両方に一致するため、両方が受信されます。
3.6.4. 概要
ダイレクト スイッチとトピック スイッチの違いについて説明してください。
- Topic スイッチが受信するメッセージ RoutingKey は、次のように
**.**
区切られた複数の単語である必要があります。 - Topic スイッチがキューにバインドされているときの bindingKey はワイルドカードを指定できます
#
: 0 個以上の単語を表します*
: 1単語を表します
3.7. メッセージコンバーター
前に述べたように、Spring は送信するメッセージをバイトにシリアル化して MQ に送信し、メッセージを受信するときにバイトを Java オブジェクトに逆シリアル化します。
ただし、デフォルトでは、Spring で使用されるシリアル化メソッドは JDK シリアル化です。ご存知のとおり、JDK のシリアル化には次の問題があります。
- データサイズが大きすぎます
- セキュリティホールがある
- 可読性が低い
試してみましょう。
まず、FanoutConfig に新しいキューを追加 (宣言) します。
// 测试序列化和反序列化
@Bean
public Queue objectQueue(){
return new Queue("object.queue");
}
次のテスト中にこの新しいキューに送信します
3.7.1. デフォルトコンバータのテスト
Map オブジェクトを送信するようにメッセージ送信のコードを変更します。
// 测试消息的系列化和反序列化 (发送复杂一点的对象才能测出来)
@Test
public void testSendObjectQueue(){
String queueName="object.queue";
// 准备消息
Map<String, Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",18);
// 发送消息
rabbitTemplate.convertAndSend(queueName, map);
}
消費者向けサービスを停止する
メッセージの送信後にコンソールを確認します。コンシューマーが書き込まれないため、メッセージは常にキューに残ります。
メッセージが文字化けしており、コンソールでメッセージを表示できません。
コンテンツ タイプを確認してください: content_type: application/x-java-serialized-object は、
デフォルトで jdk によってシリアル化されます。
3.7.2. JSONコンバーターの設定(送信側:シリアライザー)
明らかに、JDK シリアル化方法は適切ではありません。シリアル化と逆シリアル化に JSON メソッドを使用できるように、メッセージ本文を小さくして読みやすくしたいと考えています。
パブリッシャー サービスとコンシューマー サービスの両方に依存関係を導入します: (または直接の親プロジェクトに依存関係を導入します)
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
メッセージコンバータを構成します。
起動クラス PublisherApplication に Bean を追加するだけです。送信時に使用するシリアライザーがここで設定されているため、PublisherApplication 起動クラスに記述します
@Bean // 注意是amqp下的MessageConverter
public MessageConverter messageConverter() {
// 返回的就是SpringMVC常使用的消息转换器
return new Jackson2JsonMessageConverter();
}
次に、上記の SpringAmqpTest.testSendObjectQueue テスト メソッドを再度実行します。
確かに、シリアル化の結果は大幅に改善されました。
3.7.3. JSONコンバーターの設定(レシーバー:デシリアライザー)
送信された JSON メッセージ コンバーターが構成されているので、受信側を構成します。
パブリッシャー サービスとコンシューマー サービスの両方に依存関係を導入します: (または直接の親プロジェクトに依存関係を導入します)
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
受信側: ConsumerApplication スタートアップ クラスも同じジャクソン メッセージ コンバーターを構成し、同じ方法で逆シリアル化します。
@Bean // 注意是amqp包下的MessageConverter
public MessageConverter messageConverter() {
// 返回的就是SpringMVC常使用的消息转换器
return new Jackson2JsonMessageConverter();
}
次にコンシューマーを定義し、object.queue キューをリッスンしてメッセージを消費します。
@RabbitListener(queues = "object.queue") //监听object.queue队列
public void listenObjectQueue(Map<String, Object> msg) {
//参数和发送方发送的消息类型一模一样
System.out.println("收到消息:[" + msg + "]");
}
ConsumerApplication を再起動した後、SpringAmqpTest.testSendObjectQueue を実行してメッセージを送信します。
実行結果は次のとおりです。
- まとめ