Spring Cloud Stream学习及实践

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yzy199391/article/details/88551718

基本介绍

为微服务应用构建消息驱动能力的框架。

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

整合了Spring Boot和Spring Integration

支持的中间件:

  • RabbitMQ
  • Kafka

Springboot中使用

引入依赖

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

其中,spring-cloud-starter-stream-rabbit等价于spring-cloud-stream-binder-rabbit

核心注解

@EnableBinding

用于指定一个或多个定义了@Input@Output注解的接口,以此实现对消息通道(Channel)的绑定。

  • @Input
public interface Sink{
    String INPUT = "input";
    
    @Input("INPUT")
    SubscribableChannel input()
}

绑定了一个输入消息通道

  • @Output
public interface Source{
    String OUTPUT = "output";
    
    @Output("OUTPUT")
    MessageChannel output();
}

绑定了一个输出通道

上述两个接口都是Spring Cloud默认实现的绑定消息通道的接口,还有一个Processor,其结合Sink和Source,包括输入绑定和输出绑定。

  • EnableBinding指定多个接口来绑定消息通道:
    @EnableBinding(value = {Sink.class, Source.class})

@StreamListener

定义在方法上,能将被修饰的方法注册为消息中间件上数据流的监听器,属性值对应了监听的通道名。

⚠️:输入通道、输出通道的概念都是针对应用程序来说的。输入通道即,将消息输入到应用程序的通道;输出通道,即应用程序输出消息到Binder的通道。

核心概念

在这里插入图片描述

Binder(绑定器)

应用程序与消息中间件之间通过Binder关联,将两者进行隔离,使得不同消息中间件的实现细节对于应用来说是透明的。

这样做的好处是:增加灵活性,切换消息中间件或升级中间件时,只需切换Binder即可。

不同消息中间件通过Binder向应用程序暴露统一的Channel通道

Spring Cloud Stream中对RabbitMQ和Kafka提供了默认的Binder实现,对于其它消息中间件,可以使用扩展API实现Binder。

  • Binder配置的对象是消息中间件中的内容,如queue…
  • Bindings配置的对象即为@Input、@Output注解的内容

发布-订阅模式

消息投递到消息中间件时,会通过Topic广播给所有订阅它的消费者,消费者收到消息出发自身业务逻辑。

RabbitMQ中Topic对应Exchange,Kafka中就是Topic。

若要对一类消息增加一种处理方式,只需增加一个应用程序并将输入通道绑定到既有的Topic中即可。

@Input("[BinderName]")
SubscribableChannel input();

消费组

保障每个消息只被组内一个实例消费

现实的微服务架构中,为实现高可用和负载均衡,一个微服务会部署多个实例。
消息生产者发送一条消息给一个微服务时,只想被消费一次;应这种使用场景,消费组概念应运而生。

spring.cloud.stream.bindings.[input].group配置可以为应用指定一个组名,此应用的多个实例在接受到消息时,只有一个成员会真正收到消息并处理。

没有为应用指定消费组时,Spring Cloud Stream会默认为其分配一个独立的匿名消费组;同一主题下所有应用都未指定组时,消息发布时,所有应用都会消费它(它们属于独立消费组)。

消息分区

为一些具有相同特征的消息设置每次都被同一个消费实例进行消费。

Spring Cloud Stream为分区提供了通用的抽象实现,为不具备分区功能的消息中间件也增加了分区功能。

server:
  port: 8089
spring:
  cloud:
    stream:
      binders: 
        defaultRabbit: 
          type: rabbit
          environment:                                      #配置rabbimq连接环境
            spring: 
              rabbitmq:
                host: xxx.xxx.xxx.xxx
                username: xxx
                password: xxx
                virtual-host: / 
      bindings: 
        msgSender:                                                     #生产者绑定,这个是消息通道的名称
          destination: exchange-msgSender                              #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-msg交换器。
          content-type: application/json
          producer: 
            partition-count: 2                                         #指定参与消息分区的消费端节点数量为2个
            partition-key-expression: headers['partitionKey']          #payload.id#这个是分区表达式, 例如当表达式的值为1, 那么在订阅者的instance-index中为1的接收方, 将会执行该消息.
        msgSender2:                                                    #生产者绑定,这个是消息通道的名称
          destination: exchange-msgSender                              #exchange名称,交换模式默认是topic;把SpringCloud stream的消息输出通道绑定到RabbitMQ的exchange-msgSender交换器。
          content-type: application/json
          producer: 
            partition-count: 2                                         #指定参与消息分区的消费端节点数量为2个
            partition-key-expression: headers['partitionKey']          #payload.id#这个是分区表达式, 例如当表达式的值为1, 那么在订阅者的instance-index中为1的接收方, 将会执行该消息.

使用详解

开启绑定功能

@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@Import({ BindingBeansRegistrar.class, BinderFactoryConfiguration.class})
@EnableIntegration
public @interface EnableBinding {

	/**
	 * A list of interfaces having methods annotated with {@link Input} and/or
	 * {@link Output} to indicate binding targets.
	 */
	Class<?>[] value() default {};

}

上述为@EnableBinding注解源码

  • 包含@Configuration注解,所以被它注解的类也会成为Spring的基本配置类;
  • 包含@Import注解,加载了Spring Cloud Stream运行需要的几个基础类:
    + ChannelBindingServicrConfiguration:加载消息通道绑定必要的一些实例,如:处理消息通道绑定的ChannelBindingService实例、消息类型转换器MessageConverterConfigurer、消息通道工厂BindableChannelFactory等;
    • BindingBeansRegistrar:是ImportBeanDefinitionrigistrar的实现,主要在Spring加载Bean时被调用,用来实现加载更多的bean;此处其在加载用于消息驱动的基础类后,会根据@EnableBinding的value属性加载更多的类;
    • BinderFactoryConfiguration:Binder工厂的配置,主要用来加载与消息中间件相关的配置信息。从应用工程的META-INFO/spring.binders中加载针对具体消息中间件相关的配置文件等;

绑定消息通道

  • 在接口中用@Input@Output定义消息通道,其value属性可以定义消息通道名称;
  • @EnableBinding(value = {xxxx.class}),上述接口可以在此处指定,在应用启动时,实现对消息通道的绑定。

输出通道返回MessageChannel接口对象,其中定义了消息通道发送消息的方法;
输入通道需要返回SubscribableChannel接口对象,其扩展了MessageChannel,新增了维护消息通道订阅者的方法。

public interface MyChannel {
    String INPUT = "input";
    String OUTPUT = "output";

    @Input(INPUT)
    SubscribableChannel input();

    @Output(OUTPUT)
    MessageChannel output();
}

@Slf4j
@EnableBinding(value = {MyChannel.class})
public class MyReceiver {

    @StreamListener(MyChannel.INPUT)
    private void reveive(Object messageObject){
        log.info("Reveived: {}", messageObject);
    }
}

注入绑定接口

对上面代码中声明对MyChannel接口,我们可以如下调用

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
@WebAppConfiguration
public class StreamTest {

    @Autowired
    private MyChannel myChannel;

    @Test
    public void contextLoads(){
        myChannel.output().send(MessageBuilder.withPayload("From MyChannel").build());
    }
}

注入消息通道

除了注入绑定接口,我们还可以指定消息通道进行注入:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
@WebAppConfiguration
public class StreamTest {
    @Autowired
    private MessageChannel output;
    
    @Test
    public void channelSend(){
     output.send(MessageBuilder.withPayload("From MyChannel").build());
    }
}

⚠️:参数命名需要与通道同名;或者使用@Qualifier注解进行指定。

消息生产与消费

Spring Integration原生支持

@ServiceActivator@InboundChannelAdapter

@EnableBinding(value = {MyChannel.class})
public class OriginSpringIntegrationReceiver {

    @ServiceActivator(inputChannel = MyChannel.INPUT)
    public void receive(Object message){
        System.out.println((Message) message);
    }

    @Bean
    @InboundChannelAdapter(value = MyChannel.OUTPUT, poller = @Poller(fixedDelay = "2000"))
    public MessageSource<Date> timerMessageSource(){
        return () -> new GenericMessage<>(new Date());
    }
}
  • 消费者中,@ServiceActivator代替了@StreamListener;
  • 生产者中,@InboundChannelAdapter定义了该方法是对MyChannel.OUTPUT通道的输出绑定;poller属性将该方法设置为轮询执行,此例中,轮询频率为2s。

通过@Transform对指定通道的消息进行转换

@Transformer(inputChannel = MyChannel.INPUT, outputChannel = MyChannel.INPUT)
    public Object transform(Date message){
        return new SimpleDateFormat(("yyyy-mm-dd HH:mm:ss")).format(message);
    }

@StreamListener详解

  • 消息转换
    @StreamListener内置了一系列消息转换功能,相较于@ServiceActivator,基于@StreamListener注解的消息处理模型更简单。

通常来说,消息生产者都会以JSON或XML字符串的形式来发送消息,当消息到达时,消费者的输入通道的监听器需要对字符串进行一定处理,转换成具体对象,再做后续处理。

用Spring Integration原生注解,需要使用@ServiceActivator和@Transformer两个注解才能完成;而使用@StreamListener,只需如下编写即可:

@EnableBinding(value = {MyChannel.class})
public class MyReceiver {

    @StreamListener(MyChannel.INPUT)
    private void reveive(User user){
        System.out.println(user.getName() + user.getAge());
    }
}

@DATA
public class User{
    private String name;
    private int age;
}

上例中,省略了消息生产者代码,我们只需知道其发送的消息格式是:
{\"name\":\"yanzy\", \"age\":\"25\"}
即可;
要使上述类型转换生效,还需在配置文件中配置:
spring.cloud.stream.bindings.input.content-type=application/json

  • 消息反馈
    有时,处理完消息后,需要反馈一个消息给对方,此时可通过@SendTo注解来指定返回内容的输出通道。
@StreamListener(MyChannel.INPUT)
    @SendTo(MyChannel.OUTPUT)
    private Object receive(Object messageObject){
        System.out.println((Message)messageObject);
        return (Message)messageObject;
    }

上述代码,会将receive方法监听到到消息转换格式后,再次作为消息发送到output通道中。

若使用Spring Integration原生注解@ServiceActivator,可以使用其属性outputChannel来配置输出通道。

响应式编程

基于RxJava的响应式编程来处理消息的输入和输出。
1)pom中引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-rxjava</artifactId>
    <version>1.0.2.RELEASE</version>
</dependency>

2)改造消息监听

    @EnableRxJavaProcessor
public class RxJavaReceiver {
    @Bean
    public RxJavaProcessor<String, String> processor(){
        return inputStream -> inputStream.map(data -> {
            return data;
        }).map(data -> String.valueOf("From Input Channel Return - " + data));
    }
}

@EnableRxJavaProcessor标识当前类中应该提供一个RxJavaProcessor实现的Bean;
RxJavaProcessor接口定义了一个用来处理输入通道和返回内容输出通道的process方法。

下面的具体使用场景可能帮助你更好地理解响应式编程的意思

希望消息监听者在接收到5条消息后才将结果返回给输出通道

@EnableRxJavaProcessor()
public class RxJavaReceiver {
    @Bean
    public RxJavaProcessor<String, String> cacheProcessor(){
        return inputStream -> inputStream.map(data -> {
            return data;
        }).buffer(5).map(data -> String.valueOf("From Input Channel Return - " + data));
    }
}

消费组和消息分区

  • 消费组
    订阅同一个主题的一组应用实例,只有一个实例会消费发出的消息。

在消费者端,使用spring.cloud.stream.bindings.[input].group配置属性,即可实现为消费者设置消费组。

  • 消息分区
    一组带有相同特性信息的消息,即处于同一分区的消息,能被指定的实例消费。

实现方式:

  1. 在消费者应用中,增加配置:
# 开启消费者分区功能
spring.cloud.stream.bindings.album-notification-in.consumer.partitioned=true
# 指定当前消费者总实例个数
spring.cloud.stream.instance-count=2
# 设置当前实例索引值,可通过运行参数来为不同实例设置不同索引值
spring.cloud.stream.instance-index=0

2)生产者应用中,增加配置:

# 指定分区键的表达式规则,支持SpEL
spring.cloud.stream.bindings.output.producer.partition-key-expression=[xxxx]
# 指定消息分区的数量
spring.cloud.stream.bindings.output.producer.partition-count=2

消息类型

Spring Cloud Stream在输出消息头信息中,定义了一个默认值:contentType。

对于不支持头信息的消息中间件,Spring Cloud Stream提供了自己的实现机制-消息发出前,自动将其包装进自定义的消息封装格式中,并加入头信息。这样,对于不支持头信息的消息中间件,Spring Cloud Stream构建的服务也可以接收和处理包含符合规范的头信息的消息。

设置消息内容类型:
spring.cloud.stream.bindings.<channelName>.content-type

自带支持的常用消息类型转换:

  • Json与POJO的互相转换;
  • Json与org.springframework.tuple.Tuple的相互转换;
  • Object与byte[]的相互转换,Object需要可序列化;
  • String与byte[]的互相转换;
  • Object向纯文本转换,Object需要实现toString()方法

MIME类型

互联网媒体类型,Internet Media Type,又称MIME。

常见的类型有:

  • application/json
  • text/plain;charset=UTF-8
  • application/x-java-object;type=java.util.Map

Spring Cloud Stream提供的开箱即用的类型转换器
在这里插入图片描述

消息类型转换只会在需要转换时触发,如:当服务模块产生了一条头信息为application/json的XML字符串消息,Spring Cloud Stream不会将该XML字符串转换为Json。

⚠️:开发时,尽量在输出通道中做消息转换,因为输入通道的消费者使用的@StreamListener可以自动将POJO进行转换。

支持自定义消息转换器:
实现org.springframework.messaging.converter.MessageConvertor接口的自定义转换器会被加载到消息转换工厂中,以提供给消息处理时使用。

绑定器详解

绑定器SPI

涵盖了一套可插拔的用于连接外部中间件的实现机制。

  • Binder接口
    将输入和输出连接到外部中间件的抽象
public interface Binder<T, C extends ConsumerProperties, P extends ProducerProperties> {
	Binding<T> bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties);
	Binding<T> bindProducer(String name, T outboundBindTarget, P producerProperties);

}

1)生产者调用bindProducer(String name, T outboundBindTarget, P producerProperties)绑定输出通道时:第一个参数为消息中间件的目标名称(destination);第二个参数为发送消息的本地通道实例;第三个参数为创建通道时使用的属性配置(如分区键表达式等)。

2)消费者调用bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties)绑定输入通道时:第一个参数为消息中间件的目标名称;第二个参数为消费组名;第三个参数为本地通道实例;第四个参数为创建通道时使用的属性配置。

一个典型的Binder绑定器实现,一般包括:

  • 一个实现Binder接口的类;
  • 一个Spring配置加载类,用来创建连接消息中间件的基础结构使用的实例;
  • 一个或多个能够在classpath下的META-INF/spring.binders路径找到的绑定器定义文件。

多绑定器配置

  • 配置不同类型的绑定器
    使用RabbitMQ设置默认绑定器
    spring.cloud.stream.defaultBinder=rabbit
    设置了默认绑定器后,为其它一些少数消息通道单独设置绑定器
    spring.cloud.stream.bindings.<ChannelName>.binder=kafka

上述rabbitkafka值为每个绑定器实现的META-INF/spring.binders文件中定义的标识(一个绑定器实现可定义多个标识,用逗号分隔)

  • 配置同一类型的多个绑定器
spring.cloud.stream.bindings.input.binder=rabbit1
spring.cloud.stream.bindings.output.binder=rabbit2

spring.cloud.stream.binders.rabbit1.type=rabbit
spring.cloud.stream.binders.rabbit1.environment.spring.rabbitmq.host=192.168.0.101
spring.cloud.stream.binders.rabbit1.environment.spring.rabbitmq.port=5672
spring.cloud.stream.binders.rabbit1.environment.spring.rabbitmq.username=guest
spring.cloud.stream.binders.rabbit1.environment.spring.rabbitmq.passowrd=guest

spring.cloud.stream.binders.rabbit2.type=rabbit
spring.cloud.stream.binders.rabbit2.environment.spring.rabbitmq.host=192.168.0.102
spring.cloud.stream.binders.rabbit2.environment.spring.rabbitmq.port=5672
spring.cloud.stream.binders.rabbit2.environment.spring.rabbitmq.username=guest
spring.cloud.stream.binders.rabbit2.environment.spring.rabbitmq.passowrd=guest

当采用显式配置方式会自动禁用默认绑定器配置。
上述绑定器配置主要有以下四个属性:

  • spring.cloud.stream.binders.<configurationName>.type:指定绑定器类型;
  • spring.cloud.stream.binders.<configurationName>.environment:可以直接用来设置绑定器的属性,默认为空;
  • spring.cloud.stream.binders.<configurationName>.inheritEnvironment:用来配置当前绑定器是否继承应用程序自身的环境配置,默认为true
  • spring.cloud.stream.binders.<configurationName>.defaultCandidata:用来设置当前绑定器配置是否被视为默认绑定器的候选项,默认为true,表示当前配置影响默认配置。
    ⚠️:environment下,可以配置RabbitProperties下的配置选项,以spring.rabbitmq.为前缀。

常用配置如下:

参数名 说明 默认值
host rabbitmq host id localhost
port 端口 5672
username 登录用户名 guest
password 登录密码 guest
virtual-host 连接到代理时用的虚拟主机
addresses 逗号分隔的字符串,内容为客户端应该连接的地址列表
publisher-confirms 设置channel进入confirm模式

channel的confirm模式,使该通道上发布的所有消息都会带有唯一ID,一旦消息被投递到所有匹配的队列中时,broker会发送一条确认信息给生产者;若消息和队列时可持久化的,那确认消息会在将消息写入磁盘后发出,broker回传的确认消息中包含一个deliver-tag域,包含了消息的序列号,此外basic.ack-multiple域表示序列号之前的所有消息都已经得到了处理。
confirm模式是异步的,生产者可以在等待确认信息同时发送吓一条消息。得到确认信息后,生产者应用通过回调方法来处理确认消息,若rabbitmq由于自身内部错误导致消息丢失,会发送一条 nack消息,生产者应用程序同样可以通过回调方法处理nack消息。
在confirm模式下,所有消息都会被confirm或nack一次。
confirm无法回滚,一旦服务器崩溃,生产者无法得到confirm信息,只能通过重新发送消息来确保消息不丢失,若原先消息已被消费,则会有消息重复消费的问题,需要支持去重。

RabbitMQ与Kafka绑定器

配置详解

可以通过Spring Boot支持任何配置方式来进行:包括环境变量、YAML或properties文件等。

基础配置

都以spring.cloud.stream为前缀

参数名 说明 默认值
instanceCount 应用程序部署的实例数量。当使用kafka时需要设置分区 1
instanceIndex 应用实例的索引,值从0开始,最大值设置为-1。当使用分区和kafka的时候使用
dynamicDestinations 动态绑定目标列表,该列表默认为空,当设置了具体列表之后,只有列表中的目标才能被发现
defaultBinder 默认绑定器配置,在应用程序中有多个绑定器时使用
overrideCloudConnectors 该属性只适用于激活cloud配置并提供了Spring Cloud Stream的应用,false时,绑定器会自动检测合适的服务来绑定;true时,绑定器会忽略绑定的服务而是依赖应用程序中的设置属性来进行绑定和连接 false

绑定通道配置

1)通用配置
适用于输入和输出通道,通过spring.cloud.stream.bindings.<channelName>.前缀配置。
源码类BindingProperties中存有所有可配置属性。

参数名 说明 默认值
destination 消息通道绑定在消息中间件中的目标名称,如rabbitmq中为exchange名。消费者可以配置多个,以逗号隔开。若为设置该属性,将使用通道名
group 设置绑定通道的消费组,主要作用于输入通道 null
contentType 设置绑定通道的消息类型 null
binder 存在多个绑定器时,指定当前通道使用的绑定器 null

2)消费者配置
spring.cloud.stream.bindings.<channelName>.consumer前缀,仅对输入通道有效。
源码类ConsumerProperties中存有所有可配置属性。

参数名 说明 默认值
concurrency 输入通道消费者的并发数 1
partitioned 来自生产者的消息是否采用分区 false
headerMode 设置为raw时,禁用对消息头的解析。仅在使用不支持消息头功能的中间件有效,因为Spring Cloud Stream默认会解析嵌入的头部 embeddedheaders
maxAttempts 对输入通道消息处理最大重试次数 3
backOffInitialInterval 重试消息处理的初始间隔时间 1000
backOffMaxInterval 重试消息处理的最大间隔时间 10000
backOffMultiplier 重试消息处理时间间隔的递增乘数 2.0

3)生产者配置
spring.cloud.stream.bindings.<channelName>.producer前缀,仅对输出通道有效。
源码类ProducerProperties中存有所有可配置属性。

参数名 说明 默认值
partitionKeyExpression 配置输出通道分区键的spEL表达式。partitionCount配置为大于1才能生效。与partitionKeyExtractorClass互斥 null
partitionKeyExtractorClass 指定分区选择器接口partitionSelectorStrategy的实现,与上述参数互斥。若两者都不设置,分区选择计算规则为hashCode(key)%partitionCode,此处key根据partitionExpression或partitionKeyExtractorClass的配置计算得到 null
partitionSelectorExpression 配置自定义分区选择器的SpEL表达式。与上述参数互斥,默认规则与上述相同 null
partitionCount 当分区功能开启时,配置消息数据的分区数;若消息生产者已经配置了分区键的生成策略,那么它的值必须大于1 1
headerMode 和消费者中的此参数功能一致 embeddedHeaders

绑定器配置

RabbitMQ配置

  • 通用配置
    spring.rabbitmq为前缀。更多配置可通过RabbitBinderConfigurationProperties类源码查看。
参数名 说明 默认值
adminAdresses 配置rabbitmq管理插件的URL,多个用逗号分隔,只有在nodes参数包含多个时使用,此处配置的内容必须在spring.rabbit.addresses 中存在
nodes 配置rabbitmq的节点名称,多个用逗号分隔,配置多个时,可以用来定位队列所在服务器地址。配置的内容必须在spring.rabbitmq.addresses中存在
compressionLevel 绑定通道的压缩级别,选值及含义,可见java.util.zip.Defaulter中定义 1
  • 消费者配置
    spring.cloud.stream.rabbit.bindings.<channelName>.consumer.为前缀。
    源码类RabbitConsumerProperties存有所有可配置属性。
参数名 说明 默认值
autoBindDlq 是否自动声明(死信队列),并绑定到DLX上;重新消费,只需在控制台进入DLQ,使用Move Message功能 ;代码中抛出AmqpRejectAndDontRequeueException会将消息放入DLQ队列; false
deadLetterExchange 声明dlx名 DLX
dlqTtl dlq中消息存储的有效时间;超过该时间,消息自动从死信队列中移除 null
republishToDlq 为true时,在将消息转储到dlq时,在header中加入错误信息 false
requeueRejected 为true时,消费失败后,不会将消息抛弃,而是重新入队,所以消费的逻辑会被重复执行,直到消费成功 false
durableSubscription 用来设置订阅是否被持久化,仅在group被设置时有效 true
prefix 用来设置统一的目标和队列名称前缀
recoveryInterval 设置恢复连接的尝试时间间隔,毫秒 5000
requeueRejected 设置消息传递失败时重传 true
requestHeaderPatterns 用来设置需要被传递的请求头信息 [STANDARD_REQUEST_HEADERS, ‘*’]
replyHeaderPatterns 用来设置需要被传递的响应头信息 [STANDARD_REPLY_HEADERS, ‘*’]
transacted 是否开启channel-transacted,即是否在消息中使用事务 false
txSize 设置transaction-size的数量,当acknowledgeMode为AUTO时,容器会在处理txSize数目消息之后才开始应答 1

在消息队列失败处理的方案中,我们可以选择:
1)默认重试机制
通常重试只能解决因环境不稳定等外在因素导致的失败情况;
2)自定义异常处理逻辑

/**
     * 消息消费失败的降级处理逻辑
     *
     * @param message
     */
    @ServiceActivator(inputChannel = "test-topic.[groupName].errors")
    public void error(Message<?> message) {
        log.info("Message consumer failed, call fallback!");
    }

其中[groupName]为配置的group名。
3)使用DLQ队列
4)重新入队
消息消费失败后,不会将消息抛弃,而是重新放入队列,消费消息的逻辑会重复执行,直到消息消费成功。它的重试逻辑和通过maxAttamps配置的消息不同,它是收到多次消息而实现的重试;默认的重试规则是对处理逻辑的重试,所有处理逻辑都是由同一条消息触发。
为防止消息一直消费不成功导致的堆积问题,可以使用dlq,在指定的场景,抛出AmqpRejectAndDontRequeueException,将消费不成功的消息放入dlq中。

同一通道根据消息内容分发不同的消费逻辑

@StreamListener(value = TestTopic.INPUT, condition = "headers['version']=='2.0'")
        public void receiveV2(String payload, @Header("version") String version) {
            log.info("Received v2 : " + payload + ", " + version);
        }

注意此处的condition属性,它是分发消费逻辑的关键。

  • 生产者配置

源码类RabbitProducerProperties,以spring.cloud.stream.rabbit.bindings.<channelName>.producer.开头

参数名 说明 默认值
autoBindDlq 设置是否自动声明DLQ,并绑定到DLX false
batchingEnabled 是否启动消息批处理 false
batchSize 设置缓存的批处理消息数量 100
batchBufferLimit 批处理缓存限制 10000
batchTimeout 批处理超时时间 5000
compress 消息发送时是否启用压缩 false
deliveryMode 消息发送模式 PRESISTENT
prefix 设置统一的目标前缀
requeueRejected 设置消息传递失败时重传 true
requestHeaderPatterns 用来设置需要被传递的请求头信息 [STANDARD_REQUEST_HEADERS, ‘*’]
replyHeaderPatterns 用来设置需要被传递的响应头信息 [STANDARD_REPLY_HEADERS, ‘*’]

下面是一个本人实践的配置:

spring:
  cloud:
    stream:
      binders:
        business:
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: @rabbitmq.host@
                port: @rabbitmq.port@
                username: guest
                password: guest
                virtual-host: /
                publisher-confirms: true
      bindings:
        album_purchase_input:
          binder: business
          destination: business.group.checkin.confirm.album.ttl.direct.exchange.test
          group: queue.${spring.profiles.active}
          consumer:
            concurrency: 10
            max-attempts: 15
            back-off-initial-interval: @back.off.initial.interval@
            back-off-multiplier: @back.off.multiplier@
            back-off-max-interval: @back.off.max.interval@
        album_refund_input:
          binder: business
          destination: business.group.checkin.refund.album.ttl.direct.exchange.test
          group: queue.${spring.profiles.active}
          consumer:
            concurrency: 10
            max-attempts: 15
            back-off-initial-interval: @back.off.initial.interval@
            back-off-multiplier: @back.off.multiplier@
            back-off-max-interval: @back.off.max.interval@
      rabbit:
        bindings:
          album_purchase_input:
            consumer:
              binding-routing-key: 201
              exchange-type: direct
              max-concurrency: 20
              prefetch: 5
              auto-bind-dlq: true
              republish-to-dlq: true
              dead-letter-exchange: ${spring.cloud.stream.bindings.album_purchase_input.destination}.${spring.cloud.stream.bindings.album_purchase_input.group}.DLX
          album_refund_input:
            consumer:
              binding-routing-key: 201
              exchange-type: direct
              max-concurrency: 20
              prefetch: 5
              auto-bind-dlq: true
              republish-to-dlq: true
              dead-letter-exchange: ${spring.cloud.stream.bindings.album_refund_input.destination}.${spring.cloud.stream.bindings.album_refund_input.group}.DLX

猜你喜欢

转载自blog.csdn.net/yzy199391/article/details/88551718