RabbitMQ簡介和交換機入門使用

一、RabbitMQ簡介

1、什麼是MQ

      消息隊列(Message Queue,簡稱MQ),從字面意思上看,本質是個隊列,FIFO先入先出,只不過隊列中存放的內容是message而已。其主要用途:不同進程Process/線程Thread之間通信。

1)爲什麼會產生消息隊列?有幾個原因:

      不同進程(process)之間傳遞消息時,兩個進程之間耦合程度過高,改動一個進程,引發必須修改另一個進程,爲了隔離這兩個進程,在兩進程間抽離出一層(一個模塊),所有兩進程之間傳遞的消息,都必須通過消息隊列來傳遞,單獨修改某一個進程,不會影響另一個;

      不同進程(process)之間傳遞消息時,爲了實現標準化,將消息的格式規範化了,並且,某一個進程接受的消息太多,一下子無法處理完,並且也有先後順序,必須對收到的消息進行排隊,因此誕生了事實上的消息隊列;

     MQ框架非常之多,比較流行的有RabbitMQ、ActiveMQ、ZeroMQ、kafka,以及阿里開源的RocketMQ等。本文介紹RabbitMQ。 瞭解更多消息中間件推薦文章:https://www.cnblogs.com/huojg-21442/p/7601380.html

2、什麼是RabbitMQ

     RabbitMQ是實現了高級消息隊列協議(AMQP)的開源消息代理軟件(亦稱面向消息的中間件)。RabbitMQ服務器是用Erlang語言編寫的,而集羣和故障轉移是構建在開放電信平臺框架上的。所有主要的編程語言均有與代理接口通訊的客戶端庫。 -- 百度百科

      簡單來說,RabbitMQ就是一個開源的消息代理和隊列服務器,並且是基於AMQP協議的,是AMQP的一個實現,支持多種客戶端。

1)爲什麼使用RabbitMQ?

開源的消息中間件

可以跨平臺,跨語言。數據的生成和消費可以是不同的語言。

提供可靠性消息投遞模式,返回模式,在易用性、擴展性、高可用性等方面表現不俗。

與springAMQP完美的整合 集羣模式豐富,表達式配置,HA模式,鏡像隊列模型。

2)RabbitMQ一些基礎概念

Server:又稱Broker,接受客戶端的連接,實現AMQP實體服務。簡單來說就是消息隊列服務器實體。

Connection:連接,應用程序跟Broker的網絡連接。

Channel:消息通道/網絡信道,幾乎所有的操作都是在channel中進行。數據的流轉都要在channel上進行。channel是進行消息讀寫的通道。在客戶端的每個連接裏,可以建立多個channel,每個channel代表一個會話任務。

Message:消息,服務器與應用程序之間傳送的數據,由Properties和body組成。Properties可以對消息進行修飾,比如消息的優先級,延遲等高級特性。body則就是消息體的內容。

Virtual host:虛擬主機/虛擬地址,用於進行邏輯隔離,最上層的消息路由。一個broker裏可以開設多個vhost,用作不同用戶的權限分離。一個虛擬地址裏面可以有多個交換機 exchange和消息隊列message queue。

Exchange:消息交換機,它指定消息按什麼規則,路由到哪個隊列中。接收消息,根據路由機轉發消息到綁定的隊列。

Binding:綁定,交換機和隊列之間的虛擬鏈接,綁定中可以包含routing key。它的作用就是把exchange和queue按照路由規則綁定起來。

Routing Key:路由關鍵字,可以用它來確定如何路由一個特定消息,exchange根據這個關鍵字進行消息投遞。

Queue:消息隊列載體,保存消息並將它們轉發給消費者,每個消息都會被投入到一個或多個隊列。

Producer:消息生產者,就是投遞消息的程序。

Consumer:消息消費者,就是接受消息的程序。

3)消息隊列的使用過程大概如下:

      生產者把消息交給服務器,服務器裏面有虛擬主機,主機裏面有AMQP的核心exchange交換機。生產者需要有服務器的ip和端口號,找到服務器,服務器需要把消息投遞到哪個虛擬主機上。

      接下來,虛擬主機把消息交給交換機,交換機接收到消息後會根據路由規則找到指定的消息隊列 ,所以生產者生產消息時需要指定消息的routing key。交換機會把消息交給消息隊列。到此,消息的生產者的任務就做完了。

      消費者可以監聽消息隊列,由於交換機和消息隊列會進行綁定,消費者會監聽消息隊列message queue,RabbitMQ會把消息隊列交給消費者。

    

3、開發語言:Erlang – 面向併發的編程語言。

     

4、什麼是AMQP協議

      AMQP,即Advanced Message Queuing Protocol,高級消息隊列協議,

      AMQP是具有現代特徵的二進制協議。是一個提供統一消息服務的的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計,是一個規範。AMQP的主要特徵是面向消息、隊列、路由(包括點對點和發佈/訂閱)、可靠性、安全。

      消息中間件主要用於組件之間的解耦,消息的發送者無需知道消息使用者的存在,反之亦然。

      在 AMQP 模型中,消息的 producer 將 Message 發送給 Exchange,Exchange 負責交換 / 路由,將消息正確地轉發給相應的 Queue。消息的 Consumer 從 Queue 中讀取消息。

     

 

二、Java入門使用

    QueueingConsumer在Rabbitmq客戶端4.x版本就被標記爲@Deprecated:

    參考文章:爲什麼 QueueingConsumer 會被 Deprecated ?

    RabbitMQ Java客戶端使用 com.rabbitmq.client 作爲其頂級軟件包。關鍵的類和接口是:

    

com.rabbitmq.client.Connection接口:

      

    com.rabbitmq.client.Channel接口挺重要,包含了很多消息讀寫的重載方法,具體看API

    對於類和接口中的方法參考官方API:RabbitMQ Java Client 5.7.3 API

 1、使用默認的交換機

     創建一個 maven項目,引入依賴 :

        <!--rabbitmq-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

   

1)消息生產者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) throws Exception {
        //1.創建工廠類
        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(5672);
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        //默認情況下爲“ guest” /“ guest”,僅限本地主機連接
        factory.setUsername("guest");
        factory.setPassword("guest");

        //2.通過工廠創建connection
        Connection connection = factory.newConnection();
        //3.創建channel對象
        Channel channel = connection.createChannel();

        //4.發佈消息
        String routingKey = "test1";
        String msg="hello rabbitmq consumer message";
        for (int i = 0; i < 2; i++) {
            /**
             參數:
                 exchange -將消息發佈到的交換機, 若爲空字符串時,使用默認的交換機
                 routingKey -路由鍵
                 mandatory -如果要設置“強制性”標誌,則爲true
                 props -消息的其他屬性-路由標頭等
                 body -消息正文
             */
            channel.basicPublish("", routingKey, false, null, msg.getBytes());
        }
        //5.釋放資源
        channel.close();
        connection.close();
    }

}

2)消息消費者

import com.rabbitmq.client.*;
import java.io.IOException;

public class Consumer {
    public static void main(String[] args) throws Exception {
        //1.創建工廠類
        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(5672);
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        //默認情況下爲“ guest” /“ guest”,僅限本地主機連接
        factory.setUsername("guest");
        factory.setPassword("guest");

        //2.通過工廠創建connection
        Connection connection = factory.newConnection();
        //3.創建channel對象
        Channel channel = connection.createChannel();

        //4. 創建消息隊列
        String queueName = "test1";
        channel.queueDeclare(queueName, true, false, false, null);


        //5.通過channel把消費者和消息隊列進行關聯,獲取消息進行處理
        /**
         參數:
             queue -隊列名稱
             autoAck-如果服務器應考慮一旦傳遞已確認的消息,則爲true;如果服務器應該期望顯式確認,則返回false
             callback -消費者對象的接口
         */
        boolean autoAck = false;
        channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) {
            /**
             參數:
                 consumerTag-與消費者相關聯的消費者標籤
                 envelope -消息的打包數據
                 properties -消息的內容頭數據
                 body -消息正文(客戶端特定的不透明字節數組)
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, 
                                       byte[] body) throws IOException {
                System.out.println("------------consumer message-----------");
                System.out.println("sonsumerTag:" + consumerTag);
                System.out.println("envelope:" + envelope);
                System.out.println("properties:" + properties);
                System.out.println("msg:" + new String(body));
            }
        });
    }
}

三、exchange交換機機制

1、什麼是交換機

      rabbitmq的message model實際上消息不直接發送到queue中,中間有一個exchange是做消息分發,producer甚至不知道消息發送到那個隊列中去。因此,當exchange收到message時,必須準確知道該如何分發。是append到一定規則的queue,還是append到多個queue中,還是被丟棄?這些規則都是通過exchagne的4種type去定義的。

      exchange是一個消息的agent,每一個虛擬的host中都有定義。它的職責是把message路由到不同的queue中。

2、交換器分類

     RabbitMQ的Exchange(交換器)分爲四類:direct(默認)、headers、fanout、topic

     其中headers交換器允許你匹配AMQP消息的header而非路由鍵,除此之外headers交換器和direct交換器完全一致,但性能卻很差,幾乎用不到,忽略。

3、Direct Exchange交換機

      直連型交換機,也非常的簡單,所有發送到Direct Exchange交換機的消息被轉發到 RouteKey中指定的Queue,消息攜帶的路由鍵與隊列名要完全匹配。

      

1)消息生產者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) throws Exception {
        //1.創建工廠類
        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(5672);
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        //默認情況下爲“ guest” /“ guest”,僅限本地主機連接
        factory.setUsername("guest");
        factory.setPassword("guest");

        //2.通過工廠創建connection
        Connection connection = factory.newConnection();
        //3.創建channel對象
        Channel channel = connection.createChannel();

        //4.發佈消息
        String exchangeName="test_direct_exchange";
        String routingKey = "test.direct";
        String msg="hello rabbitmq consumer message - test_direct";
        for (int i = 0; i < 2; i++) {
            /**
             參數:
                 exchange -將消息發佈到的交換機, 若爲空字符串時,使用默認的交換機
                 routingKey -路由鍵
                 mandatory -如果要設置“強制性”標誌,則爲true
                 props -消息的其他屬性-路由標頭等
                 body -消息正文
             */
            channel.basicPublish(exchangeName, routingKey, false, null, msg.getBytes());
        }
        //5.釋放資源
        channel.close();
        connection.close();
    }

}

2)消息消費者

import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer {
    public static void main(String[] args) throws Exception {
        //1.創建工廠類
        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(5672);
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        //默認情況下爲“ guest” /“ guest”,僅限本地主機連接
        factory.setUsername("guest");
        factory.setPassword("guest");

        //2.通過工廠創建connection
        Connection connection = factory.newConnection();
        //3.創建channel對象
        Channel channel = connection.createChannel();


        //4. 創建消息隊列和direct交換機,並通過channel讓交換機跟消息隊列進行綁定
        String queueName = "test_queue";
        String exchangeName="test_direct_exchange";
        String exchangeType="direct";
        String routingKey="test.direct";
        /**
         參數:
             queue -隊列名稱
             durable -如果我們聲明一個持久隊列,則爲true(該隊列將在服務器重啓後保留下來)
             exclusive -如果我們聲明一個排他隊列,則爲true(僅限此連接)
             autoDelete -如果我們聲明一個自動刪除隊列,則爲true(服務器將在不再使用它時將其刪除)
             arguments -隊列的其他屬性(構造參數)
         */
        channel.queueDeclare(queueName, true, false, false, null);
        /**
         參數:
             exchange -交易所名稱
             type -交易所類型
             durable -如果我們聲明持久交換,則爲true(該交換將在服務器重啓後保留下來)
             autoDelete -如果服務器在不再使用交換機時應刪除該交換機,則爲true
             arguments -用於交換的其他屬性(構造參數)
         */
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
        /**
         參數:
             queue -隊列名稱
             exchange -交易所名稱
             routingKey -用於綁定的路由鍵
         */
        channel.queueBind(queueName, exchangeName, routingKey);

        //5.通過channel把消費者和消息隊列進行關聯,獲取消息進行處理
        /**
         參數:
             queue -隊列名稱
             autoAck-如果服務器應考慮一旦傳遞已確認的消息,則爲true;如果服務器應該期望顯式確認,則返回false
             callback -消費者對象的接口
         */
        boolean autoAck = true;
        channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) {
            /**
             參數:
                 consumerTag-與消費者相關聯的消費者標籤
                 envelope -消息的打包數據
                 properties -消息的內容頭數據
                 body -消息正文(客戶端特定的不透明字節數組)
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                System.out.println("------------consumer message-----------");
                System.out.println("sonsumerTag:" + consumerTag);
                System.out.println("envelope:" + envelope);
                System.out.println("properties:" + properties);
                System.out.println("msg:" + new String(body));
            }
        });
    }
}

     

4、Topic Exchange交換機

      主題交換機,這個交換機其實跟直連交換機流程差不多,但是它的特點就是在它的路由鍵和綁定鍵之間是有規則的。消息攜帶的路由鍵與隊列名屬於模糊匹配。

       

簡單地介紹下規則:

    *  (星號) 用來表示一個單詞 (必須出現的)

    #  (井號) 用來表示任意數量(零個或多個)單詞

1)消息生產者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) throws Exception {
        //1.創建工廠類
        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(5672);
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        //默認情況下爲“ guest” /“ guest”,僅限本地主機連接
        factory.setUsername("guest");
        factory.setPassword("guest");

        //2.通過工廠創建connection
        Connection connection = factory.newConnection();
        //3.創建channel對象
        Channel channel = connection.createChannel();

        //4.發佈消息
        String exchangeName="test_topic_exchange";
        String routingKey = "test.topic";
        String msg="hello rabbitmq consumer message - test_topic_exchange";
        for (int i = 0; i < 2; i++) {
            /**
             參數:
                 exchange -將消息發佈到的交換機, 若爲空字符串時,使用默認的交換機
                 routingKey -路由鍵
                 mandatory -如果要設置“強制性”標誌,則爲true
                 props -消息的其他屬性-路由標頭等
                 body -消息正文
             */
            channel.basicPublish(exchangeName, routingKey, false, null, msg.getBytes());
        }
        //5.釋放資源
        channel.close();
        connection.close();
    }

}

2)消息消費者

import com.rabbitmq.client.*;
import java.io.IOException;

public class Consumer {
    public static void main(String[] args) throws Exception {
        //1.創建工廠類
        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(5672);
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        //默認情況下爲“ guest” /“ guest”,僅限本地主機連接
        factory.setUsername("guest");
        factory.setPassword("guest");

        //2.通過工廠創建connection
        Connection connection = factory.newConnection();
        //3.創建channel對象
        Channel channel = connection.createChannel();


        //4. 創建消息隊列和direct交換機,並通過channel讓交換機跟消息隊列進行綁定
        String queueName = "test_queue";
        String queueName2 = "test_queue2";
        String exchangeName ="test_topic_exchange";
        String exchangeType = "topic";
        String routingKey = "test.#";
        String routingKey2 = "*.topic";
        /**
         參數:
             queue -隊列名稱
             durable -如果我們聲明一個持久隊列,則爲true(該隊列將在服務器重啓後保留下來)
             exclusive -如果我們聲明一個排他隊列,則爲true(僅限此連接)
             autoDelete -如果我們聲明一個自動刪除隊列,則爲true(服務器將在不再使用它時將其刪除)
             arguments -隊列的其他屬性(構造參數)
         */
        channel.queueDeclare(queueName, true, false, false, null);
        channel.queueDeclare(queueName2, true, false, false, null);
        /**
         參數:
             exchange -交易所名稱
             type -交易所類型
             durable -如果我們聲明持久交換,則爲true(該交換將在服務器重啓後保留下來)
             autoDelete -如果服務器在不再使用交換機時應刪除該交換機,則爲true
             arguments -用於交換的其他屬性(構造參數)
         */
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
        /**
         參數:
             queue -隊列名稱
             exchange -交易所名稱
             routingKey -用於綁定的路由鍵
         */
        channel.queueBind(queueName, exchangeName, routingKey);
        channel.queueBind(queueName2, exchangeName, routingKey2);

        //5.通過channel把消費者和消息隊列進行關聯,獲取消息進行處理
        /**
         參數:
             queue -隊列名稱
             autoAck-如果服務器應考慮一旦傳遞已確認的消息,則爲true;如果服務器應該期望顯式確認,則返回false
             callback -消費者對象的接口
         */
        boolean autoAck = false;
        channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) {
            /**
             參數:
                 consumerTag-與消費者相關聯的消費者標籤
                 envelope -消息的打包數據
                 properties -消息的內容頭數據
                 body -消息正文(客戶端特定的不透明字節數組)
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                System.out.println("------------consumer message-----------");
                System.out.println("sonsumerTag:" + consumerTag);
                System.out.println("envelope:" + envelope);
                System.out.println("properties:" + properties);
                System.out.println("msg:" + new String(body));
            }
        });

    }
}

   創建了兩個隊列,這是隻對一個隊列的消息進行了消費

    

  

5、Fanout Exchange交換機

     扇型交換機,這個交換機沒有路由鍵概念,就算你綁了路由鍵也是無視的,不處理的。 這個交換機在接收到消息後,會直接轉發到綁定到它上面的所有隊列。

     

1)消息生產者

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) throws Exception {
        //1.創建工廠類
        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(5672);
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        //默認情況下爲“ guest” /“ guest”,僅限本地主機連接
        factory.setUsername("guest");
        factory.setPassword("guest");

        //2.通過工廠創建connection
        Connection connection = factory.newConnection();
        //3.創建channel對象
        Channel channel = connection.createChannel();

        //4.發佈消息
        String exchangeName="test_fanout_exchange";
        String routingKey = "";
        String msg="hello rabbitmq consumer message - test_fanout_exchange";
        for (int i = 0; i < 2; i++) {
            /**
             參數:
                 exchange -將消息發佈到的交換機, 若爲空字符串時,使用默認的交換機
                 routingKey -路由鍵
                 mandatory -如果要設置“強制性”標誌,則爲true
                 props -消息的其他屬性-路由標頭等
                 body -消息正文
             */
            channel.basicPublish(exchangeName, routingKey, false, null, msg.getBytes());
        }
        //5.釋放資源
        channel.close();
        connection.close();
    }

}

2)消息消費者

import com.rabbitmq.client.*;
import java.io.IOException;

public class Consumer {
    public static void main(String[] args) throws Exception {
        //1.創建工廠類
        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(5672);
        factory.setHost("localhost");
        factory.setVirtualHost("/");
        //默認情況下爲“ guest” /“ guest”,僅限本地主機連接
        factory.setUsername("guest");
        factory.setPassword("guest");

        //2.通過工廠創建connection
        Connection connection = factory.newConnection();
        //3.創建channel對象
        Channel channel = connection.createChannel();


        //4. 創建消息隊列和direct交換機,並通過channel讓交換機跟消息隊列進行綁定
        String queueName = "test_queue";
        String queueName2 = "test_queue2";
        String exchangeName ="test_fanout_exchange";
        String exchangeType = "fanout";
        String routingKey = "test.#"; // 隨便寫,扇形交換機不處理路右鍵
        String routingKey2 = "1*";
        /**
         參數:
             queue -隊列名稱
             durable -如果我們聲明一個持久隊列,則爲true(該隊列將在服務器重啓後保留下來)
             exclusive -如果我們聲明一個排他隊列,則爲true(僅限此連接)
             autoDelete -如果我們聲明一個自動刪除隊列,則爲true(服務器將在不再使用它時將其刪除)
             arguments -隊列的其他屬性(構造參數)
         */
        channel.queueDeclare(queueName, true, false, false, null);
        channel.queueDeclare(queueName2, true, false, false, null);
        /**
         參數:
             exchange -交易所名稱
             type -交易所類型
             durable -如果我們聲明持久交換,則爲true(該交換將在服務器重啓後保留下來)
             autoDelete -如果服務器在不再使用交換機時應刪除該交換機,則爲true
             arguments -用於交換的其他屬性(構造參數)
         */
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
        /**
         參數:
             queue -隊列名稱
             exchange -交易所名稱
             routingKey -用於綁定的路由鍵
         */
        channel.queueBind(queueName, exchangeName, routingKey);
        channel.queueBind(queueName2, exchangeName, routingKey2);

        //5.通過channel把消費者和消息隊列進行關聯,獲取消息進行處理
        /**
         參數:
             queue -隊列名稱
             autoAck-如果服務器應考慮一旦傳遞已確認的消息,則爲true;如果服務器應該期望顯式確認,則返回false
             callback -消費者對象的接口
         */
        boolean autoAck = false;
        channel.basicConsume(queueName2, autoAck, new DefaultConsumer(channel) {
            /**
             參數:
                 consumerTag-與消費者相關聯的消費者標籤
                 envelope -消息的打包數據
                 properties -消息的內容頭數據
                 body -消息正文(客戶端特定的不透明字節數組)
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                System.out.println("------------consumer message-----------");
                System.out.println("sonsumerTag:" + consumerTag);
                System.out.println("envelope:" + envelope);
                System.out.println("properties:" + properties);
                System.out.println("msg:" + new String(body));
            }
        });

    }
}

   

   

          

      一開始,我對RabbitMQ一點都不瞭解,但是項目中需要用到RabbitMQ,查資料算是springboot使用RabbitMQ實現了項目中的業務,但是,知其然不知其所以然,所以,從頭紮實的瞭解下RabbitMQ

     RabbitMQ 不同交換機的處理機制,也算入門了,交換機的創建不同,其他基本類似,剛開始先會使用即可。

   

     站在前輩的肩膀上,每天進步一點點

ends~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章