Spring Cloud及微服务(九):消息驱动Spring Cloud Stream

版权声明:YETA https://blog.csdn.net/qq_28958301/article/details/88823165

出现背景

Spring Cloud Stream是一个用来为微服务应用构建消息驱动能力的框架。

通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。

Spring Cloud Stream本质上就是整合了Spring Boot和Spring Integration,实现了一套轻量级消息驱动的微服务框架。

Spring Boot整合Stream-Rabbit

  • 依赖

  • 消息消费者

  • 启动程序,在RabbitMQ控制台发送给消息

  • 程序输出

Spring Cloud Stream应用模型结构图

Spring Cloud Stream构建的应用程序与消息中间件之间是通过绑定器Binder相关联的,绑定器对于应用程序而言起到了隔离作用,它使得不同消息中间件的实现细节对应应用程序来说是透明的。

  • 绑定器

通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件的实现。就如上一章中,从RabbitMQ切换到Kafka。

  • 发布-订阅模式

Spring Cloud Stream中的消息通信方式遵循了发布-订阅模式,当一条消息被投递到消息中间件之后,它会通过共享的Topic主题进行广播,消息消费者在订阅的主题中收到它并触发自身的业务逻辑处理。

相对于点对点队列实现的消息通信来说,Spring Cloud Stream采用的发布-订阅模式可以有效降低消息生产者与消费者之间的耦合。当需要对同一类消息增加一种处理方式时,只需要增加一个应用程序并将输入通道绑定到既有的Topic中就可以实现功能的扩展,而不需要改变原来已经实现的任何内容。

  • 消费组

在现实的微服务架构中,每一个微服务应用都会部署多个实例,当消息生产者发送消息给某个微服务时,这时候就会被同一个微服务的多个实例消费,出现重复消费。

通过spring.cloud.stream.bindings.input.group属性为应用指定一个组名,这样这个应用的多个实例在接收到消息的时候,只会有一个成员真正收到并进行处理。

  • 消息分区

消费组无法控制消息具体被哪个实例消费,但是对于一些业务场景,需要对一些具有相同特征的消息设置每次都被同一个消费实例处理。

消息生产者可以为消息增加一个固有的特征ID来进行分区。

绑定消息通道

在Spring Cloud Stream中,可以在接口中通过@Input和@Output注解来定义消息通道,而用于定义绑定消息通道的接口则可以被@EnableBinding注解的value参数来指定,从而在应用启动的时候实现对定义消息通道的绑定。

Spring Cloud Stream自带一个Sink、一个Source、一个Processor,源码如下:

@Input和@Output注解有一个value属性,用来设置消息通道的名称。如果没有指定具体的value值,将默认使用方法名作为消息通道的名称。

测试使用Processor

  • 创建MessageService类,并绑定Processor的input消息通道

  • 创建单元测试类,并绑定Processor的output消息通道

  • 此时先后启动主程序和单元测试类,发现并无输出。原因:发送消息的通道是output,而接收消息的通道是input,通道不一致。但是如果将通道设置为一致,会出现以下错误:
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'example-topic' defined in com.didispace.stream.TestTopic: bean definition with this name already exists - Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.didispace.stream.TestTopic; factoryMethodName=input; initMethodName=null; destroyMethodName=null
	at org.springframework.cloud.stream.binding.BindingBeanDefinitionRegistryUtils.registerBindingTargetBeanDefinition(BindingBeanDefinitionRegistryUtils.java:64) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
	at org.springframework.cloud.stream.binding.BindingBeanDefinitionRegistryUtils.registerOutputBindingTargetBeanDefinition(BindingBeanDefinitionRegistryUtils.java:54) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
	at org.springframework.cloud.stream.binding.BindingBeanDefinitionRegistryUtils.lambda$registerBindingTargetBeanDefinitions$0(BindingBeanDefinitionRegistryUtils.java:86) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:562) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:541) ~[spring-core-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.cloud.stream.binding.BindingBeanDefinitionRegistryUtils.registerBindingTargetBeanDefinitions(BindingBeanDefinitionRegistryUtils.java:76) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
	at org.springframework.cloud.stream.config.BindingBeansRegistrar.registerBeanDefinitions(BindingBeansRegistrar.java:45) ~[spring-cloud-stream-2.0.1.RELEASE.jar:2.0.1.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:358) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_151]
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:357) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:145) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:328) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:61) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1277) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1265) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at com.didispace.stream.TestApplication.main(TestApplication.java:13) [classes/:na]

详情参考: http://blog.didispace.com/spring-cloud-starter-finchley-7-1/

  • 配置文件中加入如下配置

  • 运行结果

注入消息通道

  • 修改上文创建的单元测试类

  • 运行结果

  • 要注意参数命名要与通道同名才能被正确注入

Spring Integration原生支持

  • 修改MessaggeService类:使用@ServiceActivator注解替换@StreamListener注解,实现对input通道的监听处理。使用@InboundChannelAdapter注解对output通道输出绑定。由于上文配置文件中将两个通道的Topic绑定到一起,所以这两个方法可以存在同一个程序中,如果使用同名的通道,则需在两个程序中分别运行。

  • 运行结果,2秒一次

  • 使用@Transformer注解对指定通道的消息进行转换

@StreamListener注解内置消息转换功能

假如消息是JSON格式,使用@ServiceActivator需要这样做:

使用@StreamListener只需这样做:

消息反馈

很多时候在处理完输入消息之后,需要反馈一个消息给对方,这时候可以通过@SendTo注解来指定返回内容的输出通道。

  • 使用@StreamListener注解监听input通道,如果有消息进来,如理完之后通过@SendTo注解返回消息到output通道

  • 使用@ServiceActivator注解监听output通道,接收返回消息。这里使用@StreamListener一个意思。使用@InboundChannelAdapter发送消息

  • 运行结果

消息类型

基础配置

以spring.cloud.stream.为前缀

绑定通道配置

  • 通用配置

以spring.cloud.stream.bindings.<channelName>.为前缀

  • 消费者配置

以spring.cloud.stream.bindings.<channelName>.consumer.为前缀

  • 生产者配置

以spring.cloud.stream.bindings.<channelName>.producer.为前缀

RabbitMQ配置

  • 通用配置

以spring.cloud.stream.rabbit.binder.为前缀

  • 消费者配置

以spring.cloud.stream.rabbit.bindings.<channelName>.consumer.为前缀

  • 生产者配置

以spring.cloud.stream.rabbit.bindings.<channelName>.producer.为前缀

Kafka配置

  • 通用配置

以spring.cloud.stream.kafka.binder.为前缀

  • 消费者配置

以spring.cloud.stream.kafka.bindings.<channelName>.consumer.为前缀

  • 生产者配置

以spring.cloud.stream.kafka.bindings.<channelName>.producer.为前缀

猜你喜欢

转载自blog.csdn.net/qq_28958301/article/details/88823165
今日推荐