一、 概述
本文我們來學(xué)習(xí) Spring Cloud Alibaba 提供的 Spring Cloud Stream RocketMQ 組件,基于 Spring Cloud Stream 的編程模型,接入 RocketMQ 作為消息中間件,實現(xiàn)消息驅(qū)動的微服務(wù)。
在開始本文之前,要對 RocketMQ 進行簡單的學(xué)習(xí)。可以閱讀本系列前面的[RocketMQ]文章,在本機搭建一個 RocketMQ 服務(wù)。
二、Spring Cloud Stream 介紹
Spring Cloud Stream 是一個用于構(gòu)建基于消息的微服務(wù)應(yīng)用框架,使用 Spring Integration 與 Broker 進行連接。
一般來說,消息隊列中間件都有一個 Broker Server(代理服務(wù)器),消息中轉(zhuǎn)角色,負責(zé)存儲消息、轉(zhuǎn)發(fā)消息。
例如說在 RocketMQ 中,Broker 負責(zé)接收從生產(chǎn)者發(fā)送來的消息并存儲、同時為消費者的拉取請求作準(zhǔn)備。另外,Broker 也存儲消息相關(guān)的元數(shù)據(jù),包括消費者組、消費進度偏移和主題和隊列消息等。
Spring Cloud Stream 提供了消息中間件的統(tǒng)一抽象,推出了 publish-subscribe、consumer groups、partition 這些統(tǒng)一的概念。
Spring Cloud Stream 內(nèi)部有兩個概念:Binder 和 Binding。
① Binder,跟消息中間件集成的組件,用來創(chuàng)建對應(yīng)的 Binding。各消息中間件都有自己的 Binder 具體實現(xiàn)。
public interface Binder<T,
C extends ConsumerProperties, // 消費者配置
P extends ProducerProperties> { // 生產(chǎn)者配置
// 創(chuàng)建消費者的 Binding
Binding<T> bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
// 創(chuàng)建生產(chǎn)者的 Binding
Binding<T> bindProducer(String name, T outboundBindTarget, P producerProperties);
}
- Kafka 實現(xiàn)了 KafkaMessageChannelBinder
- RabbitMQ 實現(xiàn)了 RabbitMessageChannelBinder
- RocketMQ 實現(xiàn)了 RocketMQMessageChannelBinder
② Binding,包括 Input Binding 和 Output Binding。Binding 在消息中間件與應(yīng)用程序提供的 Provider 和 Consumer 之間提供了一個橋梁,實現(xiàn)了開發(fā)者只需使用應(yīng)用程序的 Provider 或 Consumer 生產(chǎn)或消費數(shù)據(jù)即可,屏蔽了開發(fā)者與底層消息中間件的接觸。
最終整體交互如下圖所示:三、 快速入門
本小節(jié),我們一起來快速入門下,會創(chuàng)建 2 個項目,分別作為生產(chǎn)者和消費者。最終項目如下圖所示:3.1 搭建生產(chǎn)者
3.1.1 引入依賴
創(chuàng)建 [pom.xml
]文件中,引入 Spring Cloud Alibaba RocketMQ 相關(guān)依賴。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.erbadagang.springcloud.stream</groupId>
<artifactId>sc-stream-rocketmq-producer</artifactId>
<version>0.0.1</version>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
<spring.cloud.alibaba.version>2.2.0.RELEASE</spring.cloud.alibaba.version>
</properties>
<!--
引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,進行依賴版本的管理,防止不兼容。
在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 開發(fā)團隊推薦了三者的依賴關(guān)系
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 引入 SpringMVC 相關(guān)依賴,并實現(xiàn)對其的自動配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Spring Cloud Alibaba Stream RocketMQ 相關(guān)依賴,將 RocketMQ 作為消息隊列,并實現(xiàn)對其的自動配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
</dependencies>
</project>
3.1.2 配置文件
[application.yaml
]
spring:
application:
name: stream-rocketmq-producer-application
cloud:
# Spring Cloud Stream 配置項,對應(yīng) BindingServiceProperties 類
stream:
# Binding 配置項,對應(yīng) BindingProperties Map
bindings:
erbadagang-output:
destination: ERBADAGANG-TOPIC-01 # 目的地。這里使用 RocketMQ Topic
content-type: application/json # 內(nèi)容格式。這里使用 JSON
trek-output:
destination: TREK-TOPIC-01 # 目的地。這里使用 RocketMQ Topic
content-type: application/json # 內(nèi)容格式。這里使用 JSON
# Spring Cloud Stream RocketMQ 配置項
rocketmq:
# RocketMQ Binder 配置項,對應(yīng) RocketMQBinderConfigurationProperties 類
binder:
name-server: 101.133.227.13:9876 # RocketMQ Namesrv 地址
# RocketMQ 自定義 Binding 配置項,對應(yīng) RocketMQBindingProperties Map
bindings:
erbadagang-output:
# RocketMQ Producer 配置項,對應(yīng) RocketMQProducerProperties 類
producer:
group: test # 生產(chǎn)者分組
sync: true # 是否同步發(fā)送消息,默認為 false 異步。
server:
port: 18080
同時我們設(shè)置了2個binding,模擬2個topic情形。
① spring.cloud.stream
為 Spring Cloud Stream 配置項,對應(yīng) BindingServiceProperties 類。配置的層級有點深,我們一層一層來看看。
② spring.cloud.stream.bindings
為 Binding 配置項,對應(yīng) BindingProperties Map。其中,key 為 Binding 的名字。要注意,雖然說 Binding 分成 Input 和 Output 兩種類型,但是在配置項中并不會體現(xiàn)出來,而是要在稍后搭配 @Input
還是 @Output
注解,才會有具體的區(qū)分。
這里,我們配置了一個名字為 erbadagang-output
和 trek-output
的 Binding。從命名上,我們的意圖是想作為 Output Binding,用于生產(chǎn)者發(fā)送消息。
-
destination
:目的地。在 RocketMQ 中,使用 Topic 作為目的地。這里我們設(shè)置為ERBADAGANG-TOPIC-01
。主題(Topic):表示一類消息的集合,每個主題包含若干條消息,每條消息只能屬于一個主題,是 RocketMQ 進行消息訂閱的基本單位。
content-type
:內(nèi)容格式。這里使用 JSON 格式,因為稍后我們將發(fā)送消息的類型為 POJO,使用 JSON 進行序列化。
③ spring.cloud.stream.rocketmq
為 Spring Cloud Stream RocketMQ 配置項。
④ spring.cloud.stream.rocketmq.binder
為 RocketMQ Binder 配置項,對應(yīng) RocketMQBinderConfigurationProperties 類。
-
name-server
:RocketMQ Namesrv 地址。名字服務(wù)(Name Server):名稱服務(wù)充當(dāng)路由消息的提供者。生產(chǎn)者或消費者能夠通過名字服務(wù)查找各主題相應(yīng)的 Broker IP 列表。多個 Namesrv 實例組成集群,但相互獨立,沒有信息交換。
⑤ spring.cloud.stream.rocketmq.bindings
為 RocketMQ 自定義 Binding 配置項,用于對通用的spring.cloud.stream.bindings
配置項的增強,實現(xiàn) RocketMQ Binding 獨特的配置。該配置項對應(yīng) RocketMQBindingProperties Map,其中 key 為 Binding 的名字,需要對應(yīng)上噢。
這里,我們對名字為 erbadagang-output
的 Binding 進行增強,進行 Producer 的配置。其中,producer
為 RocketMQ Producer 配置項,對應(yīng) RocketMQProducerProperties 類。
-
group
:生產(chǎn)者分組。生產(chǎn)者組(Producer Group):同一類 Producer 的集合,這類 Producer 發(fā)送同一類消息且發(fā)送邏輯一致。如果發(fā)送的是事務(wù)消息且原始生產(chǎn)者在發(fā)送之后崩潰,則 Broker 服務(wù)器會聯(lián)系同一生產(chǎn)者組的其他生產(chǎn)者實例以提交或回溯消費。
-
sync
:是否同步發(fā)送消息,默認為false
異步。一般業(yè)務(wù)場景下,使用同步發(fā)送消息較多,所以這里我們設(shè)置為true
同步消息。使用 RocketMQ 發(fā)送三種類型的消息:同步消息(sync)、異步消息(async)和單向消息(oneway)。其中前兩種消息是可靠的,因為會有發(fā)送是否成功的應(yīng)答。
MySource.java
package com.erbadagang.springcloudalibaba.stream.rocketmq.producer.message;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
public interface MySource {
@Output("erbadagang-output")
MessageChannel erbadagangOutput();
@Output("trek-output")
MessageChannel trekOutput();
}
這里,我們通過 @Output
注解,聲明了一個名字為 erbadagang-output
的 Output Binding。注意,這個名字要和我們配置文件中的 spring.cloud.stream.bindings
配置項對應(yīng)上。
同時,@Output
注解的方法的返回結(jié)果為 MessageChannel 類型,可以使用它發(fā)送消息。
3.1.4 Demo01Message
創(chuàng)建 [Demo01Message]類,示例 Message 消息。代碼如下:
public class Demo01Message {
/**
* 編號
*/
private Integer id;
// ... 省略 setter/getter/toString 方法
}
3.1.5 Demo01Controller
創(chuàng)建 [Demo01Controller]類,提供發(fā)送消息的 HTTP 接口。代碼如下:
package com.erbadagang.springcloudalibaba.stream.rocketmq.producer.controller;
import com.erbadagang.springcloudalibaba.stream.rocketmq.producer.message.Demo01Message;
import com.erbadagang.springcloudalibaba.stream.rocketmq.producer.message.MySource;
import org.apache.rocketmq.common.message.MessageConst;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
@RestController
@RequestMapping("/demo01")
public class Demo01Controller {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private MySource mySource;//<1>
@GetMapping("/send")
public boolean send() {
// <2>創(chuàng)建 Message
Demo01Message message = new Demo01Message()
.setId(new Random().nextInt());
// <3>創(chuàng)建 Spring Message 對象
Message<Demo01Message> springMessage = MessageBuilder.withPayload(message)
.build();
// <4>發(fā)送消息
return mySource.erbadagangOutput().send(springMessage);
}
@GetMapping("/sendTrek")
public boolean sendTrek() {
// <2>創(chuàng)建 Message
Demo01Message message = new Demo01Message()
.setId(new Random().nextInt());
// <3>創(chuàng)建 Spring Message 對象
Message<Demo01Message> springMessage = MessageBuilder.withPayload(message)
.build();
// <4>發(fā)送消息
return mySource.trekOutput().send(springMessage);
}
@GetMapping("/send_delay")
public boolean sendDelay() {
// 創(chuàng)建 Message
Demo01Message message = new Demo01Message()
.setId(new Random().nextInt());
// 創(chuàng)建 Spring Message 對象
Message<Demo01Message> springMessage = MessageBuilder.withPayload(message)
.setHeader(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "3") // 設(shè)置延遲級別為 3,10 秒后消費。
.build();
// 發(fā)送消息
boolean sendResult = mySource.erbadagangOutput().send(springMessage);
logger.info("[sendDelay][發(fā)送消息完成, 結(jié)果 = {}]", sendResult);
return sendResult;
}
@GetMapping("/send_tag")
public boolean sendTag() {
for (String tag : new String[]{"trek", "specialized", "look"}) {
// 創(chuàng)建 Message
Demo01Message message = new Demo01Message()
.setId(new Random().nextInt());
// 創(chuàng)建 Spring Message 對象
Message<Demo01Message> springMessage = MessageBuilder.withPayload(message)
.setHeader(MessageConst.PROPERTY_TAGS, tag) // 設(shè)置 Tag
.build();
// 發(fā)送消息
mySource.erbadagangOutput().send(springMessage);
}
return true;
}
}
-
<1>
處,使用@Autowired
注解,注入 MySource Bean。 -
<2>
處,創(chuàng)建 Demo01Message 對象。 -
<3>
處,使用 MessageBuilder 創(chuàng)建 Spring Message 對象,并設(shè)置消息內(nèi)容為 Demo01Message 對象。 -
<4>
處,通過 MySource 獲得 MessageChannel 對象,然后發(fā)送消息。
3.1.6 ProducerApplication
創(chuàng)建 [ProducerApplication]類,啟動應(yīng)用。代碼如下:
package com.erbadagang.springcloudalibaba.stream.rocketmq.producer;
import com.erbadagang.springcloudalibaba.stream.rocketmq.producer.message.MySource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
@SpringBootApplication
@EnableBinding(MySource.class)
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
使用 @EnableBinding
注解,聲明指定接口開啟 Binding 功能,掃描其 @Input
和 @Output
注解。這里,我們設(shè)置為 MySource 接口。
3.2 搭建消費者
創(chuàng)建項目,作為消費者。
3.2.1 引入依賴
創(chuàng)建 [pom.xml
],引入 Spring Cloud Alibaba RocketMQ 相關(guān)依賴。
友情提示:和「3.1.1 引入依賴」基本一樣。
3.2.2 配置文件
創(chuàng)建 [application.yaml
]配置文件,添加 Spring Cloud Alibaba RocketMQ 相關(guān)配置。
spring:
application:
name: erbadagang-consumer-application
cloud:
# Spring Cloud Stream 配置項,對應(yīng) BindingServiceProperties 類
stream:
# Binding 配置項,對應(yīng) BindingProperties Map
bindings:
erbadagang-input:
destination: ERBADAGANG-TOPIC-01 # 目的地。這里使用 RocketMQ Topic
content-type: application/json # 內(nèi)容格式。這里使用 JSON
group: erbadagang-consumer-group-ERBADAGANG-TOPIC-01 # 消費者分組,命名規(guī)則:組名+topic名
trek-input:
destination: TREK-TOPIC-01 # 目的地。這里使用 RocketMQ Topic
content-type: application/json # 內(nèi)容格式。這里使用 JSON
group: trek-consumer-group-TREK-TOPIC-01 # 消費者分組,命名規(guī)則:組名+topic名
# Spring Cloud Stream RocketMQ 配置項
rocketmq:
# RocketMQ Binder 配置項,對應(yīng) RocketMQBinderConfigurationProperties 類
binder:
name-server: 101.133.227.13:9876 # RocketMQ Namesrv 地址
# RocketMQ 自定義 Binding 配置項,對應(yīng) RocketMQBindingProperties Map
bindings:
erbadagang-input:
# RocketMQ Consumer 配置項,對應(yīng) RocketMQConsumerProperties 類
consumer:
enabled: true # 是否開啟消費,默認為 true
broadcasting: false # 是否使用廣播消費,默認為 false 使用集群消費,如果要使用廣播消費值設(shè)成true。
server:
port: ${random.int[10000,19999]} # 隨機端口,方便啟動多個消費者
總體來說,和「3.1.2 配置文件」是比較接近的,所以我們只說差異點噢。
① spring.cloud.stream.bindings
為 Binding 配置項。
這里,我們配置了一個名字為 erbadagang-input
和trek-input
的 Binding。從命名上,我們的意圖是想作為 Input Binding,用于消費者消費消息。
-
group
:消費者分組。消費者組(Consumer Group):同一類 Consumer 的集合,這類 Consumer 通常消費同一類消息且消費邏輯一致。消費者組使得在消息消費方面,實現(xiàn)負載均衡和容錯的目標(biāo)變得非常容易。要注意的是,消費者組的消費者實例必須訂閱完全相同的 Topic。RocketMQ 支持兩種消息模式:集群消費(Clustering)和廣播消費(Broadcasting)。
② spring.cloud.stream.rocketmq.bindings
為 RocketMQ 自定義 Binding 配置項。
這里,我們對名字為 erbadagang-input
的 Binding 進行增強,進行 Consumer 的配置。其中,consumer
為 RocketMQ Producer 配置項,對應(yīng) RocketMQConsumerProperties 類。
enabled
:是否開啟消費,默認為true
。在日常開發(fā)時,如果在本地環(huán)境不想消費,可以通過設(shè)置enabled
為false
進行關(guān)閉。-
broadcasting
: 是否使用廣播消費,默認為false
使用的是集群消費。- 集群消費(Clustering):集群消費模式下,相同 Consumer Group 的每個 Consumer 實例平均分?jǐn)傁ⅰ?/li>
- 廣播消費(Broadcasting):廣播消費模式下,相同 Consumer Group 的每個 Consumer 實例都接收全量的消息。
這里一點要注意!!!加了三個感嘆號,一定要理解集群消費和廣播消費的差異。我們來舉個例子,以有兩個消費者分組 A 和 B 的場景舉例子:
- 假設(shè)每個消費者分組各啟動一個實例,此時我們發(fā)送一條消息,該消息會被兩個消費者分組
"consumer_group_01"
和"consumer_group_02"
都各自消費一次。 - 假設(shè)每個消費者分組各啟動一個實例,此時我們發(fā)送一條消息,該消息會被分組 A 的某個實例消費一次,被分組 B 的某個實例也消費一次
通過集群消費的機制,我們可以實現(xiàn)針對相同 Topic ,不同消費者分組實現(xiàn)各自的業(yè)務(wù)邏輯。例如說:用戶注冊成功時,發(fā)送一條 Topic 為 "USER_REGISTER"
的消息。然后,不同模塊使用不同的消費者分組,訂閱該 Topic ,實現(xiàn)各自的拓展邏輯:
- 積分模塊:判斷如果是手機注冊,給用戶增加 20 積分。
- 優(yōu)惠劵模塊:因為是新用戶,所以發(fā)放新用戶專享優(yōu)惠劵。
- 站內(nèi)信模塊:因為是新用戶,所以發(fā)送新用戶的歡迎語的站內(nèi)信。
- … 等等
這樣,我們就可以將注冊成功后的業(yè)務(wù)拓展邏輯,實現(xiàn)業(yè)務(wù)上的解耦,未來也更加容易拓展。同時,也提高了注冊接口的性能,避免用戶需要等待業(yè)務(wù)拓展邏輯執(zhí)行完成后,才響應(yīng)注冊成功。
同時,相同消費者分組的多個實例,可以實現(xiàn)高可用,保證在一個實例意外掛掉的情況下,其它實例能夠頂上。并且,多個實例都進行消費,能夠提升消費速度。
3.2.3 MySink
創(chuàng)建 [MySink]接口,聲明名字為 Input Binding。代碼如下:
package com.erbadagang.springcloudalibaba.stream.rocketmq.consumer.listener;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
public interface MySink {
String ERBADAGANG_INPUT = "erbadagang-input";
String TREK_INPUT = "trek-input";
@Input(ERBADAGANG_INPUT)
SubscribableChannel demo01Input();
@Input(TREK_INPUT)
SubscribableChannel trekInput();
}
這里,我們通過 @Input
注解,聲明了一個名字為 erbadagang-input
和trek-input
的 Input Binding。注意,這個名字要和我們配置文件中的 spring.cloud.stream.bindings
配置項對應(yīng)上。
同時,@Input
注解的方法的返回結(jié)果為 SubscribableChannel 類型,可以使用它訂閱消息來消費。MessageChannel 提供的訂閱消息的方法如下:
public interface SubscribableChannel extends MessageChannel {
boolean subscribe(MessageHandler handler); // 訂閱
boolean unsubscribe(MessageHandler handler); // 取消訂閱
}
那么,我們是否要實現(xiàn) MySink 接口呢?和MySource
一樣,答案也是不需要,還是全部交給 Spring Cloud Stream 的 BindableProxyFactory 大兄弟來解決。BindableProxyFactory 會通過動態(tài)代理,自動實現(xiàn) MySink 接口。 而 @Input
注解的方法的返回值,BindableProxyFactory 會掃描帶有 @Input
注解的方法,自動進行創(chuàng)建。
例如說,#demo01Input()
方法被自動創(chuàng)建返回結(jié)果為 DirectWithAttributesChannel,它也是 SubscribableChannel 的子類。
3.2.4 Demo01Message
創(chuàng)建 [Demo01Message]類,示例 Message 消息。
友情提示:和[3.1.4 Demo01Message]基本一樣。
3.2.5 Demo01Consumer
創(chuàng)建 [Demo01Consumer] 類,消費消息。代碼如下:
package com.erbadagang.springcloudalibaba.stream.rocketmq.consumer.listener;
import com.erbadagang.springcloudalibaba.stream.rocketmq.consumer.message.Demo01Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
@Component
public class Demo01Consumer {
private Logger logger = LoggerFactory.getLogger(getClass());
@StreamListener(MySink.ERBADAGANG_INPUT)
public void onMessage(@Payload Demo01Message message) {
logger.info("[onMessage][線程編號:{} 消息內(nèi)容:{}]", Thread.currentThread().getId(), message);
}
@StreamListener(MySink.TREK_INPUT)
public void onTrekMessage(@Payload Demo01Message message) {
logger.info("[onMessage][線程編號:{} 消息內(nèi)容:{}]", Thread.currentThread().getId(), message);
}
}
在方法上,添加 @StreamListener
注解,聲明對應(yīng)的 Input Binding。這里,我們使用 MySink.ERBADAFANG_INPUT
。
又因為我們消費的消息是 POJO 類型,所以我們需要添加 @Payload
注解,聲明需要進行反序列化成 POJO 對象。
3.2.6 ConsumerApplication
創(chuàng)建 [ConsumerApplication]類,啟動應(yīng)用。代碼如下:
package com.erbadagang.springcloudalibaba.stream.rocketmq.consumer;
import com.erbadagang.springcloudalibaba.stream.rocketmq.consumer.listener.MySink;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
@SpringBootApplication
@EnableBinding(MySink.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
使用 @EnableBinding
注解,聲明指定接口開啟 Binding 功能,掃描其 @Input
和 @Output
注解。這里,我們設(shè)置為 MySink 接口。
3.3 測試單集群多實例的場景
本小節(jié),我們會在一個消費者集群啟動兩個實例,測試在集群消費的情況下的表現(xiàn)。
① 執(zhí)行 ConsumerApplication 兩次,啟動兩個消費者的實例,從而實現(xiàn)在消費者分組 erbadagang-consumer-group-ERBADAGANG-TOPIC-01 下有兩個消費者實例。
因為 IDEA 默認同一個程序只允許啟動 1 次,所以我們需要配置 DemoProviderApplication 為 Allow parallel run。
② 執(zhí)行 ProducerApplication,啟動生產(chǎn)者的實例。
之后,請求 http://127.0.0.1:18080/demo01/send 接口三次,發(fā)送三條消息。此時在 IDEA 控制臺看到消費者打印日志如下:
// ConsumerApplication 控制臺 01
2020-08-05 09:39:29.073 INFO 50472 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:78 消息內(nèi)容:Demo01Message{id=-1682643477}]
2020-02-22 09:41:32.754 INFO 50472 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:78 消息內(nèi)容:Demo01Message{id=1890257867}]
// ConsumerApplication 控制臺 02
2020-08-05 09:41:32.264 INFO 50534 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:80 消息內(nèi)容:Demo01Message{id=1401668556}]
符合預(yù)期。從日志可以看出,每條消息僅被消費一次。
訪問http://localhost:18080/demo01/sendTrek
也是同樣效果,只不過是演示如何操作多個topic。
3.4 測試多消費集群多實例的場景
本小節(jié),我們會在二個消費者集群各啟動兩個實例,測試在集群消費的情況下的表現(xiàn)。
① 執(zhí)行 ConsumerApplication 兩次,啟動兩個消費者的實例,從而實現(xiàn)在消費者分組 derbadagangemo01-consumer-group-ERBADAGANG-TOPIC-01
下有兩個消費者實例。
② 修改 sca-stream-rocketmq-consumer
項目的配置文件,修改 spring.cloud.stream.bindings.erbadagang-input.group
配置項,將消費者分組改成 NEW-erbadagang-consumer-group-ERBADAGANG-TOPIC-01
。
然后,執(zhí)行 ConsumerApplication 兩次,再啟動兩個消費者的實例,從而實現(xiàn)在消費者分組 NEW-erbadagang-consumer-group-ERBADAGANG-TOPIC-01
下有兩個消費者實例。
③ 執(zhí)行 ProducerApplication,啟動生產(chǎn)者的實例。
之后,請求 http://127.0.0.1:18080/demo01/send 接口三次,發(fā)送三條消息。此時在 IDEA 控制臺看到消費者打印日志如下:
// 消費者分組 `demo01-consumer-group-DEMO-TOPIC-01` 的ConsumerApplication 控制臺 01
2020-08-06 10:17:07.886 INFO 50472 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:78 消息內(nèi)容:Demo01Message{id=-276398167}]
2020-08-06 10:17:08.237 INFO 50472 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:78 消息內(nèi)容:Demo01Message{id=-250975158}]
// 消費者分組 `demo01-consumer-group-DEMO-TOPIC-01` 的ConsumerApplication 控制臺 02
2020-02-22 10:17:08.710 INFO 50534 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:80 消息內(nèi)容:Demo01Message{id=412281482}]
// 消費者分組 `X-demo01-consumer-group-DEMO-TOPIC-01` 的ConsumerApplication 控制臺 01
2020-08-06 10:17:07.887 INFO 51092 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:51 消息內(nèi)容:Demo01Message{id=-276398167}]
2020-02-22 10:17:08.238 INFO 51092 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:51 消息內(nèi)容:Demo01Message{id=-250975158}]
// 消費者分組 `X-demo01-consumer-group-DEMO-TOPIC-01` 的ConsumerApplication 控制臺 02
2020-08-06 10:17:08.787 INFO 51096 --- [MessageThread_1] c.i.s.l.r.c.listener.Demo01Consumer : [onMessage][線程編號:77 消息內(nèi)容:Demo01Message{id=412281482}]
從日志可以看出,每條消息被每個消費者集群都進行了消費,且僅被消費一次。
3.5 小結(jié)
至此,我們已經(jīng)完成了 Stream RocketMQ 的快速入門。回過頭看看 Binder 和 Binding 的概念,是不是就清晰一些了。
底線
本文源代碼使用 Apache License 2.0開源許可協(xié)議,這里是本文源碼Gitee地址,可通過命令git clone+地址
下載代碼到本地,也可直接點擊鏈接通過瀏覽器方式查看源代碼。