在2018-3-1
日SpringBoot
官方發版了2.0.0.RELEASE
最新版本,新版本完全基於Spring5.0
來構建,JDK
最低支援也從原來的1.6
也改成了1.8
,不再相容1.8
以下的版本,更多新特性請檢視官方文件。
本章目標
基於SpringBoot
整合RabbitMQ
完成訊息延遲消費。
構建專案
注意前言
由於
SpringBoot
的內建掃描機制,我們如果不自動配置掃描路徑,請保持下面rabbitmq-common
模組內的配置可以被SpringBoot
掃描到,否則不會自動建立佇列,控制檯會輸出404的錯誤資訊。
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核心技術 |
007 | SpringBoot核心技術學習目錄 | SpringBoot系統的學習目錄,敬請關注點贊!!! |
我們本章採用2.0.0.RELEASE
版本的SpringBoot
,新增相關的依賴如下所示:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
......
<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>
......
複製程式碼
我們仍然採用多模組的方式來測試佇列的Provider
以及Consumer
。
佇列公共模組
我們先來建立一個名為rabbitmq-common
公共依賴模組(Create New Maven Module)
在公共模組內新增一個QueueEnum
佇列列舉配置,該列舉內配置佇列的Exchange
、QueueName
、RouteKey
等相關內容,如下所示:
package com.hengyu.rabbitmq.lazy.enums;
import lombok.Getter;
/**
* 訊息佇列列舉配置
*
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/3
* Time:下午4:33
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Getter
public enum QueueEnum {
/**
* 訊息通知佇列
*/
MESSAGE_QUEUE("message.center.direct", "message.center.create", "message.center.create"),
/**
* 訊息通知ttl佇列
*/
MESSAGE_TTL_QUEUE("message.center.topic.ttl", "message.center.create.ttl", "message.center.create.ttl");
/**
* 交換名稱
*/
private String exchange;
/**
* 佇列名稱
*/
private String name;
/**
* 路由鍵
*/
private String routeKey;
QueueEnum(String exchange, String name, String routeKey) {
this.exchange = exchange;
this.name = name;
this.routeKey = routeKey;
}
}
複製程式碼
可以看到MESSAGE_QUEUE
佇列配置跟我們之前章節的配置一樣,而我們另外新建立了一個字尾為ttl
的訊息佇列配置。我們採用的這種方式是RabbitMQ
訊息佇列其中一種的延遲消費模組,通過配置佇列訊息過期後轉發的形式。
這種模式比較簡單,我們需要將訊息先傳送到
ttl
延遲佇列內,當訊息到達過期時間後會自動轉發到ttl
佇列內配置的轉發Exchange
以及RouteKey
繫結的佇列內完成訊息消費。
下面我們來模擬訊息通知
的延遲消費場景,先來建立一個名為MessageRabbitMqConfiguration
的佇列配置類,該配置類內新增訊息通知佇列
配置以及訊息通過延遲佇列
配置,如下所示:
/**
* 訊息通知 - 訊息佇列配置資訊
*
* @author:恆宇少年 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/3
* Time:下午4:32
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Configuration
public class MessageRabbitMqConfiguration {
/**
* 訊息中心實際消費佇列交換配置
*
* @return
*/
@Bean
DirectExchange messageDirect() {
return (DirectExchange) ExchangeBuilder
.directExchange(QueueEnum.MESSAGE_QUEUE.getExchange())
.durable(true)
.build();
}
/**
* 訊息中心延遲消費交換配置
*
* @return
*/
@Bean
DirectExchange messageTtlDirect() {
return (DirectExchange) ExchangeBuilder
.directExchange(QueueEnum.MESSAGE_TTL_QUEUE.getExchange())
.durable(true)
.build();
}
/**
* 訊息中心實際消費佇列配置
*
* @return
*/
@Bean
public Queue messageQueue() {
return new Queue(QueueEnum.MESSAGE_QUEUE.getName());
}
/**
* 訊息中心TTL佇列
*
* @return
*/
@Bean
Queue messageTtlQueue() {
return QueueBuilder
.durable(QueueEnum.MESSAGE_TTL_QUEUE.getName())
// 配置到期後轉發的交換
.withArgument("x-dead-letter-exchange", QueueEnum.MESSAGE_QUEUE.getExchange())
// 配置到期後轉發的路由鍵
.withArgument("x-dead-letter-routing-key", QueueEnum.MESSAGE_QUEUE.getRouteKey())
.build();
}
/**
* 訊息中心實際訊息交換與佇列繫結
*
* @param messageDirect 訊息中心交換配置
* @param messageQueue 訊息中心佇列
* @return
*/
@Bean
Binding messageBinding(DirectExchange messageDirect, Queue messageQueue) {
return BindingBuilder
.bind(messageQueue)
.to(messageDirect)
.with(QueueEnum.MESSAGE_QUEUE.getRouteKey());
}
/**
* 訊息中心TTL繫結實際訊息中心實際消費交換機
*
* @param messageTtlQueue
* @param messageTtlDirect
* @return
*/
@Bean
public Binding messageTtlBinding(Queue messageTtlQueue, DirectExchange messageTtlDirect) {
return BindingBuilder
.bind(messageTtlQueue)
.to(messageTtlDirect)
.with(QueueEnum.MESSAGE_TTL_QUEUE.getRouteKey());
}
}
複製程式碼
我們宣告瞭訊息通知佇列
的相關Exchange
、Queue
、Binding
等配置,將message.center.create
佇列通過路由鍵message.center.create
繫結到了message.center.direct
交換上。
除此之外,我們還新增了訊息通知延遲佇列
的Exchange
、Queue
、Binding
等配置,將message.center.create.ttl
佇列通過message.center.create.ttl
路由鍵繫結到了message.center.topic.ttl
交換上。
我們仔細來看看messageTtlQueue
延遲佇列的配置,跟messageQueue
佇列配置不同的地方這裡多出了x-dead-letter-exchange
、x-dead-letter-routing-key
兩個引數,而這兩個引數就是配置延遲佇列過期後轉發的Exchange
、RouteKey
,只要在建立佇列時對應新增了這兩個引數,在RabbitMQ
管理平臺看到的佇列配置就不僅是單純的Direct
型別的佇列型別,如下圖所示:
在上圖內我們可以看到message.center.create.ttl
佇列多出了DLX
、DLK
的配置,這就是RabbitMQ
內死信交換
的標誌。
滿足死信交換
的條件,在官方文件中表示:
Messages from a queue can be 'dead-lettered'; that is, republished to another exchange when any of the following events occur:
The message is rejected (basic.reject or basic.nack) with requeue=false, The TTL for the message expires; or The queue length limit is exceeded.
- 該訊息被拒絕(basic.reject或 basic.nack),requeue = false
- 訊息的TTL過期
- 佇列長度限制已超出 官方文件地址
我們需要滿足上面的其中一種方式就可以了,我們採用滿足第二個條件,採用過期的方式。
佇列訊息提供者
我們再來建立一個名為rabbitmq-lazy-provider
的模組(Create New Maven Module),並且在pom.xml
配置檔案內新增rabbitmq-common
模組的依賴,如下所示:
<!--新增公共模組依賴-->
<dependency>
<groupId>com.hengyu</groupId>
<artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
複製程式碼
配置佇列
在resource
下建立一個名為application.yml
的配置檔案,在該配置檔案內新增如下配置資訊:
spring:
#rabbitmq訊息佇列配置資訊
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /hengboy
publisher-confirms: true
複製程式碼
訊息提供者類
接下來我們來建立名為MessageProvider
訊息提供者類,用來傳送訊息內容到訊息通知延遲佇列,程式碼如下所示:
/**
* 訊息通知 - 提供者
*
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/3
* Time:下午4:40
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Component
public class MessageProvider {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(MessageProvider.class);
/**
* RabbitMQ 模版訊息實現類
*/
@Autowired
private AmqpTemplate rabbitMqTemplate;
/**
* 傳送延遲訊息
*
* @param messageContent 訊息內容
* @param exchange 佇列交換
* @param routerKey 佇列交換繫結的路由鍵
* @param delayTimes 延遲時長,單位:毫秒
*/
public void sendMessage(Object messageContent, String exchange, String routerKey, final long delayTimes) {
if (!StringUtils.isEmpty(exchange)) {
logger.info("延遲:{}毫秒寫入訊息佇列:{},訊息內容:{}", delayTimes, routerKey, JSON.toJSONString(messageContent));
// 執行傳送訊息到指定佇列
rabbitMqTemplate.convertAndSend(exchange, routerKey, messageContent, message -> {
// 設定延遲毫秒值
message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
return message;
});
} else {
logger.error("未找到佇列訊息:{},所屬的交換機", exchange);
}
}
}
複製程式碼
由於我們在 pom.xml
配置檔案內新增了RabbitMQ
相關的依賴並且在上面application.yml
檔案內新增了對應的配置,SpringBoot
為我們自動例項化了AmqpTemplate
,該例項可以傳送任何型別的訊息到指定佇列。
我們採用convertAndSend
方法,將訊息內容傳送到指定Exchange
、RouterKey
佇列,並且通過setExpiration
方法設定過期時間,單位:毫秒。
編寫傳送測試
我們在test
目錄下建立一個測試類,如下所示:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitMqLazyProviderApplication.class)
public class RabbitMqLazyProviderApplicationTests {
/**
* 訊息佇列提供者
*/
@Autowired
private MessageProvider messageProvider;
/**
* 測試延遲訊息消費
*/
@Test
public void testLazy() {
// 測試延遲10秒
messageProvider.sendMessage("測試延遲消費,寫入時間:" + new Date(),
QueueEnum.MESSAGE_TTL_QUEUE.getExchange(),
QueueEnum.MESSAGE_TTL_QUEUE.getRouteKey(),
10000);
}
}
複製程式碼
注意:
@SpringBootTest
註解內新增了classes
入口類的配置,因為我們是模組建立的專案並不是預設建立的SpringBoot
專案,這裡需要配置入口程式類才可以執行測試。
在測試類我們注入了MessageProvider
訊息提供者,呼叫sendMessage
方法傳送訊息到訊息通知延遲佇列
,並且設定延遲的時間為10秒
,這裡衡量傳送到指定佇列的標準是要看MessageRabbitMqConfiguration
配置類內的相關Binding
配置,通過Exchange
、RouterKey
值進行傳送到指定的佇列。
到目前為止我們的rabbitmq-lazy-provider
訊息提供模組已經編寫完成了,下面我們來看看訊息消費者模組。
佇列訊息消費者
我們再來建立一個名為rabbitmq-lazy-consumer
的模組(Create New Maven Module),同樣需要在pom.xml
配置檔案內新增rabbitmq-common
模組的依賴,如下所示:
<!--新增公共模組依賴-->
<dependency>
<groupId>com.hengyu</groupId>
<artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
複製程式碼
當然同樣需要在resource
下建立application.yml
並新增訊息佇列的相關配置,程式碼就不貼出來了,可以直接從rabbitmq-lazy-provider
模組中複製application.yml
檔案到當前模組內。
訊息消費者類
接下來建立一個名為MessageConsumer
的消費者類,該類需要監聽訊息通知佇列
,程式碼如下所示:
/**
* 訊息通知 - 消費者
*
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/3
* Time:下午5:00
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@Component
@RabbitListener(queues = "message.center.create")
public class MessageConsumer {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(MessageConsumer.class);
@RabbitHandler
public void handler(String content) {
logger.info("消費內容:{}", content);
}
}
複製程式碼
在@RabbitListener
註解內配置了監聽的佇列,這裡配置內容是QueueEnum
列舉內的queueName
屬性值,當然如果你採用常量的方式在註解屬性上是直接可以使用的,列舉不支援這種配置,這裡只能把QueueName
字串配置到queues
屬性上了。
由於我們在訊息傳送時採用字串的形式傳送訊息內容,這裡在@RabbitHandler
處理方法的引數內要保持資料型別一致!
消費者入口類
我們為消費者模組新增一個入口程式類,用於啟動消費者,程式碼如下所示:
/**
* 【第四十六章:SpringBoot & RabbitMQ完成訊息延遲消費】
* 佇列消費者模組 - 入口程式類
*
* @author:於起宇 <br/>
* ===============================
* Created with IDEA.
* Date:2018/3/3
* Time:下午4:55
* 簡書:http://www.jianshu.com/u/092df3f77bca
* ================================
*/
@SpringBootApplication
public class RabbitMqLazyConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitMqLazyConsumerApplication.class, args);
}
}
複製程式碼
測試
我們的程式碼已經編寫完畢,下面來測試下是否完成了我們預想的效果,步驟如下所示:
1. 啟動消費者模組
2. 執行RabbitMqLazyProviderApplicationTests.testLazy()方法進行傳送訊息到通知延遲佇列
3. 檢視消費者模組控制檯輸出內容
複製程式碼
我們可以在消費者模組控制檯看到輸出內容:
2018-03-04 10:10:34.765 INFO 70486 --- [cTaskExecutor-1] c.h.r.lazy.consumer.MessageConsumer : 消費內容:測試延遲消費,寫入時間:Sun Mar 04 10:10:24 CST 2018
複製程式碼
我們在提供者測試方法傳送訊息的時間為10:10:24
,而真正消費的時間則為10:10:34
,與我們預計的一樣,訊息延遲了10秒
後去執行消費。
總結
終上所述我們完成了訊息佇列的延遲消費
,採用死信
方式,通過訊息過期方式觸發,在實際專案研發過程中,延遲消費還是很有必要的,可以省去一些定時任務的配置。
本章原始碼已經上傳到碼雲: SpringBoot配套原始碼地址:gitee.com/hengboy/spr… SpringCloud配套原始碼地址:gitee.com/hengboy/spr… SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄 QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄 SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄,感謝閱讀!