1. MQ の関連概念
1.1、MQとは
MQ (メッセージ キュー) は文字通り、FIFO 先入れ先出し方式のキューですが、キューに格納される内容はメッセージだけであり、上流と下流のメッセージ送信のためのクロスプロセス通信メカニズムでもあります。インターネット アーキテクチャにおいて、MQ は、上流と下流の「論理的分離 + 物理的分離」を備えた非常に一般的なメッセージ通信サービスです。MQ を使用した後は、アップストリーム メッセージの送信は MQ のみに依存する必要があり、他のサービスに依存する必要はありません。
1.2. MQ を使用する理由
1. フローピークの除去
例えば、最大10,000件の注文を処理できる注文システムであれば、通常期であれば十分な処理能力があり、通常期であれば注文後1秒で結果を返すことができます。ただし、繁忙期に 20,000 件の注文が発生すると、OS では対応できず、10,000 件を超える注文数を制限し、ユーザーが注文できないようにすることしかできません。メッセージキューをバッファとして使用することで、この制限を解除し、1 秒以内に行われた注文を処理時間内に分散させることができます。このとき、一部のユーザーは注文後 10 秒以上経過しても正常な注文操作を受信できない場合があります。でも、注文できないよりはいいし、一度経験したほうがいいです。
2. アプリケーションのデカップリング
電子商取引アプリケーションを例にとると、アプリケーションには注文システム、在庫システム、物流システム、支払いシステムなどが含まれます。ユーザーが注文を作成した後、在庫システム、物流システム、決済システムが連携して呼び出された場合、いずれかのサブシステムに障害が発生すると、注文動作が異常になります。メッセージ キュー ベースのアプローチに変換すると、システム間呼び出しの問題が大幅に軽減され、たとえば、物流システムの障害による修復には数分かかります。この数分間の間に、物流システムによって処理されるメモリがメッセージ キューにキャッシュされ、ユーザーの注文操作は正常に完了できます。物流システムが復旧したら、注文情報の処理を続行するだけで、中間注文者は物流システムの障害を感じることがなくなり、システムの可用性が向上します。
3. 非同期処理
サービス間の一部の呼び出しは非同期です。たとえば、A が B を呼び出し、B の実行には長い時間がかかりますが、A は B がいつ完了するかを知る必要があります。以前は、一般に 2 つの方法がありました。A は B のクエリ API を呼び出します。一定期間後にクエリを実行します。または、A がコールバック API を提供し、B が実行を完了した後、その API を呼び出して A にサービスを通知します。これらの方法は両方ともあまり洗練されていません。メッセージ バスを使用すると、この問題は簡単に解決できます。A が B サービスを呼び出した後、B が完了したというメッセージを監視するだけで済みます。B が処理を完了すると、B は次のサービスにメッセージを送信します。 MQ は、このメッセージをサービス A に転送します。この方法では、サービス A はループ内で B のクエリ API を呼び出す必要がなく、コールバック API を提供する必要もありません。同様に、サービス B はこれらの操作を実行する必要はありません。サービス A は、非同期処理が成功したことを示すメッセージをタイムリーに取得することもできます。
一般的な MQ 製品
ActiveMQ: JMS ベース RabbitMQ: AMQP プロトコルに基づいており、アーラン言語で開発されており、安定性が高い
RocketMQ: JMS に基づいており、Alibaba 製品であり、現在は Apache Foundation に引き継がれています
Kafka: 分散メッセージング システム、高スループット
RabbitMQ クイック スタート
RabbitMQ Erlang 言語で開発され、AMQP (Advanced Message Queue Advanced Message Queuing Protocol) プロトコルに基づいて実装されたアプリケーション間の通信方式であり、分散システム開発で広く使用されているメッセージ キューです。RabbitMQ公式アドレス:http://www.rabbitmq.com
ダウンロードとインストール
RabbitMQ は Erlang 言語で開発されており、RabbitMQ のバージョンに対応した Erlang 言語環境をインストールする必要がありますが、詳細は説明しませんので、チュートリアルを自分で検索してください。RabbitMQ公式サイトダウンロードアドレス:http://www.rabbitmq.com/download.html
1.3. RabbitMQ の動作原理
次の図は、RabbitMQ の基本構造です:
1. メッセージ
: メッセージは特定のものではなく、メッセージ ヘッダーとメッセージ本文で構成されます。メッセージ本文は不透明で、メッセージ ヘッダーは、
routing-key (ルーティング キー)、priority (他のメッセージに対する優先度)、delivery-mode (メッセージが永続的なストレージを必要とする可能性があることを示す) などの一連のオプションの属性で構成されます。等
2.
パブリッシャ メッセージのプロデューサは、メッセージを交換にパブリッシュするクライアント アプリケーションでもあります。
3. Exchange
エクスチェンジャー。プロデューサーによって送信されたメッセージを受信し、これらのメッセージをサーバー内のキューにルーティングするために使用されます。4
バインディング
バインディング。メッセージ キューとエクスチェンジャー間の関連付けに使用されます。バインディングは、ルーティング キーに基づいてエクスチェンジャとメッセージ キューを接続するルーティング ルールであるため、
エクスチェンジャはバインディングで構成されるルーティング テーブルとして理解できます。
5. キュー
メッセージ キュー。コンシューマーに送信されるまでメッセージを保存するために使用されます。これはメッセージのコンテナーであり、メッセージの宛先です。メッセージは 1 つ以上のキューに入れることができます。
メッセージはキュー内にあり、コンシューマーがキューに接続してメッセージを取り出すのを待っています。
同じキューを同時にリッスンしている複数のコンシューマーがいる場合、それらはポーリングを使用してキュー内のメッセージを消費します。誰かが途中で終了すると、残りのコンシューマーがキュー内のメッセージを消費します。
接続ファクトリーの作成
public static ConnectionFactory getFactory(){
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("root");
//tcp连接端口
factory.setPort(5672);
factory.setPassword("root");
factory.setHost("192.168.204.129");
return factory;
}
-
接続の作成 (スイッチを使用せずに直接送信)
プロデューサー:
private static void getConnectionFactory(){ ConnectionFactory factory = FactoryUtil.getFactory(); try { // 创建连接 Connection conn = factory.newConnection(); // 获得信道 Channel channel = conn.createChannel(); // 声明队列 channel.queueDeclare("myQueue",true,false,false,null); String message = "hello,rabbitmq....."+new Date(); // 发送消息到指定队列(交换机,路由键(队列名),属性,消息内容字节流) channel.basicPublish("","myQueue",null,message.getBytes()); System.out.println("消息已经发送"+new Date()); channel.close(); conn.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } }
消費者
public static void getReciver(){ // 连接工厂对象 ConnectionFactory factory = FactoryUtil.getFactory(); //创建连接 try { Connection conn = factory.newConnection(); // 通道 Channel channel = conn.createChannel(); // 声明队列 channel.queueDeclare("myQueue",true,false,false,null); // 消费消息--队列名,自动确认,消费者 channel.basicConsume("myQueue", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("接受到消息:"+new String(body,"utf-8")); } }); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } }
Exchange - 交換を使用して接続を作成します
この定数を使用して送信モードを宣言することもできます。BuiltinExchangeType
-
ダイレクトモード
- ** ルーティングキーを処理します。**スイッチにキューをバインドする必要があります。**メッセージが特定のルーティング キーと正確に一致する必要があります。**これは完全一致です。キューがスイッチにバインドされており、ルーティング キー「dog」が必要な場合、「dog」とマークされたメッセージのみが転送されます。Dog.puppy も Dog.guard も転送されず、dog のみが転送されます。転送されました。
プロデューサー:
/** *@Description//TODO Direct模型 *@Date2022/8/8 16:29 **/ private static void getSender(){ ConnectionFactory factory = FactoryUtil.getFactory(); try { Connection conn = factory.newConnection(); Channel channel = conn.createChannel(); //声明交换机--交换机名称,类型,持久化 channel.exchangeDeclare("directExchange","direct",true); // 发送消息 String message = "hello...direct exchagne..."+new Date(); // 交换机,指定的路由键,属性,消息内容字节流 channel.basicPublish("directExchange",**"green"**,null,message.getBytes()); System.out.println("消息发送成功...."); channel.close(); conn.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } }
消費者:
public static void getReciver(){ // 连接工厂对象 ConnectionFactory factory = FactoryUtil.getFactory(); //创建连接 try { Connection conn = factory.newConnection(); // 通道 Channel channel = conn.createChannel(); // 声明队列 channel.queueDeclare("direqueue1",true,false,false,null); // 声明交换机(交换机名,交换类型(需要全小写),是否声明为持久层) channel.exchangeDeclare("directExchange",***"direct"***,true); // 绑定交接机(队列,交换机,用于绑定的路由键(子会接收路由键相同的消息)) channel.queueBind("direqueue1","directExchange",**"green"**); // 消费消息--队列名,自动确认,消费者 channel.basicConsume("direqueue1", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("DirectReciver1接收到消息:"+new String(body,"utf-8")); } }); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } }
-
ブロードキャストモードファンアウト
-
ルーティングキーは処理されません。キューをスイッチにバインドするだけです。スイッチに送信されたメッセージは、そのスイッチにバインドされているすべてのキューに転送されます。サブネット ブロードキャストと同様に、サブネット内の各ホストはメッセージのコピーを取得します。ファンアウトスイッチはメッセージを最速で転送します
プロデューサー:
/** *@Description//TODO Fanout模型-会发送给交换机中的所以队列 *@Date2022/8/8 16:29 **/ private static void getSender(){ ConnectionFactory factory = FactoryUtil.getFactory(); try { Connection conn = factory.newConnection(); Channel channel = conn.createChannel(); //声明交换机--交换机名称,类型,持久化 channel.exchangeDeclare("fanoutExchange","fanout",true); // 发送消息 String message = "hello...fanout exchagne..."+new Date(); // 交换机,不用指定路由键,属性,消息内容字节流 channel.basicPublish("fanoutExchange","",null,message.getBytes()); System.out.println("fanoutExchange消息发送成功...."); channel.close(); conn.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } }
消費者:
public static void getReciver(){ // 连接工厂对象 ConnectionFactory factory = FactoryUtil.getFactory(); //创建连接 try { Connection conn = factory.newConnection(); // 通道 Channel channel = conn.createChannel(); // 声明队列 channel.queueDeclare("fanout1",true,false,false,null); // 声明交换机 channel.exchangeDeclare("fanoutExchange","fanout",true); // 绑定交接机(队列,交换机,用于绑定的路由键) channel.queueBind("fanout1","fanoutExchange","green"); // 消费消息--队列名,自动确认,消费者 channel.basicConsume("fanout1", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("FanoutReciver1接收到消息:"+new String(body,"utf-8")); } }); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } }
-
-
トピックモード
-
プロデューサはデータをスイッチに送信し、特定のルーティング キーを設定します。コンシューマは、キューのワイルドカードに従って条件を満たすキュー内のメッセージを受信します。
- : 1単語を表します
#: 0以上を表します
プロデューサー:
/** *@Description//TODO Topic模型- *@Date2022/8/8 16:29 **/ private static void getSender(){ ConnectionFactory factory = FactoryUtil.getFactory(); try { Connection conn = factory.newConnection(); Channel channel = conn.createChannel(); //声明交换机--交换机名称,类型,持久化 channel.exchangeDeclare("topicExchange", BuiltinExchangeType.TOPIC,true); // 发送消息 String message = "hello...fanout exchagne..."+new Date(); // 交换机,不用指定路由键,属性,消息内容字节流 channel.basicPublish("topicExchange","aa.bb.zez",null,message.getBytes()); System.out.println("fanoutExchange消息发送成功...."); channel.close(); conn.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } }
消費者:
public static void getReciver(){ // 连接工厂对象 ConnectionFactory factory = FactoryUtil.getFactory(); //创建连接 try { Connection conn = factory.newConnection(); // 通道 Channel channel = conn.createChannel(); // 声明队列 channel.queueDeclare("topic3",true,false,false,null); // 声明交换机 channel.exchangeDeclare("topicExchange", BuiltinExchangeType.TOPIC,true); // 绑定交接机(队列,交换机,用于绑定的路由键) channel.queueBind("topic3","topicExchange","*.bb.#"); // 消费消息--队列名,自动确认,消费者 channel.basicConsume("topic3", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("topicExchange3接收到消息:"+new String(body,"utf-8")); } }); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); }
-
取引:
プロデューサがメッセージを送信するとき、トランザクションを使用して送信のアトミック性を保証できます。
- トランザクションを開始します。
channel.txSelect();
- トランザクションを送信します:
channel.txCommit();
リスニング確認を有効にする:
同期モード:
- モニタリング確認モードをオンにします。
channel.confirmSelect();
- 監視確認の実行:
channel.waitForConfirmsOrDie();
非同期モード:
このモードでは非同期で監視を行い、どれが成功したか失敗したかを判断し、失敗した場合は再実行して送信します。
/**
*@Description//TODO currentTimeMillis启用发布者确认使用异步执行,该
*@Date2022/8/8 16:29
**/
private static void getSender(){
ConnectionFactory factory = FactoryUtil.getFactory();
try {
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
//声明交换机--交换机名称,类型,持久化
channel.exchangeDeclare("transExchange", BuiltinExchangeType.DIRECT,true);
// 启用发布者确认模式
channel.confirmSelect();
long l = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
// 发送消息
String message1 = "hello...direct exchagne1..."+i;
// 交换机,指定的路由键,属性,消息内容字节流
channel.basicPublish("transExchange","trans",null,message1.getBytes());
}
// 执行监听确认
channel.addConfirmListener(new ConfirmListener() {
// 确认消息
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("确认消息****:"+deliveryTag+",状态:"+multiple);
}
// 未确认
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.err.println("未消息####:"+deliveryTag+",状态:"+multiple);
}
});
long j = System.currentTimeMillis();
System.out.println("消息使用时间---"+(j-l));
// channel.close();
// conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}