目次
コンソールにアクセスします (http://IP アドレス: 15672)
MQ の基礎知識
MQ の基本概念
MQ の正式名称は Message Queue (メッセージ キュー) で、メッセージ送信時にメッセージを格納するコンテナです。主に分散システム間の通信に使用されます。
MQ の概要
-
MQ、メッセージキュー、メッセージを格納するためのミドルウェア
-
分散システム通信には、直接リモート呼び出しとサードパーティの助けを借りた間接通信の 2 つの方法があります。
-
送信者はプロデューサーと呼ばれ、受信者はコンシューマと呼ばれます
MQ の長所と短所
アドバンテージ
-
アプリケーションの分離
受注システムから直接発注を行う場合、在庫システムに問い合わせて該当商品の在庫が十分にあるかどうかを確認するなどの一連の作業が行われますが、この時に在庫システムがダウンするとシステム全体が停止してしまいます。麻痺した
-
MQ を使用してアプリケーションを分離し、耐障害性と保守性を向上させます。このとき注文システムはミドルウェアMQに直接接続されており、後続システムが麻痺しても注文システムは麻痺しない
-
非同期の高速化
注文にかかる時間: 20 + 300 + 300 + 300 = 920 ミリ秒。ユーザーが注文ボタンをクリックした後、注文の応答を得るまでに 920 ミリ秒待つ必要がありますが、これでは遅すぎます。
MQ を使用した後、ユーザーが注文ボタンをクリックした後、注文応答を受け取るまでに 25 ミリ秒待つだけで済みます (20 + 5 = 25 ミリ秒)。ユーザー エクスペリエンスとシステム スループット (単位時間あたりに処理されるリクエストの数) が向上します。
-
山を削り谷を埋める
瞬時に大量の同時リクエストに直面すると、システムが直接クラッシュする可能性があります
MQ の導入後は、大量のリクエストを最初に MQ にバッファリングし、その後 MQ を介してリクエストをゆっくりとシステムに送信することで、ピークの削減と谷の埋め込みの効果を実現できます。
不利益
-
システムの可用性が低下し、
システムに導入される外部依存性が増えるほど、システムの安定性は悪化します。MQ がダウンすると、ビジネスに影響が及びます。MQ の高可用性を確保するにはどうすればよいですか?
-
システムの複雑さの増加
MQ の追加により、システムの複雑さが大幅に増加しました。以前はシステム間で同期リモート呼び出しがありましたが、現在は MQ を介して非同期呼び出しが行われます。メッセージが繰り返し消費されないようにするにはどうすればよいでしょうか? メッセージ損失にどう対処するか? では、メッセージ配信の順序を確保するにはどうすればよいでしょうか?
-
一貫性の問題
システム A は業務の処理を終了し、MQ を介してシステム B、C、D の 3 つのシステムにメッセージ データを送信します。システム B、C で処理が成功すると、システム D での処理は失敗します。メッセージ データ処理の一貫性を確保するにはどうすればよいですか?
RabbitMQ の概要
2007 年に、AMQP 標準に基づいて Rabbit Technology Company によって開発された RabbitMQ 1.0 がリリースされました。RabbitMQ は Erlang 言語で開発されています。Erlang 言語はエリクソンによって設計され、高度に同時実行された分散システムの開発のために特別に開発された言語であり、電気通信分野で広く使用されています。
インフラストラクチャー
関連概念
-
Broker : メッセージを受信および配信するためのアプリケーション。RabbitMQ サーバーはメッセージ ブローカーです。
-
仮想ホスト: マルチテナンシーとセキュリティ要素向けに設計されており、AMQP の基本コンポーネントは、ネットワーク内の名前空間の概念と同様の仮想グループに分割されます。複数の異なるユーザーが同じ RabbitMQ サーバーが提供するサービスを使用する場合、複数の仮想ホストを分割し、各ユーザーが自分の仮想ホストなどにエクスチェンジ/キューを作成することができます。
-
接続: パブリッシャー/コンシューマーとブローカー間の TCP 接続
-
チャネル:RabbitMQにアクセスするたびにConnectionを確立すると、メッセージ量が多い場合にTCP Connectionを確立するオーバーヘッドが大きくなり効率が悪くなります。チャネルは、接続内で確立される論理接続です。アプリケーションがマルチスレッドをサポートしている場合、通常、各スレッドは通信用に個別のチャネルを作成します。AMQP メソッドには、クライアントとメッセージ ブローカーがチャネルを識別するのに役立つチャネル ID が含まれているため、チャネルは完全に一致します。から分離されました。チャネルは軽量の接続として、TCP 接続を確立するためのオペレーティング システムのオーバーヘッドを大幅に削減します。
-
Exchange : メッセージは、分散ルールに従ってブローカーの最初のストップに到着し、クエリ テーブル内のルーティング キーと一致し、メッセージをキューに分散します。一般的に使用されるタイプは、ダイレクト (ポイントツーポイント)、トピック (パブリッシュ/サブスクライブ)、およびファンアウト (マルチキャスト) です。
-
Queue : メッセージは最終的にここに送信され、コンシューマーが受け取るのを待ちます。
-
バインディング: Exchange とキュー間の仮想接続。バインディングにはルーティング キーを含めることができます。バインディング情報は、メッセージ配信の基礎となる交換内のクエリ テーブルに格納されます。
JMS
-
JMS は、Java Message Service (JavaMessage Service) アプリケーション プログラミング インターフェイスであり、Java プラットフォームのメッセージ指向ミドルウェア用の API です。
-
JMS は JavaEE 仕様の 1 つであり、JDBCに似ています。
-
多くのメッセージ ミドルウェアは JMS 仕様を実装しています (例: ActiveMQ)。RabbitMQ は JMS 実装パッケージを正式に提供していませんが、オープンソース コミュニティは
-
RabbitMQ のインストール
イメージをオンラインでプルする
docker pull Rabbitmq:3-management
MQ をインストールする
次のコマンドを実行して MQ コンテナを実行します。
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
コンソールにアクセスします ( http://ip アドレス:15672 )
動作モード
シンプル モード、ワーク キュー、パブリッシュ/サブスクライブ パブリッシュおよびサブスクリプション モード、ルーティング ルーティング モード、トピック テーマ モードの 5 つの作業モードがあります。
厳密に言えば、RPC は RabbitMq の動作モードに属しておらず、ここでは導入は行われません ( RabbitMQ: 使いやすく、柔軟なメッセージングとストリーミング — RabbitMQ)
シンプルモード(プロデューサーコンシューマーモード)
-
P: プロデューサー、つまりメッセージを送信するプログラム
-
C: コンシューマ: メッセージの受信者は常にメッセージの到着を待ちます。
-
queue: メッセージキュー、図の赤い部分。メールボックスと同様に、メッセージをキャッシュできます。プロデューサーはそこにメッセージを投稿し、コンシューマーはそこからメッセージを取得します。
プロデューサー
ConnectionFactory connectionFactory = new ConnectionFactory();
//rabbitmq服务的ip地址
connectionFactory.setHost("192.168.101.133");
connectionFactory.setPort(5672);
//连接的虚拟机
connectionFactory.setVirtualHost("/itcast");
//用户名
connectionFactory.setUsername("heima");
//密码
connectionFactory.setPassword("heima");
//建立连接
Connection connection = connectionFactory.newConnection();
//再从连接中创建channel建立真正的连接
Channel channel = connection.createChannel();
/**
队列声明
AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments)
1. queue: 队列名称
2.durable:是否持久化,如果不持久化,mq重启之后队列就不存在了
3. exclusive:是否独占。只能有一个消费者监听这队列
当Connection关闭时,是否删除队列
4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5.arguments: 参数。
*/
channel.queueDeclare("hello_world",true,false,false,null);
//发送的消息
String body="hello rabbitmq~~~~";
/**
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
1.exchange:交换机名称。简单模式下交换机会使用默认的""
2.routingKev: 路由名称,简单模式下就是队列名称
3.props: 配置信息
4.body: 发送消息数据
*/
channel.basicPublish("","hello_world",null,body.getBytes());
//释放资源
channel.close();
connection.close();
消費者
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.101.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/itcast");
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hello_world",true,false,false,null);
Consumer consumer =new DefaultConsumer(channel){
/**
回调方法,当收到消息后,会自动执行该方法
1.consumerTag:标识
2.envelope: 获取一些信息,交换机,路由key...2.
3.properties:配置信息
4.body: 数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("consumerTag:"+consumerTag);
System.out.println("bodys:"+new String(body));
}
};
/**
basicConsume(string queue, boolean autoAck, Consumer callback)参数:
1. queue:队列名称
2.autoAck: 是否自动确认
calLback: 回调对象,对获取到的消息进行处理
*/
channel.basicConsume("hello_world",true,consumer);
ワークキュー ワークキューモード
-
ワークキュー: スタータープログラムのシンプルモードと比較して、1 つ以上のコンシューマーがあり、複数のコンシューマーが同じキュー内のメッセージを一緒に消費します。同じメッセージを利用できるのは 1 人のコンシューマだけであるため、複数のコンシューマが競合関係にあります。
-
アプリケーション シナリオ: ワーク キューを使用すると、タスクが重すぎる場合やタスクの数が多い場合に、タスクの処理速度を向上させることができます。例: 複数の SMS サービスの展開では、正常に送信するには 1 つのノードのみが必要です
プロデューサー
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.101.133");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/itcast");
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("work_queues",true,false,false,null);
for (int i = 0; i < 10; i++) {
String body="hello rabbitmq~~~~"+i;
channel.basicPublish("","work_queues",null,body.getBytes());
}
//释放资源
channel.close();
connection.close();
コンシューマのコードはシンプルモードと同じであり、繰り返し表示されることはなく、同じメッセージがコンシューマによって消費されるのは 1 回だけであり、2 つのコンシューマが競合関係にあることがわかります。消費方式を採用しています
Pub/Sub サブスクリプション モデル
サブスクリプション モデルでは、追加の Exchange ロールがあり、プロセスが若干変更されます。つまり、プロデューサーはメッセージをスイッチに転送し、スイッチはメッセージ ルートを別のキューに分散し、コンシューマはメッセージ ルートを取得するために別のキューをリッスンします。メッセージ
-
交換:交換(X)。一方では、プロデューサーから送信されたメッセージを受信します。一方、メッセージを特定のキューに配信する、すべてのキューに配信する、またはメッセージを破棄するなど、メッセージを処理する方法を知っています。どのように動作するかは、Exchange の種類によって異なります。
Exchange には一般的に 3 つのタイプがあります。
➢ファンアウト: ブロードキャスト、交換にバインドされたすべてのキューにメッセージを配信します。
➢ Direct : 方向性があり、指定されたルーティング キーに一致するキューにメッセージを配信します。
➢トピック: ワイルドカード、ルーティング パターン (ルーティング パターン) に一致するキューにメッセージを送信します。
Exchange (交換) はメッセージの転送のみを担当し、メッセージを保存する機能を持っていないため、Exchange にバインドされたキューがない場合、またはルーティング ルールを満たすキューがない場合、メッセージは失われます。
プロデューサー
ConnectionFactory connectionFactory = new ConnectionFactory(); //rabbitmq服务的ip地址 connectionFactory.setHost("192.168.101.134"); connectionFactory.setPort(5672); //连接的虚拟机 connectionFactory.setVirtualHost("/itcast"); //用户名 connectionFactory.setUsername("heima"); //密码 connectionFactory.setPassword("heima"); //建立连接 Connection connection = connectionFactory.newConnection(); //再从连接中创建channel建立真正的连接 Channel channel = connection.createChannel(); /** Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException; exchange:交换机名称 type:交换机类型 DIRECT("direct"),:定向 FANOUT(“fanout"),:广播,发送消息到每一个与之绑定队列。 TOPIC(“topic"),通配符的方式 HEADERS(“headers");参数匹配 3.durable:是否持久化 4. autoDelete:是否自动删除 5.参数列表 */ //创建交换机 String exchangeName="test_fanout"; channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null); //创建队列 String queue1Name="test_fanout_queue1"; String queue2Name="test_fanout_queue2"; channel.queueDeclare(queue1Name,true,false,false,null); channel.queueDeclare(queue2Name,true,false,false,null); channel.queueBind(queue1Name,exchangeName,""); channel.queueBind(queue2Name,exchangeName,""); String body="日志信息,rabbitmq瘫痪了!!!"; channel.basicPublish(exchangeName,"",null,body.getBytes()); //释放资源 channel.close(); connection.close();
2 人のコンシューマーはリッスン キューを変更するだけで済みます
ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.101.134"); connectionFactory.setPort(5672); connectionFactory.setVirtualHost("/itcast"); connectionFactory.setUsername("heima"); connectionFactory.setPassword("heima"); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); Consumer consumer =new DefaultConsumer(channel){ /** 回调方法 */ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("bodys:"+new String(body)); System.out.println("将日志打印在控制台"); } }; String queue1Name="test_fanout_queue1"; channel.basicConsume(queue1Name,true,consumer);
サブスクリプション モードを見つけます。同じメッセージは 2 つのキューに分散され、異なるマイクロサービスが同じメッセージに対して異なるビジネス処理を実行できます。たとえば、図の同じメッセージが異なるコンシューマーによって取得された後の操作は異なりますの。
ルーティングルーティングモード
ルーティングモードでは、キューとスイッチ間のバインディングを任意にバインドすることはできませんが、RoutingKey を指定する必要があります。Exchange はメッセージの Routing Key に基づいて判断します。キューの RoutingKey がルーティングとまったく同じである場合のみです。メッセージのキーは、メッセージを受信します。コンシューマーは、メッセージを送信するときに、最初にメッセージのルーティング キーを指定する必要があります。ルーティング キーを指定して、このメッセージによってさまざまなマイクロサービスが指定された操作を実行できるかどうかを判断できます。
プロデューサー
ConnectionFactory connectionFactory = new ConnectionFactory();
//rabbitmq服务的ip地址
connectionFactory.setHost("192.168.101.134");
connectionFactory.setPort(5672);
//连接的虚拟机
connectionFactory.setVirtualHost("/itcast");
//用户名
connectionFactory.setUsername("heima");
//密码
connectionFactory.setPassword("heima");
//建立连接
Connection connection = connectionFactory.newConnection();
//再从连接中创建channel建立真正的连接
Channel channel = connection.createChannel();
//创建交换机
String exchangeName="test_direct";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
//创建队列
String queue1Name="test_direct_queue1";
String queue2Name="test_direct_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//队列绑定交换机,并指定Routing key
channel.queueBind(queue1Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"info");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
String body="日志信息,rabbitmq瘫痪了!!!";
channel.basicPublish(exchangeName,"info",null,body.getBytes());
//释放资源
channel.close();
connection.close();
消費者
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.101.134");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/itcast");
connectionFactory.setUsername("heima");
connectionFactory.setPassword("heima");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
Consumer consumer =new DefaultConsumer(channel){
/**
回调方法
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("bodys:"+new String(body));
System.out.println("将日志保存到数据库");
}
};
String queue2Name="test_direct_queue2";
channel.basicConsume(queue2Name,true,consumer);
トピックのワイルドカード パターン
-
Direct と比較して、Topic タイプは、RoutingKey に従ってメッセージを異なるキューにルーティングできます。トピック タイプ Exchange では、ルーティング キーをバインドするときにキューでワイルドカードを使用できるというだけです。
-
ルーティングキーは通常、「.」で区切られた 1 つ以上の単語で構成されます (例: item.insert)。
-
ワイルドカード ルール: # は 1 つ以上の単語に一致し、* は 1 つの単語に一致します。例: item.# は item.insert.abc または item.insert に一致し、item.* は item.insert にのみ一致します
プロデューサー
キュー 1 はキー照合にエラーと順序のワイルドカードを使用し、キュー 2 は汎用照合を実行できますが、キーが Goods.info の場合、キュー 1 は正常に照合できません。
ConnectionFactory connectionFactory = new ConnectionFactory();
//rabbitmq服务的ip地址
connectionFactory.setHost("192.168.101.134");
connectionFactory.setPort(5672);
//连接的虚拟机
connectionFactory.setVirtualHost("/itcast");
//用户名
connectionFactory.setUsername("heima");
//密码
connectionFactory.setPassword("heima");
//建立连接
Connection connection = connectionFactory.newConnection();
//再从连接中创建channel建立真正的连接
Channel channel = connection.createChannel();
//创建交换机
String exchangeName="test_topic";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
//创建队列
String queue1Name="test_topic_queue1";
String queue2Name="test_topic_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//绑定key时,采用*或#进行模糊化
channel.queueBind(queue1Name,exchangeName,"#.error");
channel.queueBind(queue1Name,exchangeName,"order.*");
channel.queueBind(queue2Name,exchangeName,"*.*");
String body="日志信息,rabbitmq瘫痪了!!!";
channel.basicPublish(exchangeName,"goods.info",null,body.getBytes());
//释放资源
channel.close();
connection.close();
Springboot は RabbitMQ を統合します
AMQP 依存関係をインポートする
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
yml設定を書き込む
spring:
rabbitmq:
host: 192.168.101.134 #rabbitMQ的ip地址
port: 5672 #端口
username: itcast
password: 123321
virtual-host: "/"
listener:
simple:
prefetch: 1
RabbitMQ 構成クラス
これは主に、キューとスイッチの宣言、キューとスイッチのバインドの完了、およびルーティング キーの指定に使用されます。
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME="boot_topic_exchange";
public static final String QUEUE_NAME="boot_QUEUE";
//交换机
@Bean("bootExchange")
public Exchange bootExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//队列
@Bean("bootQueue")
public Queue bootQueue(){
return QueueBuilder.durable(QUEUE_NAME).build();
}
//队列和交换机绑定
@Bean
public Binding bindQueueExchange(
@Qualifier("bootQueue") Queue queue,@Qualifier("bootExchange") Exchange exchange
){
return BindingBuilder.bind(queue).to(exchange).with("boot.*").noargs();
}
プロデューサー
RabbitTemplate を直接挿入して、メッセージを簡単かつ迅速に送信します
@SpringBootTest
public class PublishTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test(){
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.haha","helloword,mq");
}
}
消費者
@Component
public class RabbitMQListener {
@RabbitListener(queues = "boot_QUEUE")
public void ListenerQueue(Message message){
System.out.println(message);
}
}