Spring Cloud 快速入門(八)訊息系統整合框架 Spring Cloud Stream

FixBugIdou1995發表於2020-10-14

1. 簡介

1.1 官網

在這裡插入圖片描述

【原文】A lightweight event-driven microservices framework to quickly build applications that can connect to external systems. Simple declarative(聲名式的) model to send and receive messages using Apache Kafka or RabbitMQ between Spring Boot apps.

【翻譯】一個輕量級的事件驅動微服務框架,用於快速構建可連線到外部系統的應用程式。在 Spring Boot 應用程式之間使用 Kafka 或 RabbitMQ 傳送和接收訊息的簡單宣告式模型。

1.2 綜合

Spring Cloud Stream 是一個用來為微服務應用構建訊息驅動能力的框架。通過使用Spring Cloud Stream,可以有效簡化開發人員對訊息中介軟體的使用複雜度,讓系統開發人員可以有更多的精力關注於核心業務邏輯的處理。但是目前 Spring Cloud Stream 只支援RabbitMQ 和 Kafka 的自動化配置。

2 程式模型

spring cloud 官網首頁中點選相應版本的參考文件。
在這裡插入圖片描述

應用程式的核心部分(Application Core)通過 inputs 與 outputs 管道,與中介軟體連線,而管道是通過繫結器 Binder 與中介軟體相繫結的。

3. stream kafka 微服務

3.1 總步驟

(1) 建立生產者步驟

  • 匯入 spring-cloud-stream-binder-kafka 依賴
  • 建立生產者類。在生產者類上新增@EnableBinding()註解,並宣告管道
  • 定義處理器,在處理器中呼叫訊息生產者,使其傳送訊息
  • 在配置檔案中註冊 kafka 叢集,並指定管道所繫結的主題及型別

(2) 建立消費者步驟

  • 匯入 spring-cloud-stream-binder-kafka 依賴
  • 建立消費者類。在消費者類上新增@EnableBinding()註解,並宣告管道。
  • 在消費者類中定義消費方法,在方法上新增相應的註解。
  • 在配置檔案中註冊 kafka 叢集,並指定管道所繫結的主題

3.2 訊息傳送給一個主題的生產者

(1) 建立工程 08-stream-kafka-8080
任意複製前面的一個提供者或消費者工程,將其中的除啟動類之外的其它程式碼全部刪除。這裡複製 02-consumer-8080 工程,並命名為 08-stream-kafka-8080。

修改啟動類類名ApplicationStreamKafka8080。

(2) 匯入依賴
僅需再新增一個 Spring Cloud Stream Kafka 相關的依賴即可。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>

整個 pom.xml 檔案的內容如下:

<groupId>com.abc</groupId>
<artifactId>08-stream-kafka-8080</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-stream-binder-kafka</artifactId>
    </dependency>

    <!--actuator依賴-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <!--eureka客戶端依賴-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

(3) 建立生產者類
在這裡插入圖片描述

@Component
// 將MQ與生產者類通過訊息管道相繫結
// org.springframework.cloud.stream.messaging.Source
@EnableBinding(Source.class)
public class SomeProducer {
    // 必須使用byName方式的自動注入
    // 系統中還定義了名稱為nullChannel和errorChannel的兩個同型別管道
    @Autowired
    @Qualifier(Source.OUTPUT)//Source.OUTPUT="output"
    private MessageChannel channel;

    public String sendMessage(String msg) {
        // 通過訊息管道傳送訊息,即將訊息寫入到訊息管道,再通過訊息管道寫入到MQ
        channel.send(MessageBuilder.withPayload(msg).build());
        return msg;
    }
}

(4) 建立處理器
在這裡插入圖片描述

@RestController
public class SomeController {
    // 將生產者注入
    @Autowired
    private SomeProducer producer;

    @PostMapping("/msg/send")
    public String sendHandler(@RequestParam("message") String msg) {
        // 生產者傳送訊息
        return producer.sendMessage(msg);
    }
}

(5) 建立配置檔案
在這裡插入圖片描述

spring:
  application:
    name: abcmsc-consumer-depart

  cloud:
    stream:
      kafka:
        binder:
          # 指定要連線的kafka叢集
          brokers: kafkaOS1:9092,kafkaOS2:9092,kafkaOS3:9092
          # 指定是否自動建立主題
          auto-create-topics: true
      bindings:
        # 指定要繫結的輸出管道,及要輸出到單管道中的訊息主題及型別
        output:
          destination: persons # 主題
          content-type: text/plain

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

(6) 演示
kafka消費者先用命令列方式:
因為我機器上已經有persons主題了,為了演示自動建立主題,換成names:
在這裡插入圖片描述

在這裡插入圖片描述

傳送多次:
在這裡插入圖片描述
在這裡插入圖片描述

3.3 訊息傳送給多個主題的生產者

前面工程中通過 Source 的 MessageChannel 完成了將訊息傳送給某個指定主題的功能,若要將訊息傳送給多個主題,則需要自定義 Channel。

(1) 建立工程 08-stream-kafka2-8080
複製前面的 08-stream-kafka-8080,重新命名為 08-stream-kafka2-8080。
修改啟動類類名ApplicationStreamKafka28080。

(2) 定義 Source 介面
模擬 Source 介面自定義一個 Source 介面。
在這裡插入圖片描述

/**
 * 自定義管道
 */
public interface CustomSource {
	//管道名稱
    String CHANNEL_NAME = "xxx";

    @Output(CustomSource.CHANNEL_NAME)
    MessageChannel output();
}

(3) 修改釋出者類
在這裡插入圖片描述

@Component
// 將MQ與生產者類通過訊息管道相繫結
@EnableBinding({Source.class, CustomSource.class})
public class SomeProducer {
    // 必須使用byName方式的自動注入
    @Autowired
    @Qualifier(Source.OUTPUT)
    private MessageChannel channel;

    @Autowired
    @Qualifier(CustomSource.CHANNEL_NAME)
    private MessageChannel customChannel;

    public String sendMessage(String msg) {
        // 將訊息寫入到兩個管道,將會寫入到兩個主題
        channel.send(MessageBuilder.withPayload(msg).build());
        customChannel.send(MessageBuilder.withPayload(msg).build());
        return msg;
    }
}

(4) 修改配置檔案
在配置檔案中新增如下輸出目標。
在這裡插入圖片描述

spring:
  application:
    name: abcmsc-consumer-depart

  cloud:
    stream:
      kafka:
        binder:
          # 指定要連線的kafka叢集
          brokers: kafkaOS1:9092,kafkaOS2:9092,kafkaOS3:9092
          # 指定是否自動建立主題
          auto-create-topics: true
      bindings:
        # 指定要繫結的輸出管道,及要輸出到單管道中的訊息主題及型別
        output:
          destination: names
          content-type: text/plain

        xxx:
          destination: cities
          content-type: text/plain

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

(5) 效果
傳送訊息:dddddd、eeeeee、fffff
在這裡插入圖片描述

結果:
在這裡插入圖片描述

在這裡插入圖片描述

3.4 建立訊息消費者 – @PostConstruct 方式

Spring Cloud Stream 提供了三種建立消費者的方式,這三種方式的都是在消費者類的“消費”方法上新增註解。只要有新的訊息寫入到了管道,該“消費”方法就會執行。只不過三種註解,其底層的實現方式不同。即當新訊息到來後,觸發“消費”方法去執行的實現方式不同。

  • @PostConstruct:以釋出/訂閱方式實現
  • @ServiceActivator:新的訊息啟用服務方式實現
  • @StreamListener:以監聽方式實現

為了簡單起見,這裡就不再單獨建立消費者工程了,都直接定義在前面的生產者工程中。

(1) 建立消費者類

@Component
@EnableBinding(Sink.class) //和生產者Source註解類似,繫結了一個輸入通道
public class SomeConsumer {

    @Autowired
    @Qualifier(Sink.INPUT)
    private SubscribableChannel channel;

    @PostConstruct       //以釋出/訂閱方式實現
    public void printMessage() {
    	//為輸入通道註冊訊息處理程式。
        channel.subscribe(msg -> {
            // MessageHeaders headers = msg.getHeaders();
            System.out.println(new String((byte[])msg.getPayload()));
        });
    }
}

(2) 修改配置檔案
在這裡插入圖片描述

spring:
  application:
    name: abcmsc-consumer-depart

  cloud:
    stream:
      kafka:
        binder:
          # 指定要連線的kafka叢集
          brokers: kafkaOS1:9092,kafkaOS2:9092,kafkaOS3:9092
          # 指定是否自動建立主題
          auto-create-topics: true
      bindings:
        # 指定要繫結的輸出管道,及要輸出到單管道中的訊息主題及型別
        output:
          destination: names
          content-type: text/plain

        xxx:
          destination: cities
          content-type: text/plain

        # 指定要繫結的輸入管道,及要消費的管道中的訊息主題
        input:
          destination: names

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

(3) 效果
在這裡插入圖片描述

在這裡插入圖片描述

3.5 建立訊息消費者 – @ServiceActivator 方式

該註解所標註的方法是以服務的形式出現的,只要管道中的資料發生了變化就會啟用該服務。

(1) 註釋掉前面的消費者
將前面的消費者類註釋掉,其將不會出現在 Spring 容器。
在這裡插入圖片描述

(2) 建立消費者類
在這裡插入圖片描述

@Component
@EnableBinding(Sink.class)//繫結一個輸入通道
public class SomeConsumer2 {
	//指示方法能夠處理訊息或訊息有效負載。
	//註釋為@Serviceactivator的方法可以接受型別
	//為{@link org.springframework.messaging.Message}或預期訊息有效負載型別的引數。
	//如果需要,{@link org.springframework.beans.SimpleTypeConverter}支援的任何型別
	//轉換都將應用於訊息有效負載。通過使
	//用{@link org.springframework.messaging.handler.annotation.Header @Header}引數註解
	//訊息頭的值也可以作為訊息引數傳遞。

	//註釋方法的返回值可以是任何型別。如果返回值不是訊息,則將以該物件作為其有效負載建立應答訊息。
    @ServiceActivator(inputChannel = Sink.INPUT)
    public void printMessage(Object msg) {
        System.out.println(msg);
    }
}

3.6 建立訊息消費者 – @SteamListener 方式

該方式是以監聽的方式實現,只要管道中的流資料發生變化,其就會觸發該註解所標註的方法的執行。

(1) 註釋掉前面的消費者
在這裡插入圖片描述

(2) 建立消費者類
在這裡插入圖片描述

@Component
@EnableBinding(Sink.class)//繫結一個輸入通道
public class SomeConsumer3 {
	//將一個方法標記為通過{@link EnableBinding}(例如通道)宣告的輸入的偵聽器的註解。
    @StreamListener(Sink.INPUT)
    public void printMessage(Object msg) {
        System.out.println(msg);
    }
}

相關文章