RocketMQ 與 Spring Cloud Stream整合(一、快速入門)

一、 概述

本文我們來學(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)部有兩個概念:BinderBinding

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);

}

Binding,包括 Input Binding 和 Output Binding。Binding 在消息中間件與應(yīng)用程序提供的 Provider 和 Consumer 之間提供了一個橋梁,實現(xiàn)了開發(fā)者只需使用應(yīng)用程序的 Provider 或 Consumer 生產(chǎn)或消費數(shù)據(jù)即可,屏蔽了開發(fā)者與底層消息中間件的接觸。

最終整體交互如下圖所示:
Spring Cloud Stream Application binder&bindings

三、 快速入門

本小節(jié),我們一起來快速入門下,會創(chuàng)建 2 個項目,分別作為生產(chǎn)者和消費者。最終項目如下圖所示:
producer和consumer項目

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-outputtrek-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-inputtrek-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è)置 enabledfalse 進行關(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-inputtrek-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+地址下載代碼到本地,也可直接點擊鏈接通過瀏覽器方式查看源代碼。

本文源代碼

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