Spring Cloud Stream:消息驱动架构的统一编程之道

377 阅读5分钟

Spring Cloud Stream:消息驱动架构的统一编程之道

前言

在微服务架构蓬勃发展的今天,服务间的异步通信已成为系统解耦、流量削峰和实时数据处理的核心技术手段。然而,面对KafkaRabbitMQRocketMQ等纷繁复杂的消息中间件,开发者往往陷入两难:如何在不被特定技术绑定的前提下,高效实现消息驱动的业务逻辑? 当需要在不同消息中间件之间迁移或适配多平台时,重复的代码和差异化的API是否让您的团队疲于应付?

这正是Spring Cloud Stream试图解决的痛点。作为Spring家族中面向消息驱动架构的“粘合剂”,它通过抽象化的编程模型开箱即用的中间件适配能力,重新定义了微服务间消息通信的开发范式。

和Spring Kafka比较

简单比较一下Spring Cloud Stream和单一消息中间件Spring Kafka的区别。

架构

消息传递流程

生产者侧(服务A)

  • Source生成消息:服务A通过**Source接口(如调用MessageChannel.send()**)创建消息。
  • Channel传递:消息进入**Output Channel**,准备发送到中间件。
  • Binder绑定中间件:Binder根据配置将**Channel映射到消息中间件的具体Topic**或队列。
  • 消息发送至中间件:通过中间件客户端(如**Kafka Producer**)将消息持久化到中间件。

消费者侧(服务B)

  • Binder监听中间件:**Binder从消息中间件订阅指定Topic**或队列的消息。
  • Channel接收消息:消息通过**Input Channel**传递到服务B。
  • Sink处理消息:服务B通过**Sink接口(如@StreamListener**)消费并处理消息。

**Spring Cloud Stream的架构决定了它具有与中间件解耦的特点。开发者只需关注SourceSink的业务逻辑,无需直接操作KafkaRabbitMQ的API。例如,替换消息中间件时,只需修改Binder**配置,无需重构代码。

快速入门

使用Spring Initializr或您喜欢的IDE来创建项目,我使用的Spring Boot版本是2.7.18

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.7.18</version>
	<relativePath/>
</parent>

添加依赖

Spring Boot的版本号和spring-cloud.version 要匹配,这点很重要。以下是Maven依赖:

<properties>
	<java.version>11</java.version>
	<spring-cloud.version>2021.0.8</spring-cloud.version>
</properties>

<dependencies>
	<!-- Spring Boot Web(可选,仅需 Web 功能时添加) -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<!-- Spring Cloud Stream 核心 -->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-stream</artifactId>
	</dependency>

	<!-- 绑定器 -->
<!--		<dependency>-->
<!--			<groupId>org.springframework.cloud</groupId>-->
<!--			<artifactId>spring-cloud-starter-stream-rabbit</artifactId>-->
<!--		</dependency>-->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-stream-kafka</artifactId>
	</dependency>

	<!-- 测试依赖 -->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-stream-test-support</artifactId>
		<scope>test</scope>
	</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>

消息通道

创建一个名为MessageChannels的接口,用于定义输入和输出的消息通道。这些通道允许您的Spring Cloud Stream应用程序与消息中间件进行通信。

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;

public interface MessageChannels {
    @Output("output1")
    MessageChannel output1();

    @Input("input1")
    SubscribableChannel input1();

    @Output("output2")
    MessageChannel output2();

    @Input("input2")
    SubscribableChannel input2();
}

@Output注解和@Input里的参数需要在项目配置文件(例如application.properties)中映射相应的中间件Topic。我这里配置了两种场景:单个中间件、多个中间件。

首先在application.properties文件中配置spring.profiles.active ,用于切换不同场景的配置文件。

spring.application.name=cloudstreamdemo

#spring.profiles.active=mq
spring.profiles.active=multimq

单个中间件配置文件application-mq.yaml

spring:
  kafka:
    bootstrap-servers: localhost:9092
  cloud:
    stream:
      bindings:
        input1:
          destination: test-topic1
          group: my-message-group
        output1:
          destination: test-topic1
        input2:
          destination: test-topic2
          group: my-message-group
        output2:
          destination: test-topic2

多个中间件配置文件application-multimq.yamlspring.cloud.stream.binders用于配置不同环境的中间件,spring.cloud.stream.bindings 用于将中间件(Topic)和生产者消费者(@Input、@Output)绑定。

spring:
  cloud:
    stream:
      binders:
        # 接下来的kafka1和kafka2就是两个kafka broker的环境配置,配置完成后可以应用kafka1、kafka2这个定义的名字
        # 在别的地方引用。功能和profile中的dev环境、test环境、prod环境一个意思。
        kafka1:
          type: kafka
          environment:
            spring:
              cloud:
                stream:
                  kafka:
                    binder:
                      brokers: localhost:9092
        kafka2:
          type: kafka
          environment:
            spring:
              cloud:
                stream:
                  kafka:
                    binder:
                      brokers: localhost:9093
      bindings:
        input1:
          # 没有指定binder,使用默认环境,下面的配置有指定默认环境。
          destination: test-topic1
          group: my-message-group
        output1:
          destination: test-topic1
#          content-type: application/json
        input2:
          # 指定使用kafka2的环境
          binder: kafka2
          destination: test-topic2
          group: my-message-group
        output2:
          binder: kafka2
          destination: test-topic2
#          content-type: application/json
      kafka:
        binder:
          # 自动创建不存在的topic
          autoCreateTopics: true
      # 默认使用哪个kafka环境,如下配置是在没有指定kafka环境的时候(如output_2),使用kafka1的配置
      default-binder: kafka1

生产者

写一个生产者接口,通过HTTP接口请求调用消息通道是send方法生产消息。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@EnableBinding(MessageChannels.class)
public class MessageProducerController {
    @Autowired
    private MessageChannels messageChannels;

    @PostMapping("/send")
    public String sendMessage(@RequestBody String message) {
        messageChannels.output1().send(MessageBuilder.withPayload(message).build());
        return "Message sent: " + message;
    }
}

消费者

创建一个MessageConsumerService类,使用@StreamListener注解监听输入消息通道,@SendTo注解用来将消息发送给输出消息通道。

import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.handler.annotation.SendTo;

@EnableBinding(MessageChannels.class)
public class MessageConsumerService {
    @StreamListener("input1")
    @SendTo("output2")
    public String input1(String message) {
        String msg = "test-topic1 received message: " + message;
        System.out.println(msg);
        return message;
    }

    @StreamListener("input2")
    public void inputTestTopic2(String message) {
        String msg = "test-topic2 received message: " + message;
        System.out.println(msg);
    }
}

测试结果

调用接口[http://localhost:8080](http://localhost:8080/)/send 参数如下:

{
"hello": "cloud stream"
}

可以看到日志打印出来,output1接口发送的消息被input1 监听到,并发送给output2 ,再由input2监听到,完全符合MessageConsumerService 代码的预期。

参考

Spring Cloud Stream 中文文档 参考手册 中文版

Spring Cloud Stream:打造强大的微服务事件驱动架构引言 随着云计算、微服务和大数据技术的快速发展,构建 - 掘金