阿里技術專家講解:優雅地SpringBoot中實現消息的發送和消費實踐

Apache RocketMQ是業界知名的分布式消息和流處理中間件,簡單地理解,它由Broker服務器和客戶端兩部分組成:

其中客戶端一個是消息發布者客戶端(Producer),它負責向Broker服務器發送消息; 另外一個是消息的消費者客戶端(Consumer),多個消費者可以組成一個消費組,來訂閱和拉取消費Broker服務器上存儲的消息。

為了利用Spring Boot的快速開發和讓用戶能夠更靈活地使用RocketMQ消息客戶端,Apache RocketMQ社區推出了spring-boot-starter實現。隨著分布式事務消息功能在RocketMQ 4.3.0版本的發布,近期升級了相關的spring-boot代碼,通過注解方式支持分布式事務的回查和事務消息的發送。

本文將對當前的設計實現做一個簡單的介紹,讀者可以通過本文了解將RocketMQ Client端集成為spring-boot-starter框架的開發細節,然后通過一個簡單的示例來一步一步的講解如何使用這個spring-boot-starter工具包來配置,發送和消費RocketMQ消息。

Spring 中的消息框架

順便在這里討論一下在Spring中關于消息的兩個主要的框架,即Spring Messaging和Spring Cloud Stream。它們都能夠與Spring Boot整合并提供了一些參考的實現。和所有的實現框架一樣,消息框架的目的是實現輕量級的消息驅動的微服務,可以有效地簡化開發人員對消息中間件的使用復雜度,讓系統開發人員可以有更多的精力關注于核心業務邏輯的處理。

2.1 Spring Messaging

Spring Messaging是Spring Framework 4中添加的模塊,是Spring與消息系統集成的一個擴展性的支持。它實現了從基于JmsTemplate的簡單的使用JMS接口到異步接收消息的一整套完整的基礎架構,Spring AMQP提供了該協議所要求的類似的功能集。 在與Spring Boot的集成后,它擁有了自動配置能力,能夠在測試和運行時與相應的消息傳遞系統進行集成。

單純對于客戶端而言,Spring Messaging提供了一套抽象的API或者說是約定的標準,對消息發送端和消息接收端的模式進行規定,不同的消息中間件提供商可以在這個模式下提供自己的Spring實現:在消息發送端需要實現的是一個XXXTemplate形式的Java Bean,結合Spring Boot的自動化配置選項提供多個不同的發送消息方法;在消息的消費端是一個XXXMessageListener接口(實現方式通常會使用一個注解來聲明一個消息驅動的POJO),提供回調方法來監聽和消費消息,這個接口同樣可以使用Spring Boot的自動化選項和一些定制化的屬性。

如果有興趣深入的了解Spring Messaging及針對不同的消息產品的使用,推薦閱讀這個文件。參考Spring Messaging的既有實現,RocketMQ的spring-boot-starter中遵循了相關的設計模式并結合RocketMQ自身的功能特點提供了相應的API(如,順序,異步和事務半消息等)。

2.2 Spring Cloud Stream

Spring Cloud Stream結合了Spring Integration的注解和功能,它的應用模型如下:

Spring Cloud Stream框架中提供一個獨立的應用內核,它通過輸入(@Input)和輸出(@Output)通道與外部世界進行通信,消息源端(Source)通過輸入通道發送消息,消費目標端(Sink)通過監聽輸出通道來獲取消費的消息。這些通道通過專用的Binder實現與外部代理連接。開發人員的代碼只需要針對應用內核提供的固定的接口和注解方式進行編程,而不需要關心運行時具體的Binder綁定的消息中間件。在運行時,Spring Cloud Stream能夠自動探測并使用在classpath下找到的Binder。

這樣開發人員可以輕松地在相同的代碼中使用不同類型的中間件:僅僅需要在構建時包含進不同的Binder。在更加復雜的使用場景中,也可以在應用中打包多個Binder并讓它自己選擇Binder,甚至在運行時為不同的通道使用不同的Binder。

Binder抽象使得Spring Cloud Stream應用可以靈活的連接到中間件,加之Spring Cloud Stream使用利用了Spring Boot的靈活配置配置能力,這樣的配置可以通過外部配置的屬性和Spring Boo支持的任何形式來提供(包括應用啟動參數、環境變量和application.yml或者application.properties文件),部署人員可以在運行時動態選擇通道連接destination(例如,Kafka的topic或者RabbitMQ的exchange)。

Binder SPI的方式來讓消息中間件產品使用可擴展的API來編寫相應的Binder,并集成到Spring Cloud Steam環境,目前RocketMQ還沒有提供相關的Binder,我們計劃在下一步將完善這一功能,也希望社區里有這方面經驗的同學積極嘗試,貢獻PR或建議。

spring-boot-starter的實現

在開始的時候我們已經知道,spring boot starter構造的啟動器對于使用者是非常方便的,使用者只要在pom.xml引入starter的依賴定義,相應的編譯,運行和部署功能就全部自動引入。因此常用的開源組件都會為Spring的用戶提供一個spring-boot-starter封裝給開發者,讓開發者非常方便集成和使用,這里我們詳細的介紹一下RocketMQ(客戶端)的starter實現過程。

3.1. spring-boot-starter的實現步驟

對于一個spring-boot-starter實現需要包含如下幾個部分:

1、在pom.xml的定義

定義最終要生成的starter組件信息

<groupId>org.apache.rocketmq</groupId>

<artifactId>spring-boot-starter-rocketmq</artifactId>

<version>1.0.0-SNAPSHOT</version>

定義依賴包,

它分為兩個部分: A、Spring自身的依賴包; B、RocketMQ的依賴包

<dependencies>

? ?<!-- spring-boot-start internal depdencies -->

? ?<dependency>

? ? ? ?<groupId>org.springframework.boot</groupId>

? ? ? ?<artifactId>spring-boot-starter</artifactId>

? ?</dependency> ? ? ? ?

? ?<dependency>

? ? ? ?<groupId>org.springframework.boot</groupId>

? ? ? ?<artifactId>spring-boot-starter-test</artifactId>

? ? ? ?<scope>test</scope>

? ?</dependency>

? ?<!-- rocketmq dependencies -->

? ?<dependency>

? ? ? ?<groupId>org.apache.rocketmq</groupId>

? ? ? ?<artifactId>rocketmq-client</artifactId>

? ? ? ?<version>${rocketmq-version}</version>

? ?</dependency>

</dependencies> ? ?

? ?<dependencyManagement>

? ?<dependencies>

? ? ? ?<!-- spring-boot-start parent depdency definition -->

? ? ? ?<dependency>

? ? ? ? ? ?<groupId>org.springframework.boot</groupId>

? ? ? ? ? ?<artifactId>spring-boot-starter-parent</artifactId>

? ? ? ? ? ?<version>${spring.boot.version}</version>

? ? ? ? ? ?<type>pom</type>

? ? ? ? ? ?<scope>import</scope>

? ? ? ?</dependency>

? ?</dependencies>

</dependencyManagement>

2、配置文件類

定義應用屬性配置文件類RocketMQProperties,這個Bean定義一組默認的屬性值。用戶在使用最終的starter時,可以根據這個類定義的屬性來修改取值,當然不是直接修改這個類的配置,而是spring-boot應用中對應的配置文件:src/main/resources/application.properties.

3、定義自動加載類

定義 src/resources/META-INF/spring.factories文件中的自動加載類, 其目的是讓spring boot更具文中中所指定的自動化配置類來自動初始化相關的Bean,Component或Service,它的內容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.apache.rocketmq.spring.starter.RocketMQAutoConfiguration

在RocketMQAutoConfiguration類的具體實現中,定義開放給用戶直接使用的Bean對象. 包括:

1、RocketMQProperties 加載應用屬性配置文件的處理類; 2、RocketMQTemplate 發送端用戶發送消息的發送模板類; 3、ListenerContainerConfiguration 容器Bean負責發現和注冊消費端消費實現接口類,這個類要求:由@RocketMQMessageListener注解標注;實現RocketMQListener泛化接口。

最后具體的RocketMQ相關的封裝

在發送端(producer)和消費端(consumer)客戶端分別進行封裝,在當前的實現版本提供了對Spring Messaging接口的兼容方式。

3.2. 消息發送端實現

1、普通發送端

發送端的代碼封裝在RocketMQTemplate POJO中,下圖是發送端的相關代碼的調用關系圖:


為了與Spring Messaging的發送模板兼容,在RocketMQTemplate集成了AbstractMessageSendingTemplate抽象類,來支持相關的消息轉換和發送方法,這些方法最終會代理給doSend()方法;doSend()以及RocoketMQ所特有的一些方法如異步,單向和順序等方法直接添加到RoketMQTempalte中,這些方法直接代理調用到RocketMQ的Producer API來進行消息的發送。

2、事務消息發送端

對于事務消息的處理,在消息發送端進行了部分的擴展,參考下圖的調用關系類圖:

RocketMQTemplate里加入了一個發送事務消息的方法sendMessageInTransaction(), 并且最終這個方法會代理到RocketMQ的TransactionProducer進行調用,在這個Producer上會注冊其關聯的TransactionListener實現類,以便在發送消息后能夠對TransactionListener里的方法實現進行調用。

3.3. 消息消費端實現

在消費端Spring-Boot應用啟動后,會掃描所有包含@RocketMQMessageListener注解的類(這些類需要集成RocketMQListener接口,并實現onMessage()方法),這個Listener會一對一的被放置到DefaultRocketMQListenerContainer容器對象中,容器對象會根據消費的方式(并發或順序),將RocketMQListener封裝到具體的RocketMQ內部的并發或者順序接口實現。在容器中創建RocketMQ Consumer對象,啟動并監聽定制的Topic消息,如果有消費消息,則回調到Listener的onMessage()方法。

使用示例

上面的一章介紹了RocketMQ在spring-boot-starter方式的實現,這里通過一個最簡單的消息發送和消費的例子來介紹如何使這個rocketmq-spring-boot-starter。

4.1 RocketMQ服務端的準備

1、啟動NameServer和Broker

要驗證RocketMQ的Spring-Boot客戶端,首先要確保RocketMQ服務正確的下載并啟動。可以參考RocketMQ主站的快速開始來進行操作。確保啟動NameServer和Broker已經正確啟動。

2、創建實例中所需要的Topics

在執行啟動命令的目錄下執行下面的命令行操作

bash bin/mqadmin updateTopic -c DefaultCluster -t string-topic

4.2. 編譯rocketmq-spring-boot-starter

目前的spring-boot-starter依賴還沒有提交的Maven的中心庫,用戶使用前需要自行下載git源碼,然后執行mvn clean install 安裝到本地倉庫。

git clone https://github.com/apache/rocketmq-externals.git

cd rocketmq-spring-boot-starter

mvn clean install

4.3. 編寫客戶端代碼

用戶如果使用它,需要在消息的發布和消費客戶端的maven配置文件pom.xml中添加如下的依賴:

<properties>

<spring-boot-starter-rocketmq-version>1.0.0-SNAPSHOT</spring-boot-starter-rocketmq-version>

</properties>

<dependency>

? <groupId>org.apache.rocketmq</groupId>

? <artifactId>spring-boot-starter-rocketmq</artifactId>

? <version>${spring-boot-starter-rocketmq-version}</version>

</dependency>

屬性spring-boot-starter-rocketmq-version的取值為:1.0.0-SNAPSHOT, 這與上一步驟中執行安裝到本地倉庫的版本一致。

1、消息發送端的代碼

發送端的配置文件application.properties

# 定義name-server地址

spring.rocketmq.name-server=localhost:9876

# 定義發布者組名

spring.rocketmq.producer.group=my-group1

# 定義要發送的topic

spring.rocketmq.topic=string-topic

發送端的Java代碼

@SpringBootApplication

public class ProducerApplication implements CommandLineRunner {

? ?// 聲明并引用RocketMQTemplate

? ?@Resource

? ?private RocketMQTemplate rocketMQTemplate;

? ?// 使用application.properties里定義的topic屬性

? ?@Value("${spring.rocketmq.springTopic}")

? ?private String springTopic;

? ?public static void main(String[] args){

? ? ? ?SpringApplication.run(ProducerApplication.class, args);

? ?}

? ?public void run(String... args) throws Exception {

? ? ? ?// 以同步的方式發送字符串消息給指定的topic

? ? ? ?SendResult sendResult = rocketMQTemplate.syncSend(springTopic, "Hello, World!");

? ? ? ?// 打印發送結果信息

? ? ? ?System.out.printf("string-topic syncSend1 sendResult=%s %n", sendResult);

? ?}

}

2、消息消費端代碼

消費端的配置文件application.properties

# 定義name-server地址

spring.rocketmq.name-server=localhost:9876

# 定義發布者組名

spring.rocketmq.consumer.group=my-customer-group1

# 定義要發送的topic

spring.rocketmq.topic=string-topic

消費端的Java代碼

@SpringBootApplication

public class ConsumerApplication {

? ?public static void main(String[] args) {

? ? ? ?SpringApplication.run(ConsumerApplication.class, args);

? ?}

}

// 聲明消費消息的類,并在注解中指定,相關的消費信息

@Service

@RocketMQMessageListener(topic = "${spring.rocketmq.topic}", consumerGroup = "${spring.rocketmq.consumer.group}")

class StringConsumer implements RocketMQListener <string>{

? ?@Override

? ?public void onMessage(String message) {

? ? ? ?System.out.printf("------- StringConsumer received: %s %f", message);

? ?}

}

這里只是簡單的介紹了使用spring-boot來編寫最基本的消息發送和接收的代碼

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,925評論 6 342
  • 本文參考了:http://blog.didispace.com/springcloud7/http://blog....
    WeiminSun閱讀 7,242評論 0 23
  • 前言 在微服務架構的系統中,我們通常會使用輕量級的消息代理來構建一個共用的消息主題讓系統中所有微服務實例都連接上來...
    Chandler_玨瑜閱讀 6,607評論 2 39
  • 總會有人想要追求完美,追求完美并沒有什么不好的,但是會讓自己很累. 完美主義者的最大特點是追求完美,而這種欲望是建...
    小饅頭0601閱讀 180評論 0 0