前言

通过之前的文章,我们已经对Spring cloud stream有了大概的理解,比如:输入、输出通道的绑定,通道消息事件的监听等。这篇文章,我们将详细介绍一下Spring Cloud Stream中是如何通过定义一些基础概念来对各种不同的消息中间件做抽象的。

结构模型

SSE消息推 springcloudgateway_Cloud

通过上图,我们可以了解到以下信息:

  • Spring Cloud Stream构建的应用程序与消息中间件之间是通过绑定器Binder相关联的,绑定器对于应用程序而言起到了隔离作用,它使得不同消息中间件的实现细节对应用程序来说是透明的。
  • Channel :在应用程序和Binder之间定义了两条输入通道和三条输出通道来传递消息,而绑定器则是作为这些通道和消息中间件之间的桥梁进行通信。
绑定器
  • 在没有绑定器的情况下, 我们使用String boot 与中间件进行信息交互时,由于各种中间件存在许多差异,这使我们实现消息交互流程时非常的笨重。
  • 通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。它需要向Applation暴露出一个通道,,使得应用程序不需要再考虑各种不同的消息中间件实现。当我们需要升级消息中间件,或是更换其他消息中间件产品时,我们要做的就是更换它们对应的Binder绑定器而不需要修改任何Spring Boot的应用逻辑
  • 目前版本的Spring Cloud Stream为主流的消息中间件产品RabbitMQ和Kafka提供了默认的Binder实现,在之前的列子中我们使用了RabbitMQ的Binder.
  • 使用application.properties或是application.yml来做任何属性设置。
spring.cloud.stream.bindings.input.destination=raw-sensor-data

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=springcloud
spring.rabbitmq.password=123456
发布-订阅模式
  • 在Spring Cloud Stream中的消息通信方式遵循了发布-订阅模式,当一条消息被投递到消息中间件之后,它会通过共享的Topic主题进行广播,消息消费者在订阅的主题中收到它并触发自身的业务逻辑处理。
  • Topic主题是Spring Cloud Stream中的一个抽象概念,用来代表发布共享消息给消费者的地方。在不同的消息中间件中,Topic可能对应着不同的概念,比如:在RabbitMQ中的它对应了Exchange、而在Kakfa中则对应了Kafka中的Topic。
  • 在之前的文章中,我们通过RabbitMQ的Channel进行发布消息给我们编写的应用程序消费,而实际上Spring Cloud Stream应用启动的时候,在RabbitMQ的Exchange中也创建了一个名为input的Exchange交换器,由于Binder的隔离作用,应用程序并无法感知它的存在,应用程序只知道自己指向Binder的输入或是输出通道。
  • 为了直观的感受发布-订阅模式中,消息是如何被分发到多个订阅者的,我们可以使用快速入门的例子,通过命令行的方式启动两个不同端口的进程。
  • 现在我们创建一个input的输入管道,这时候我们发生一条消息,我们可以看到两个页面都会收到消息
  • 如下图,我们分别是“订阅者-1”和“订阅者-2”,他们都建立了一条输入通道绑定到同一个Topic(RabbitMQ的Exchange)上。
消费组

Spring Cloud Stream中提供了消费组的概念,来解决多节点消费问题。

  • 之前的讨论中,spring Cloud Stream通过发布-订阅模式将消息生产者与消费者做了很好的解耦,在我们的微服务架构中,为了实现高可用模式,往往一个微服务会部署多个节点,那么之前的案例中,一个消息会被消费多次,这不符合我们的需求。
  • 我们可以通过spring.cloud.stream.bindings.input.group属性为应用指定一个组名,这样这个应用的多个实例在接收到消息的时候,只会有一个成员真正的收到消息并进行处理。
  • 如下图所示,我们为Service-A和Service-B分别启动了两个实例,并且根据服务名进行了分组,这样当消息进入主题之后,Group-A和Group-B都会收到消息的副本,但是在两个组中都只会有一个实例对其进行消费。
    默认情况下,当我们没有为应用指定消费组的时候,Spring Cloud Stream会为其分配一个独立的匿名消费组。所以,如果同一主题下所有的应用都没有指定消费组的时候,当有消息被发布之后,所有的应用都会对其进行消费,因为它们各自都属于一个独立的组中。大部分情况下,我们在创建Spring Cloud Stream应用的时候,建议最好为其指定一个消费组,以防止对消息的重复处理,除非该行为需要这样做(比如:刷新所有实例的配置等)
消息分区

当生产者将消息数据发送给多个消费者实例时,保证拥有共同特征的消息数据始终是由同一个消费者实例接收和处理。
通过引入消费组的概念,我们已经能够在多实例的情况下,保障每个消息只被组内一个实例进行消费。通过上面对消费组参数设置后的实验,我们可以观察到,消费组并无法控制消息具体被哪个实例消费。也就是说,对于同一条消息,它多次到达之后可能是由不同的实例进行消费的。但是对于一些业务场景,就需要对于一些具有相同特征的消息每次都可以被同一个消费实例处理,比如:一些用于监控服务,为了统计某段时间内消息生产者发送的报告内容,监控服务需要在自身内容聚合这些数据,那么消息生产者可以为消息增加一个固有的特征ID来进行分区,使得拥有这些ID的消息每次都能被发送到一个特定的实例上实现累计统计的效果,否则这些数据就会分散到各个不同的节点导致监控结果不一致的情况。