Springboot整合RabbitMQ

lianaozhe發表於2021-11-14

說明該篇文章內容包括有rabbitMq相關的一些簡單理論介紹,provider訊息推送例項,consumer訊息消費例項,Direct、Topic、Fanout的使用。

 

  在安裝完rabbitMq後,輸入http://ip:15672/ ,是可以看到一個簡單後臺管理介面的。

 

在這個介面裡面我們可以做些什麼?
可以手動建立虛擬host,建立使用者,分配許可權,建立交換機,建立佇列等等,還有檢視佇列訊息,消費效率,推送效率等等。

以上這些管理介面的操作在這篇暫時不做擴充套件描述,我想著重介紹後面例項裡會使用到的。

首先先介紹一個簡單的一個訊息推送到接收的流程,提供一個簡單的圖:

 

黃色的圈圈就是我們的訊息推送服務,將訊息推送到 中間方框裡面也就是 rabbitMq的伺服器,然後經過伺服器裡面的交換機、佇列等各種關係(後面會詳細講)將資料處理入列後,最終右邊的藍色圈圈消費者獲取對應監聽的訊息。

常用的交換機有以下三種,因為消費者是從佇列獲取資訊的,佇列是繫結交換機的(一般),所以對應的訊息推送/接收模式也會有以下幾種:
Direct Exchange

直連型交換機,根據訊息攜帶的路由鍵將訊息投遞給對應佇列。

大致流程,有一個佇列繫結到一個直連交換機上,同時賦予一個路由鍵 routing key 。
然後當一個訊息攜帶著路由值為X,這個訊息通過生產者傳送給交換機時,交換機就會根據這個路由值X去尋找繫結值也是X的佇列。

Fanout Exchange

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

Topic Exchange

主題交換機,這個交換機其實跟直連交換機流程差不多,但是它的特點就是在它的路由鍵和繫結鍵之間是有規則的。
簡單地介紹下規則:

* (星號) 用來表示一個單詞 (必須出現的)
# (井號) 用來表示任意數量(零個或多個)單詞
通配的繫結鍵是跟佇列進行繫結的,舉個小例子
佇列Q1 繫結鍵為 *.TT.* 佇列Q2繫結鍵為 TT.#
如果一條訊息攜帶的路由鍵為 A.TT.B,那麼佇列Q1將會收到;
如果一條訊息攜帶的路由鍵為TT.AA.BB,那麼佇列Q2將會收到;

主題交換機是非常強大的,為啥這麼膨脹?
當一個佇列的繫結鍵為 "#"(井號) 的時候,這個佇列將會無視訊息的路由鍵,接收所有的訊息。
當 * (星號) 和 # (井號) 這兩個特殊字元都未在繫結鍵中出現的時候,此時主題交換機就擁有的直連交換機的行為。
所以主題交換機也就實現了扇形交換機的功能,和直連交換機的功能。

另外還有 Header Exchange 頭交換機 ,Default Exchange 預設交換機,Dead Letter Exchange 死信交換機,這幾個該篇暫不做講述。

本次例項教程需要建立2個springboot專案,一個生產者,一個消費者。

首先建立 生產者,

pom.xml裡用到的jar依賴:

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

  application.yml:

#本機埠
server:
  port: 8082
spring:
  #給專案來個名字
  application:
    name: rabbitmq-provider
  #配置rabbitMq 伺服器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

  然後先使用下direct exchange(直連型交換機),建立DirectRabbitConfig.java

package com.wx.test.rtmdemo.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author : laz
 * @CreateTime : 2021/11/14
 * @Description :
 **/
@Configuration
public class DirectRabbitConfig {

    //佇列 起名:TestDirectQueue
    @Bean
    public Queue TestDirectQueue() {
        //佇列的三個引數講解
        // durable:是否持久化,預設是false,持久化佇列:會被儲存在磁碟上,當訊息代理重啟時仍然存在,暫存佇列:當前連線有效
        // exclusive:預設也是false,只能被當前建立的連線使用,而且當連線關閉後佇列即被刪除。此參考優先順序高於durable
        // autoDelete:是否自動刪除,當沒有生產者或者消費者使用此佇列,該佇列會自動刪除。
        //   return new Queue("TestDirectQueue",true,true,false);

        //一般設定一下佇列的持久化就好,其餘兩個就是預設false
        return new Queue("TestDirectQueue",true);
    }

    //Direct交換機 起名:TestDirectExchange
    @Bean
    DirectExchange TestDirectExchange() {
      //  return new DirectExchange("TestDirectExchange",true,true);
        return new DirectExchange("TestDirectExchange",true,false);
    }

    //繫結  將佇列和交換機繫結, 並設定用於匹配鍵:TestDirectRouting
    @Bean
    Binding bindingDirect() {

        return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
    }


}

  然後寫個簡單的介面進行訊息推送:

    @GetMapping("/sendDirectMessage")
    public String sendDirectMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "test message, hello!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String,Object> map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //將訊息攜帶繫結鍵值:TestDirectRouting 傳送到交換機TestDirectExchange
        rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map);
        return "ok";
    }

  將專案執行起來,呼叫這個介面:

 

 

 此時,在rabbitMq管理頁面可以看到,有一條推送的訊息。

 然後有一個訊息等待被消費。

 

 

 

 此時,說明我們的生產者的訊息已經生產成功,只需要等待消費者消費即可!

接下來建立消費者服務:

首先是pom.xml檔案的依賴:

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

  然後是application.yml:

server:
  port: 8083
spring:
  #給專案來個名字
  application:
    name: rabbitmq-consumer
  #配置rabbitMq 伺服器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

然後是建立訊息接收監聽類,DirectReceiver.java:

package com.wx.test.consumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RabbitListener(queues = "TestDirectQueue")//監聽的佇列名稱 TestDirectQueue
public class DirectReceiver {

    @RabbitHandler
    public void process(Map testMessage) {
        System.out.println("DirectReceiver消費者收到訊息  : " + testMessage.toString());
    }

}

  然後啟動專案,可以看到生產者的訊息,在這邊被成功消費了。

 

 

 

 

 接下來是Topic Exchange交換機,配置檔案以及依賴包不變,新建一個TopicRabbitConfig.java:

package com.wx.test.rtmdemo.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author : laz
 * @CreateTime : 2021/11/14
 * @Description :
 **/

@Configuration
public class TopicRabbitConfig {
    //繫結鍵
    public final static String man = "topic.man";
    public final static String woman = "topic.woman";

    @Bean
    public Queue firstQueue() {
        return new Queue(TopicRabbitConfig.man);
    }

    @Bean
    public Queue secondQueue() {
        return new Queue(TopicRabbitConfig.woman);
    }

    @Bean
    TopicExchange exchange() {
        return new TopicExchange("topicExchange");
    }


    //將firstQueue和topicExchange繫結,而且繫結的鍵值為topic.man
    //這樣只要是訊息攜帶的路由鍵是topic.man,才會分發到該佇列
    @Bean
    Binding bindingExchangeMessage() {
        return BindingBuilder.bind(firstQueue()).to(exchange()).with(man);
    }

    //將secondQueue和topicExchange繫結,而且繫結的鍵值為用上通配路由鍵規則topic.#
    // 這樣只要是訊息攜帶的路由鍵是以topic.開頭,都會分發到該佇列
    @Bean
    Binding bindingExchangeMessage2() {
        return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");
    }

}

  然後在建立兩個介面,用於測試:

    @GetMapping("/sendTopicMessage1")
    public String sendTopicMessage1() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "message: M A N ";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String, Object> manMap = new HashMap<>();
        manMap.put("messageId", messageId);
        manMap.put("messageData", messageData);
        manMap.put("createTime", createTime);
        rabbitTemplate.convertAndSend("topicExchange", "topic.man", manMap);
        return "ok";
    }

    @GetMapping("/sendTopicMessage2")
    public String sendTopicMessage2() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "message: woman is all ";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String, Object> womanMap = new HashMap<>();
        womanMap.put("messageId", messageId);
        womanMap.put("messageData", messageData);
        womanMap.put("createTime", createTime);
        rabbitTemplate.convertAndSend("topicExchange", "topic.woman123", womanMap);
        return "ok";
    }

  然後在建立一個消費者TopicManReceiver.java::

package com.wx.test.consumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;

/**
 * @Author : laz
 * @CreateTime : 2021/11/14
 * @Description :
 **/
@Component
@RabbitListener(queues = "topic.man")
public class TopicManReceiver {

    @RabbitHandler
    public void process(Map testMessage) {
        System.out.println("TopicManReceiver消費者收到訊息  : " + testMessage.toString());
    }
}

  然後再建立一個消費者TopicTotalReceiver.java::

package com.wx.test.consumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;

/**
 * @Author : laz
 * @CreateTime : 2021/11/14
 * @Description :
 **/

@Component
@RabbitListener(queues = "topic.woman")
public class TopicTotalReceiver {

    @RabbitHandler
    public void process(Map testMessage) {
        System.out.println("TopicTotalReceiver消費者收到訊息  : " + testMessage.toString());
    }
}

  啟動專案,呼叫sendTopicMessage1這個介面:

 

 

 

可以看到,兩個消費者都消費了這條訊息。

注意:

TopicManReceiver監聽佇列1,繫結鍵為:topic.man
TopicTotalReceiver監聽佇列2,繫結鍵為:topic.#
而當前推送的訊息,攜帶的路由鍵為:topic.man

再呼叫sendTopicMessage2這個介面:

 

 這時,只有TopicTotalReceiver成功消費了。

 

 

然後是Fanout Exchang交換機:

首先,建立生產者:

package com.wx.test.rtmdemo.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @Author : laz
 * @CreateTime : 2021/11/14
 * @Description :
 **/

@Configuration
public class FanoutRabbitConfig {

    /**
     *  建立三個佇列 :fanout.A   fanout.B  fanout.C
     *  將三個佇列都繫結在交換機 fanoutExchange 上
     *  因為是扇型交換機, 路由鍵無需配置,配置也不起作用
     */


    @Bean
    public Queue queueA() {
        return new Queue("fanout.A");
    }

    @Bean
    public Queue queueB() {
        return new Queue("fanout.B");
    }

    @Bean
    public Queue queueC() {
        return new Queue("fanout.C");
    }

    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanoutExchange");
    }

    @Bean
    Binding bindingExchangeA() {
        return BindingBuilder.bind(queueA()).to(fanoutExchange());
    }

    @Bean
    Binding bindingExchangeB() {
        return BindingBuilder.bind(queueB()).to(fanoutExchange());
    }

    @Bean
    Binding bindingExchangeC() {
        return BindingBuilder.bind(queueC()).to(fanoutExchange());
    }
}

  同樣寫一個介面,用於生產一條訊息:

    @GetMapping("/sendFanoutMessage")
    public String sendFanoutMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "message: testFanoutMessage ";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String, Object> map = new HashMap<>();
        map.put("messageId", messageId);
        map.put("messageData", messageData);
        map.put("createTime", createTime);
        rabbitTemplate.convertAndSend("fanoutExchange", null, map);
        return "ok";
    }

  接下來在消費者裡面建立一個類,用於接收訊息:

FanoutReceiverA.java:

package com.wx.test.consumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
 * @Author : laz
 * @CreateTime : 2021/11/14
 * @Description :
 **/
@Component
@RabbitListener(queues = "fanout.A")
public class FanoutReceiverA {

    @RabbitHandler
    public void process(Map testMessage) {
        System.out.println("FanoutReceiverA消費者收到訊息  : " +testMessage.toString());
    }

}

  

FanoutReceiverB.java:

package com.wx.test.consumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
 * @Author : laz
 * @CreateTime : 2021/11/14
 * @Description :
 **/
@Component
@RabbitListener(queues = "fanout.B")
public class FanoutReceiverB {

    @RabbitHandler
    public void process(Map testMessage) {
        System.out.println("FanoutReceiverB消費者收到訊息  : " +testMessage.toString());
    }

}

  

FanoutReceiverC.java:

package com.wx.test.consumer.config;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;

/**
 * @Author : laz
 * @CreateTime : 2021/11/14
 * @Description :
 **/
@Component
@RabbitListener(queues = "fanout.C")
public class FanoutReceiverC {

    @RabbitHandler
    public void process(Map testMessage) {
        System.out.println("FanoutReceiverC消費者收到訊息  : " +testMessage.toString());
    }

}

  最後啟動專案,呼叫我們生產者的介面,檢視訊息消費情況:

 

 可以看到,這三個類都消費到訊息。

 

 

好了,這篇Springboot整合rabbitMq教程就暫且到此。本文若有錯誤,還請各路大佬指正指正!

 

相關文章