【2023年】RabbitMQの基本入門とコード実装(1)

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();
        }
    }

おすすめ

転載: blog.csdn.net/weixin_52315708/article/details/131725030