我們在之前的兩個章節第四十一章: 基於SpringBoot & RabbitMQ完成DirectExchange分散式訊息消費、第四十二章: 基於SpringBoot & RabbitMQ完成DirectExchange分散式訊息多消費者消費提高了RabbitMQ
訊息佇列的DirectExchange
交換型別的訊息消費,我們之前的章節提到了RabbitMQ
比較常用的交換型別有三種,我們今天來看看TopicExchange
主題交換型別。
本章目標
基於SpringBoot
平臺完成RabbitMQ
的TopicExchange
訊息型別交換。
SpringBoot 企業級核心技術學習專題
專題 | 專題名稱 | 專題描述 |
---|---|---|
001 | Spring Boot 核心技術 | 講解SpringBoot一些企業級層面的核心元件 |
002 | Spring Boot 核心技術章節原始碼 | Spring Boot 核心技術簡書每一篇文章碼雲對應原始碼 |
003 | Spring Cloud 核心技術 | 對Spring Cloud核心技術全面講解 |
004 | Spring Cloud 核心技術章節原始碼 | Spring Cloud 核心技術簡書每一篇文章對應原始碼 |
005 | QueryDSL 核心技術 | 全面講解QueryDSL核心技術以及基於SpringBoot整合SpringDataJPA |
006 | SpringDataJPA 核心技術 | 全面講解SpringDataJPA核心技術 |
解決問題
之前少年也遇到了一個問題,分類了多模組後訊息佇列無法自動建立,說來也好笑,之前沒有時間去看這個問題,今天在編寫本章文章時發現原因竟然是SpringBoot
沒有掃描到common
模組內的配置類。讓我一陣的頭大~~~,我們在XxxApplication
啟動類上新增@ComponentScan(value = "com.hengyu.rabbitmq")
就可以自動建立佇列了!!!
構建專案
本章構建專案時同樣採用多模組的方式進行設計,可以很好的看到訊息處理的效果,因為是多模組專案,我們先來建立一個SpringBoot
專案,pom.xml
配置檔案依賴配置如下所示:
<dependencies>
<!--rabbbitMQ相關依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--web相關依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--spring boot tester-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--fast json依賴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.40</version>
</dependency>
</dependencies>
複製程式碼
下面我們先來構建公共RabbitMQ
模組,因為我們的消費者以及生產者都是需要RabbitMQ
相關的配置資訊,這裡我們可以提取出來,使用時進行模組之間的引用。
rabbitmq-topic-common
建立子模組rabbitmq-topic-common
,在resources
下新增application.yml
配置檔案並配置RabbitMQ
相關的依賴配置,如下所示:
spring:
#rabbitmq訊息佇列配置資訊
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirms: true
複製程式碼
定義交換配置資訊
我們跟之前的章節一張,獨立編寫一個列舉型別來配置訊息佇列的交換資訊,如下所示:
/**
* rabbitmq交換配置列舉
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:13:56
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@Getter
public enum ExchangeEnum
{
/**
* 使用者註冊交換配置列舉
*/
USER_REGISTER_TOPIC_EXCHANGE("register.topic.exchange")
;
private String name;
ExchangeEnum(String name) {
this.name = name;
}
}
複製程式碼
定義佇列配置資訊
同樣訊息佇列的基本資訊配置也同樣採用列舉的形式配置,如下所示:
/**
* 佇列配置列舉
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:14:05
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@Getter
public enum QueueEnum
{
/**
* 使用者註冊
* 建立賬戶訊息佇列
*/
USER_REGISTER_CREATE_ACCOUNT("register.account","register.#"),
/**
* 使用者註冊
* 傳送註冊成功郵件訊息佇列
*/
USER_REGISTER_SEND_MAIL("register.mail","register.#")
;
/**
* 佇列名稱
*/
private String name;
/**
* 佇列路由鍵
*/
private String routingKey;
QueueEnum(String name, String routingKey) {
this.name = name;
this.routingKey = routingKey;
}
}
複製程式碼
訊息佇列列舉內新增了兩個屬性,分別對應了佇列名稱
、佇列路由
,我們本章所講解的TopicExchange
型別訊息佇列可以根據路徑資訊配置多個訊息消費者,而轉發的匹配規則資訊則是我們定義的佇列的路由資訊。
定義傳送訊息路由資訊
我們在傳送訊息到佇列時,需要我們傳遞一個路由相關的配置資訊,RabbitMQ
會根據傳送時的訊息路由規則資訊與定義訊息佇列時的路由資訊進行匹配,如果可以匹配則呼叫該佇列的消費者完成訊息的消費,傳送訊息路由資訊配置如下所示:
/**
* 訊息佇列topic交換路由key配置列舉
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:21:58
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@Getter
public enum TopicEnum {
/**
* 使用者註冊topic路由key配置
*/
USER_REGISTER("register.user")
;
private String topicRouteKey;
TopicEnum(String topicRouteKey) {
this.topicRouteKey = topicRouteKey;
}
}
複製程式碼
路由特殊字元 #
我們在QueueEnum
內配置的路由鍵
時有個特殊的符號:#
,在RabbitMQ
訊息佇列內路由配置#
時表示可以匹配零個或多個字元,我們TopicEnum
列舉內定義的register.user
,則是可以匹配QueueEnum
列舉定義register.#
佇列的路由規則。
當然傳送訊息時如果路由傳遞:register.user.account
也是可以同樣匹配register.#
的路由規則。
路由特殊字元 *
除此之外比較常用到的特殊字元還有一個*
,在RabbitMQ
訊息佇列內路由配置*
時表示可以匹配一個字元,我們QueueEnum
定義路由鍵如果修改成register.*
時,傳送訊息時路由為register.user
則是可以接受到訊息的。但如果傳送時的路由為register.user.account
時,則是無法匹配該訊息。
訊息佇列配置
配置準備工作已經做好,接下來我們開始配置佇列相關的內容,跟之前一樣我們需要配置Queue
、Exchange
、Binding
將訊息佇列與交換繫結。下面我們來看看配置跟之前的章節有什麼差異的地方,程式碼如下所示:
/**
* 使用者註冊訊息佇列配置
* ========================
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:16:58
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@Configuration
public class UserRegisterQueueConfiguration {
private Logger logger = LoggerFactory.getLogger(UserRegisterQueueConfiguration.class);
/**
* 配置使用者註冊主題交換
* @return
*/
@Bean
public TopicExchange userTopicExchange()
{
TopicExchange topicExchange = new TopicExchange(ExchangeEnum.USER_REGISTER_TOPIC_EXCHANGE.getName());
logger.info("使用者註冊交換例項化成功。");
return topicExchange;
}
/**
* 配置使用者註冊
* 傳送啟用郵件訊息佇列
* 並設定持久化佇列
* @return
*/
@Bean
public Queue sendRegisterMailQueue()
{
Queue queue = new Queue(QueueEnum.USER_REGISTER_SEND_MAIL.getName());
logger.info("建立使用者註冊訊息佇列成功");
return queue;
}
/**
* 配置使用者註冊
* 建立賬戶訊息佇列
* 並設定持久化佇列
* @return
*/
@Bean
public Queue createAccountQueue()
{
Queue queue = new Queue(QueueEnum.USER_REGISTER_CREATE_ACCOUNT.getName());
logger.info("建立使用者註冊賬號佇列成功.");
return queue;
}
/**
* 繫結使用者傳送註冊啟用郵件佇列到使用者註冊主題交換配置
* @return
*/
@Bean
public Binding sendMailBinding(TopicExchange userTopicExchange,Queue sendRegisterMailQueue)
{
Binding binding = BindingBuilder.bind(sendRegisterMailQueue).to(userTopicExchange).with(QueueEnum.USER_REGISTER_SEND_MAIL.getRoutingKey());
logger.info("繫結傳送郵件到註冊交換成功");
return binding;
}
/**
* 繫結使用者建立賬戶到使用者註冊主題交換配置
* @return
*/
@Bean
public Binding createAccountBinding(TopicExchange userTopicExchange,Queue createAccountQueue)
{
Binding binding = BindingBuilder.bind(createAccountQueue).to(userTopicExchange).with(QueueEnum.USER_REGISTER_CREATE_ACCOUNT.getRoutingKey());
logger.info("繫結建立賬號到註冊交換成功。");
return binding;
}
}
複製程式碼
我們從上面開始分析。
第一步: 首先我們建立了TopicExchange
訊息佇列物件,使用ExchangeEnum
列舉內的USER_REGISTER_TOPIC_EXCHANGE
型別作為交換名稱。
第二步:我們建立了傳送註冊郵件的佇列sendRegisterMailQueue
,使用QueueEnum
列舉內的型別USER_REGISTER_SEND_MAIL
作為佇列名稱。
第三步:與傳送郵件佇列一致,使用者建立完成後需要初始化賬戶資訊,而createAccountQueue
訊息佇列後續邏輯就是來完成該工作,使用QueueEnum
列舉內的USER_REGISTER_CREATE_ACCOUNT
列舉作為建立賬戶佇列名稱。
第四步:在上面步驟中已經將交換、佇列建立完成,下面就開始將佇列繫結到使用者註冊交換,從而實現註冊使用者訊息佇列訊息消費,sendMailBinding
繫結了QueueEnum.USER_REGISTER_SEND_MAIL
的RoutingKey
配置資訊。
createAccountBinding
繫結了QueueEnum.USER_REGISTER_CREATE_ACCOUNT
的RoutingKey
配置資訊。
到目前為止我們完成了rabbitmq-topic-common
模組的所有配置資訊,下面我們開始編寫使用者註冊訊息消費者模組。
rabbitmq-topic-consumer
我們首先來建立一個子模組命名為rabbitmq-topic-consumer
,在pom.xml
配置檔案內新增rabbitmq-topic-common
模組的引用,如下所示:
....//
<dependencies>
<!--公共模組依賴-->
<dependency>
<groupId>com.hengyu</groupId>
<artifactId>rabbitmq-topic-common</artifactId>
<version>${parent.version}</version>
</dependency>
</dependencies>
....//
複製程式碼
消費者程式入口
下面我們來建立程式啟動類RabbitMqTopicConsumerApplication
,在這裡需要注意,手動配置下掃描路徑@ComponentScan
,啟動類程式碼如下所示:
/**
* 訊息消費者程式啟動入口
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:21:48
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@SpringBootApplication
@ComponentScan(value = "com.hengyu.rabbitmq")
public class RabbitMqTopicConsumerApplication {
/**
* logback
*/
private static Logger logger = LoggerFactory.getLogger(RabbitMqTopicConsumerApplication.class);
/**
* 程式入口
* @param args
*/
public static void main(String[] args)
{
SpringApplication.run(RabbitMqTopicConsumerApplication.class,args);
logger.info("【【【【【Topic佇列訊息Consumer啟動成功】】】】】");
}
}
複製程式碼
手動配置掃描路徑在文章的開始解釋過了,主要目的是為了掃描到RabbitMQConfiguration
配置類內的資訊,讓RabbitAdmin
自動建立配置資訊到server
端。
傳送郵件消費者
傳送郵件訊息費監聽register.mail
訊息佇列資訊,如下所示:
/**
* 傳送使用者註冊成功郵件消費者
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:22:07
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@Component
@RabbitListener(queues = "register.mail")
public class SendMailConsumer
{
/**
* logback
*/
Logger logger = LoggerFactory.getLogger(SendMailConsumer.class);
/**
* 處理訊息
* 傳送使用者註冊成功郵件
* @param userId 使用者編號
*/
@RabbitHandler
public void handler(String userId)
{
logger.info("使用者:{},註冊成功,自動傳送註冊成功郵件.",userId);
//... 傳送註冊成功郵件邏輯
}
}
複製程式碼
在這裡我只是完成了訊息的監聽,具體的業務邏輯可以根據需求進行處理。
建立賬戶消費者
建立使用者賬戶資訊消費者監聽佇列register.account
,程式碼如下所示:
/**
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:22:04
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@Component
@RabbitListener(queues = "register.account")
public class CreateAccountConsumer {
/**
* logback
*/
Logger logger = LoggerFactory.getLogger(CreateAccountConsumer.class);
/**
* 處理訊息
* 建立使用者賬戶
* @param userId 使用者編號
*/
@RabbitHandler
public void handler(String userId)
{
logger.info("使用者:{},註冊成功,自動建立賬戶資訊.",userId);
//... 建立賬戶邏輯
}
}
複製程式碼
建立賬戶,賬戶初始化邏輯都可以在handler
方法進行處理,本章沒有做資料庫複雜的處理,所以沒有過多的邏輯處理在消費者業務內。
rabbitmq-topic-provider
接下來是我們的訊息提供者的模組編寫,我們依然先來建立程式入口類,並新增掃描配置@ComponentScan
路徑,程式碼如下所示:
/**
* 訊息生產者程式啟動入口
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:21:48
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@SpringBootApplication
@ComponentScan(value = "com.hengyu.rabbitmq")
public class RabbitMqTopicProviderApplication {
/**
* logback
*/
private static Logger logger = LoggerFactory.getLogger(RabbitMqTopicProviderApplication.class);
/**
* 程式入口
* @param args
*/
public static void main(String[] args)
{
SpringApplication.run(RabbitMqTopicProviderApplication.class,args);
logger.info("【【【【【Topic佇列訊息Provider啟動成功】】】】】");
}
}
複製程式碼
定義訊息傳送介面
建立QueueMessageService
佇列訊息傳送介面並新增send
方法,如下所示:
/**
* 訊息佇列業務
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:14:50
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
public interface QueueMessageService
{
/**
* 傳送訊息到rabbitmq訊息佇列
* @param message 訊息內容
* @param exchangeEnum 交換配置列舉
* @param routingKey 路由key
* @throws Exception
*/
public void send(Object message, ExchangeEnum exchangeEnum, String routingKey) throws Exception;
}
複製程式碼
send
方法內有三個引數,解析如下:
- message:傳送訊息內容,可以為任意型別,當然本章內僅僅是java.lang.String。
- exchangeEnum:我們自定義的交換列舉型別,方便傳送訊息到指定交換。
- routingKey:傳送訊息時的路由鍵內容,該值採用
TopicEnum
列舉內的topicRouteKey
作為引數值。
下面我們來看看該介面的實現類QueueMessageServiceSupport
內send
方法實現,如下所示:
/**
* 訊息佇列業務邏輯實現
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:14:52
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@Component
public class QueueMessageServiceSupport
implements QueueMessageService
{
/**
* 訊息佇列模板
*/
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void send(Object message, ExchangeEnum exchangeEnum, String routingKey) throws Exception {
//傳送訊息到訊息佇列
rabbitTemplate.convertAndSend(exchangeEnum.getName(),routingKey,message);
}
}
複製程式碼
我們通過RabbitTemplate
例項的convertAndSend
方法將物件型別轉換成JSON
字串後傳送到訊息佇列服務端,RabbitMQ
接受到訊息後根據註冊的消費者並且路由規則篩選後進行訊息轉發,並實現訊息的消費。
執行測試
為了方便測試我們建立一個名為UserService
的實現類,如下所示:
/**
* 使用者業務邏輯
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:22:10
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@Service
public class UserService
{
/**
* 訊息佇列傳送業務邏輯
*/
@Autowired
private QueueMessageService queueMessageService;
/**
* 隨機建立使用者
* 隨機生成使用者uuid編號,傳送到訊息佇列服務端
* @return
* @throws Exception
*/
public String randomCreateUser() throws Exception
{
//使用者編號
String userId = UUID.randomUUID().toString();
//傳送訊息到rabbitmq服務端
queueMessageService.send(userId, ExchangeEnum.USER_REGISTER_TOPIC_EXCHANGE, TopicEnum.USER_REGISTER.getTopicRouteKey());
return userId;
}
}
複製程式碼
該類內新增了一個名為randomCreateUser
隨機建立使用者的方法,通過UUID
隨機生成字串作為使用者的編號進行傳遞給使用者註冊訊息佇列,完成使用者的模擬建立。
編寫測試用例
接下來我們建立RabbitMqTester
測試類來完成隨機使用者建立訊息傳送,測試用例完成簡單的UserService
注入,並呼叫randomCreateUser
方法,如下所示:
/**
* ========================
*
* @author 恆宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:22:10
* 碼雲:http://git.oschina.net/jnyqy
* ========================
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitMqTopicProviderApplication.class)
public class RabbitMqTester
{
/**
* 使用者業務邏輯
*/
@Autowired
private UserService userService;
/**
* 模擬隨機建立使用者 & 傳送訊息到註冊使用者訊息佇列
* @throws Exception
*/
@Test
public void testTopicMessage() throws Exception
{
userService.randomCreateUser();
}
}
複製程式碼
到目前為止,我們的編碼已經完成,下面我們按照下面的步驟啟動測試:
- 啟動
rabbitmq-topic-consumer
訊息消費者模組,並檢視控制檯輸出內容是否正常- 執行
rabbitmq-topic-provider
模組測試用例方法testTopicMessage
- 檢視
rabbitmq-topic-consumer
控制檯輸出內容
最終效果:
2017-12-30 18:39:16.819 INFO 2781 --- [ main] c.h.r.c.RabbitMqTopicConsumerApplication : 【【【【【Topic佇列訊息Consumer啟動成功】】】】】
2017-12-30 18:39:29.376 INFO 2781 --- [cTaskExecutor-1] c.h.r.consumer.CreateAccountConsumer : 使用者:c6ef682d-da2e-4cac-a004-c244ff4c4503,註冊成功,自動建立賬戶資訊.
2017-12-30 18:39:29.376 INFO 2781 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.SendMailConsumer : 使用者:c6ef682d-da2e-4cac-a004-c244ff4c4503,註冊成功,自動傳送註冊成功郵件.
複製程式碼
總結
本章主要講解了TopicExchange
交換型別如何消費佇列訊息,講解了常用到了的特殊字元#
、*
如何匹配,解決了多模組下的佇列配置資訊無法自動建立問題。還有一點需要注意TopicExchange
交換型別在訊息消費時不存在固定的先後順序!!!
本章原始碼已經上傳到碼雲: SpringBoot配套原始碼地址:gitee.com/hengboy/spr… SpringCloud配套原始碼地址:gitee.com/hengboy/spr… SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄 QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄 SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄,感謝閱讀! 歡迎加入QQ技術交流群,共同進步。
![QQ技術交流群](https://i.iter01.com/images/94fa9c4ad859e8cb0699b34ac4dc20567460b52a362528788aa68216090ca55e.png)