如何在優雅地Spring 中實現訊息的傳送和消費

許此一生發表於2018-12-05

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

作者簡介:遼天,阿里巴巴技術專家,Apache RocketMQ 核心控,擁有多年分散式系統研發經驗,對Microservice、Messaging和Storage等領域有深刻理解, 目前專注 RocketMQ 核心最佳化以及 Messaging 生態建設。

透過本文,您將瞭解到:

  • Spring的訊息框架介紹

  • rocketmq-spring-boot具體實現

  • 使用示例

插播一條廣告:本週六下午,Apache RocketMQ 開發者沙龍將來到杭州,歡迎大家到現場,活動詳情請點選“閱讀原文”。

前言

上世紀90年代末,隨著Java EE(Enterprise Edition)的出現,特別是Enterprise Java Beans的使用需要複雜的描述符配置和死板複雜的程式碼實現,增加了廣大開發者的學習曲線和開發成本,由此基於簡單的XML配置和普通Java物件(Plain Old Java Objects)的Spring技術應運而生,依賴注入(Dependency Injection), 控制反轉(Inversion of Control)和麵向切面程式設計(AOP)的技術更加敏捷地解決了傳統Java企業及版本的不足。

隨著Spring的持續演進,基於註解(Annotation)的配置逐漸取代了XML檔案配置, 2014年4月1日,Spring Boot 1.0.0正式釋出,它基於“約定大於配置”(Convention over configuration)這一理念來快速地開發、測試、執行和部署Spring應用,並能透過簡單地與各種啟動器(如 spring-boot-web-starter)結合,讓應用直接以命令列的方式執行,不需再部署到獨立容器中。這種簡便直接快速構建和開發應用的過程,可以使用約定的配置並且簡化部署,受到越來越多的開發者的歡迎。

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
該圖片引自spring cloud stream

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>
  1. 配置檔案類

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

  1. 定義自動載入類

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

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.rocketmq.spring.starter.RocketMQAutoConfiguration

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

  • RocketMQProperties  載入應用屬性配置檔案的處理類;

  • RocketMQTemplate    傳送端使用者傳送訊息的傳送模板類;

  • ListenerContainerConfiguration 容器Bean負責發現和註冊消費端消費實現介面類,這個類要求:由@RocketMQMessageListener註解標註;實現RocketMQListener泛化介面。

  1. 最後具體的RocketMQ相關的封裝
    在傳送端(producer)和消費端(consumer)客戶端分別進行封裝,在當前的實現版本提供了對Spring Messaging介面的相容方式。

3.2. 訊息傳送端實現

  1. 普通傳送端

傳送端的程式碼封裝在RocketMQTemplate POJO中,下圖是傳送端的相關程式碼的呼叫關係圖:

為了與Spring Messaging的傳送模板相容,在RocketMQTemplate整合了AbstractMessageSendingTemplate抽象類,來支援相關的訊息轉換和傳送方法,這些方法最終會代理給doSend()方法;doSend()以及RocoketMQ所特有的一些方法如非同步,單向和順序等方法直接新增到RoketMQTempalte中,這些方法直接代理呼叫到RocketMQ的Producer API來進行訊息的傳送。

  1. 事務訊息傳送端

對於事務訊息的處理,在訊息傳送端進行了部分的擴充套件,參考下圖的呼叫關係類圖:

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已經正確啟動。

  1. 建立例項中所需要的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 
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# 定義要傳送的topicspring.rocketmq.topic=string-topic

傳送端的Java程式碼

import org.apache.rocketmq.spring.starter.core.RocketMQTemplate;
...@SpringBootApplicationpublic 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);
    }
}
  1. 訊息消費端程式碼

消費端的配置檔案application.properties

# 定義name-server地址spring.rocketmq.name-server=localhost:9876# 定義釋出者組名spring.rocketmq.consumer.group=my-customer-group1# 定義要傳送的topicspring.rocketmq.topic=string-topic

消費端的Java程式碼

@SpringBootApplicationpublic 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來編寫最基本的訊息傳送和接收的程式碼,如果需要了解更多的呼叫方式,如: 非同步傳送,物件訊息體,指定tag標籤以及指定事務訊息,請參看github的說明文件和詳細的程式碼。我們後續還會對這些高階功能進行陸續的介紹。

預告:近期還會推出第二篇文章解讀springboot框架下訊息事務的用法。

參考文件

1.Spring Boot features-Messaging

2.Enterprise Integration Pattern-組成簡介

3.Spring Cloud Stream Reference Guide

4. https://dzone.com/articles/creating-custom-springboot-starter-for-twitter4j


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31551794/viewspace-2284247/,如需轉載,請註明出處,否則將追究法律責任。

相關文章