RabbitMQ學習(二):RabbitMQ的基本概念

RabbitMQ相關概念

  • RabbitMQ是一個Erlang開發的AMQP(Advanced Message Queuing Protocol,高級消息隊列協議)的開源實現。是應用層協議的一個開放標準,爲面向消息的中間件設計。消息中間件主要用於組件之間的解耦,消息的發送者無需知道消息使用者的存在。
  • 主要特徵
    1. 可靠性:持久化、傳輸確認、發佈確認等機制來保證可靠性。
    2. 擴展性:支持動態擴展集羣中的節點
    3. 高可用:隊列可在集羣中設置鏡像,部分節點出現問題仍然可用
    4. 多協議:AMQP協議、STOMP、MOTT等多種消息中間件協議
    5. 多語言:java、Python、Ruby、PHP、C#、JavaScript、Go、Object-C等
    6. 支持插件:如web管理端。
  • 消息隊列有三個基本概念: 發送方、消息隊列、消費方。RabbitMQ 在這個基本概念之上, 多做了一層抽象, 在發消息者和隊列之間, 加入了交換器 (Exchange)。這樣發消息者和消息隊列就沒有直接聯繫,轉而變成發消息者把消息發給交換器,交換器根據調度策略再把消息轉發給消息隊列。消息生產者並沒有直接將消息發送給消息隊列,而是通過建立與Exchange的Channel,將消息發送給Exchange。Exchange根據路由規則,將消息轉發給指定的消息隊列。消息隊列儲存消息,等待消費者取出消息。消費者通過建立與消息隊列相連的Channel,從消息隊列中獲取消息。

AMQP概念

Broker:

接收和分發消息的應用,我們在介紹消息中間件的時候所說的消息系統就是Message Broker。

Virtual host:

出於多租戶和安全因素設計的,把AMQP的基本組件劃分到一個虛擬的分組中,類似於網絡中的namespace概念。當多個不同的用戶使用同一個RabbitMQ server提供的服務時,可以劃分出多個vhost,每個用戶在自己的vhost創建exchange/queue等。

Connection:

publisher/consumer和broker之間的TCP連接。斷開連接的操作只會在client端進行,Broker不會斷開連接,除非出現網絡故障或broker服務出現問題。

Channel:

如果每一次訪問RabbitMQ都建立一個Connection,在消息量大的時候建立TCP Connection的開銷將是巨大的,效率也較低。Channel是在connection內部建立的邏輯連接,如果應用程序支持多線程,通常每個thread創建單獨的channel進行通訊,AMQP method包含了channel id幫助客戶端和message broker識別channel,所以channel之間是完全隔離的。Channel作爲輕量級的Connection極大減少了操作系統建立TCP connection的開銷。

Exchange:

message到達broker的第一站,根據分發規則,匹配查詢表中的routing key,分發消息到queue中去。

  • **direct (路由模式)😗*這種類型的交換機的路由規則是根據一個routingKey的標識,交換機通過一個routingKey與隊列綁定 ,在生產者生產消息的時候 指定一個routingKey 當綁定的隊列的routingKey 與生產者發送的一樣 那麼交換機會吧這個消息發送給對應的隊列。
  • **fanout(發佈訂閱)😗*這種類型的交換機路由規則很簡單,只要與他綁定了的隊列, 他就會吧消息發送給對應隊列(與routingKey沒關係)
  • **topic(主題模式)😗*這種類型的交換機路由規則也是和routingKey有關 只不過 topic他可以根據:星,#( 星號代表過濾一單詞,#代表過濾後面所有單詞, 用.隔開)來識別routingKey 我打個比方 假設 我綁定的routingKey 有隊列A和B A的routingKey是:星.user B的routingKey是: #.user那麼我生產一條消息routingKey 爲: error.user 那麼此時 2個隊列都能接受到, 如果改爲 topic.error.user 那麼這時候 只有B能接受到了
  • **headers(RPC)😗*這個類型的交換機很少用到,他的路由規則 與routingKey無關 而是通過判斷header參數來識別的, 基本上沒有應用場景,因爲上面的三種類型已經能應付了。

Queue:

消息最終被送到這裏等待consumer取走。一個message可以被同時拷貝到多個queue中。

Binding:

exchange和queue之間的虛擬連接,binding中可以包含routing key。Binding信息被保存到exchange中的查詢表中,用於message的分發依據。

RabbitMQ幾種應用模式

在這裏插入圖片描述

    // 獲取MQ的連接  下面代碼獲取連接都是使用的這個工具類
    public static Connection newConnection() throws IOException, TimeoutException {
        // 1.創建連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        // 2.設置連接地址
        factory.setHost("192.168.100.150");
        // 3.設置用戶名稱
        factory.setUsername("admin");
        // 4.設置用戶密碼
        factory.setPassword("admin");
        // 5.設置amqp協議端口號
        factory.setPort(5672);
        // 6.設置VirtualHost地址
        factory.setVirtualHost("adminDemo");
        Connection connection = factory.newConnection();
        return connection;
    }
  • 簡單模式
    在這裏插入圖片描述
    • 消息產生消息,將消息放入隊列
    • 消息的消費者(consumer) 監聽(while) 消息隊列,如果隊列中有消息,就消費掉,消息被拿走後,自動從隊列中刪除。
    • Demo演示
    private static String QUEUE_NAME = "demo_hello";
      //獲取連接
        Connection connection = MQConnectionUtils.newConnection();
        //創建通道
        Channel channel = connection.createChannel();
        // 創建一個隊列:隊列名稱,是否持久化,是否自動刪除隊列,是否排外
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        for (int i = 1; i <= 25; i++) {
            // 創建 msg
            String msg = "生成 ---" + i;
            // 生產者發送消息者     MessageProperties.PERSISTENT_TEXT_PLAIN 設置消息的持久化(消息沒有接收到也不會丟失)
            channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes("UTF-8"));
        }
        // 關閉通道和連接
        channel.close();
        connection.close();

在這裏插入圖片描述

    private static String QUEUE_NAME = "demo_hello";
     	 //獲取連接
        Connection connection = MQConnectionUtils.newConnection();
       	 //創建通道
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
                DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            // 監聽獲取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body, "UTF-8");
            }
        };
        // 設置應答模式 如果爲true情況下 表示爲自動應答模式 false 表示爲手動應答
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

在這裏插入圖片描述

  • work queues(工作模式)
    在這裏插入圖片描述
    • 多個消費者可以訂閱同一個隊列,這時隊列中的消息會被平均分攤給多個消費者進行處理,而不是每個消費
      者都收到所有的消息並處理。
    • 這時如果每個消息的處理時間不同,就有可能會導致某些消費者一直在忙,而另外一些消費者很快就處理完手頭工作並一直空閒的情況。我們可以通過設置prefetchCount來限制Queue每次發送給每個消費者的消息數,比如我們設置prefetchCount=1,則Queue每次給每個消費者發送一條消息;消費者處理完這條消息後Queue會再給該消費者發送一條消息。
    • Demo演示
      工作隊列只需要將上面的消費者多複製幾分就可以了。
      如果發現消息分發不均勻可以設置
        /**
         * 公平隊列原理:隊列服務器向消費者發送消息的時候,消費者採用手動應答模式,
         * 隊列服務器必須要收到消費者發送ack結果通知,纔會繼續發送一下一個消息
         * 此處設置一次只消費1個
         */
        channel.basicQos(1);

默認情況下,rabbitmq開啓了消息的自動應答。此時,一旦rabbitmq將消息分發給了消費者,就會將消息從內存中刪除。這種情況下,如果正在執行的消費者被“殺死”或“崩潰”,就會丟失正在處理的消息。 如果想要確保消息不丟失,我們需要設置消息應答方式爲手動應答。設置爲手工應答後,消費者接受並處理完一個消息後,會發送應答給rabbitmq,rabbitmq收到應答後,會將該條消息從內存中刪除。如果一個消費者在處理消息的過程中“崩潰”,rabbitmq沒有收到應答,那麼”崩潰“前正在處理的這條消息會重新被分發到別的消費者。

        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body, "UTF-8");
                // 手動應答 模式 告訴給隊列服務器 已經處理成功
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 4.設置應答模式 如果爲true情況下 表示爲自動應答模式 false 表示爲手動應答
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
  • publish/subscribe(發佈訂閱)
    在這裏插入圖片描述
    • 1個生產者,多個消費者。每一個消費者都有自己的一個隊列。生產者沒有將消息直接發送到隊列,而是發送到了交換機。每個隊列都要綁定到交換機。生產者發送的消息,經過交換機到達隊列,實現一個消息被多個消費者獲取的目的
    • X(Exchanges)接收生產者發送的消息。知道如何處理消息,例如遞交給某個特別隊列、遞交給所有隊列、或是將消息丟棄。
    • 這種模式不需要RouteKey
    • Demo演示
//生產者
	private static final String DESTINATION_NAME = "rabbitMq_fanout";
	// 1. 建立mq連接
		Connection connection = MQConnectionUtils.newConnection();
		// 2.創建通道
		Channel channel = connection.createChannel();
		// 3.生產者綁定交換機 參數1:交換機名稱 參數2:交換機類型
		channel.exchangeDeclare(DESTINATION_NAME, "fanout");
		// 4.創建消息
		String msg = "rabbitMq_fanout";
		System.out.println("生產者投遞消息:" + msg);
		// 5.發送消息
		channel.basicPublish(DESTINATION_NAME, "",null , msg.getBytes());
		// 6.關閉通道 和連接
		channel.close();
		connection.close();
//消費者
 // 交換機名稱
    private static final String DESTINATION_NAME = "rabbitMq_fanout";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 建立mq連接
        Connection connection = MQConnectionUtils.newConnection();
        // 2.創建通道
        Channel channel = connection.createChannel();
        System.out.println("短信消費者啓動");
        FanoutConsumer.smsConsumer(channel);
        System.out.println("郵件消費啓動");
        FanoutConsumer.emailConsumer(channel);
    }
    private static final String SMS_QUEUE = "Sms";
    public static void smsConsumer(Channel channel) throws IOException {
        // 3.消費聲明隊列
        channel.queueDeclare(SMS_QUEUE, false, false, false, null);
        // 4.消費者隊列綁定交換機
        channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "");
        // 5.消費監聽消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("短信消費者獲取生產消息:" + msg);
            }
        };
        channel.basicConsume(SMS_QUEUE, true, defaultConsumer);
    }
    private static final String EMAIL_QUEUE = "Email";
    public static void emailConsumer(Channel channel) throws IOException {
        // 3.消費聲明隊列
        channel.queueDeclare(EMAIL_QUEUE, false, false, false, null);
        // 4.消費者隊列綁定交換機
        channel.queueBind(EMAIL_QUEUE, DESTINATION_NAME, "");
        // 5.消費監聽消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("郵件消費者獲取生產消息:" + msg);
            }
        };
        //自動應答
        channel.basicConsume(EMAIL_QUEUE, true, defaultConsumer);
    }
  • routing(路由模式)
    在這裏插入圖片描述
    • 處理路由鍵。需要將一個隊列綁定到交換機上,要求該消息與一個特定的路由鍵完全匹配。這是一個完整的匹配。如果一個隊列綁定到該交換機上要求路由鍵 “dog”,則只有被標記爲“dog”的消息才被轉發,不會轉發dog.puppy,也不會轉發dog.guard,只會轉發dog。
    • 任何發送到Direct Exchange的消息都會被轉發到RouteKey中指定的Queue
    • 一般情況可以使用rabbitMQ自帶的Exchange:”"(該Exchange的名字爲空字符串,下文稱其爲default Exchange)。
    • 這種模式下不需要將Exchange進行任何綁定(binding)操作。
    • 消息傳遞時需要一個“RouteKey”,可以簡單的理解爲要發送到的隊列名字。
    • 如果vhost中不存在RouteKey中指定的隊列名,則該消息會被拋棄。
    • Demo演示
//生產者
	// 交換機名稱
	private static final String DESTINATION_NAME = "rabbitMq_direct";
		// 1. 建立mq連接
		Connection connection = MQConnectionUtils.newConnection();
		// 2.創建通道
		Channel channel = connection.createChannel();
		// 3.生產者綁定交換機 參數1 交換機名稱 參數2 交換機類型
		channel.exchangeDeclare(DESTINATION_NAME, "direct");
		//這個是路由鍵的名稱,方便測試
		String s1="sms";
		// 4.創建消息
		String msg = "rabbitMq_direct---:" +s1;
		System.out.println("生產者投遞消息:" + msg);
		// 5.發送消息  routingKey:email
		channel.basicPublish(DESTINATION_NAME, s1, null, msg.getBytes());
		// 6.關閉通道 和連接
		channel.close();
		connection.close();
//消費者
   // 交換機名稱
    private static final String DESTINATION_NAME = "rabbitMq_direct";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MQConnectionUtils.newConnection();
        // 2.創建通道
        Channel channel = connection.createChannel();
        System.out.println("短信消費者啓動");
        DirectConsumer.smsConsumer(channel);
        System.out.println("郵件消費者啓動");
        DirectConsumer.emailConsumer(channel);
    }
    private static final String SMS_QUEUE = "Sms_msg";
    public static void smsConsumer( Channel channel) throws IOException {
        // 3.消費聲明隊列
        channel.queueDeclare(SMS_QUEUE, false, false, false, null);
        // 4.消費者隊列綁定交換機 綁定路由鍵
        channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "sms");
        // 5.消費監聽消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("短信消費者獲取生產消息--:" + msg);
            }
        };
        channel.basicConsume(SMS_QUEUE, true, defaultConsumer);
    }
    
    private static final String EMAIL_QUEUE = "Email_msg";
    public static void emailConsumer( Channel channel) throws IOException {
        // 3.消費聲明隊列
        channel.queueDeclare(EMAIL_QUEUE, false, false, false, null);
        // 4.消費者隊列綁定交換機 綁定路由鍵,可以設置多個
        channel.queueBind(EMAIL_QUEUE, DESTINATION_NAME, "email");
        // 5.消費監聽消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("郵件消費者獲取生產消息-----:" + msg);
            }
        };
        channel.basicConsume(EMAIL_QUEUE, true, defaultConsumer);
    }

  • topic(主題模式)
    在這裏插入圖片描述
    • Topic Exchange – 將路由鍵和某模式進行匹配。此時隊列需要綁定要一個模式上。符號“#”匹配一個或多個詞,符號“ * ”匹配不多不少一個詞。因此“audit.#”能夠匹配到“audit.irs.corporate”,但是“audit.*” 只會匹配到“audit.irs”
    • 任何發送到Topic Exchange的消息都會被轉發到所有關心RouteKey中指定話題的Queue上
    • 這種模式較爲複雜,簡單來說,就是每個隊列都有其關心的主題,所有的消息都帶有一個“標題”(RouteKey),Exchange會將消息轉發到所有關注主題能與RouteKey模糊匹配的隊列。
    • 這種模式需要RouteKey,也許要提前綁定Exchange與Queue。
    • 在進行綁定時,要提供一個該隊列關心的主題,如“#.log.#”表示該隊列關心所有涉及log的消息(一個RouteKey爲”MQ.log.error”的消息會被轉發到該隊列)。
    • “#”表示0個或若干個關鍵字,“ * ”表示一個關鍵字。如“log.*”能與“log.warn”匹配,無法與“log.warn.timeout”匹配;但是“log.#”能與上述兩者匹配。
    • 同樣,如果Exchange沒有發現能夠與RouteKey匹配的Queue,則會拋棄此消息。
    • Demo演示
// 交換機名稱   生產者
	private static final String DESTINATION_NAME = "rabbitMq_topic";
		// 1. 建立mq連接
		Connection connection = MQConnectionUtils.newConnection();
		// 2.創建通道
		Channel channel = connection.createChannel();
		// 3.生產者綁定交換機 參數1 交換機名稱 參數2 交換機類型
		channel.exchangeDeclare(DESTINATION_NAME, "topic");
		//routingKey
		String s1="log.sms.test.demo";
		// 4.創建消息
		String msg = "rabbitMq_msg_topic:"+s1 ;
		System.out.println("生產者投遞消息:" + msg);
		// 5.發送消息  routingKey:email
		channel.basicPublish(DESTINATION_NAME, s1, null, msg.getBytes());
		// 6.關閉通道 和連接
		channel.close();
		connection.close();

 // 交換機名稱
    private static final String DESTINATION_NAME = "rabbitMq_topic";
    public static void main(String[] args) throws Exception {
        // 1. 建立mq連接
        Connection connection = MQConnectionUtils.newConnection();
        // 2.創建通道
        Channel channel = connection.createChannel();
        System.out.println("短信消費者啓動");
        TopicConsumer.smsConsumer(channel);
        System.out.println("郵件消費者啓動");
        TopicConsumer.maileConsumer(channel);
    }
    private static final String SMS_QUEUE = "topic_sms";
    public static void smsConsumer(Channel channel) throws IOException {
        channel.queueDeclare(SMS_QUEUE, false, false, false, null);
        channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "log.sms");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("短信消費者獲取生產消息----:" + msg);
            }
        };
        channel.basicConsume(SMS_QUEUE, true, defaultConsumer);
    }
    private static final String MAILE_QUEUE = "topic_email";
    public static void maileConsumer(Channel channel) throws IOException {
        // 3.消費聲明隊列
        channel.queueDeclare(MAILE_QUEUE, false, false, false, null);
        // *只要前綴相同都能收到
		//channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "log.*");
        //可以匹配後面所有的詞
        channel.queueBind(MAILE_QUEUE, DESTINATION_NAME, "log.#");
        // 5.消費監聽消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("郵件消費者獲取生產消息--------:" + msg);
            }
        };
        channel.basicConsume(MAILE_QUEUE, true, defaultConsumer);
    }


  • RPC
    在這裏插入圖片描述
    這個模式聽說用的很少,我也沒有去了解這個模式。以後如果遇到會再來補充的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章