說明該篇文章內容包括有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教程就暫且到此。本文若有錯誤,還請各路大佬指正指正!