RabbitMQ第三天

3.SpringCloudStream集成

SpringCloudStream框架由于编程方式非常简单,所以在很多技术非常扎实的大型企业中,SpringCloudStream框架的使用频率会比SpringBoot框架更高。

3.1定义

SpringCloudStream 是一个构建高扩展和事件驱动的微服务系统的框架,用于连接共有消息系统,官网地址: https://spring.io/projects/spring-cloud-stream 。整体上是把各种花里胡哨的MQ产品抽象成了一套非常简单的统一的编程框架,以实现事件驱动的编程模型。社区官方实现了RabbitMQ,Apache Kafka,Kafka Stream和Amazon Kinesis这几种产品,而其他还有很多产品比如RocketMQ,都是由产品方自行提供扩展实现。所以可以看到,对于RabbitMQ,使用SpringCloudStream框架算是一种比较成熟的集成方案。但是需要主要注意的是,SpringCloudStream框架集成的版本通常是比RabbitMQ落后几个版本的,使用时需要注意。

SpringCloudStream框架封装出了三个最基础的概念来对各种消息中间件提供统一的抽象:

a)Destination Binders:负责集成外部消息系统的组件。

b)Destination Binding:由Binders创建的,负责沟通外部消息系统、消息发送者和消息消费者的桥梁。

c)Message:消息发送者与消息消费者沟通的简单数据结构。

可以看到,这个模型非常简单,使用时也会非常方便。但是简单,意味着SCStream中的各种概念模型,与RabbitMQ的基础概念之间是有比较大的差距的,例如Exchange、Queue这些原生概念,集成到SCStream框架时,都需要注意如何配置,如何转换。

3.2引入依赖

RabbitMQ的SpringCloudStream支持是由Spring社区官网提供的,所以这也是相当成熟的一种集成方案。但是要注意,SpringCloudStream框架集成的版本通常是比RabbitMQ产品本身落后几个版本的,使用时需要注意。

他的核心依赖也就一个:

这两个Maven依赖没有什么特别大的区别,实际上,他们的github代码库是在一起的。仓库地址:https://github.com/spring-cloud/spring-cloud-stream-binder-rabbit

依赖的版本通常建议使用SpringCloud的整体版本控制。 org.springframework.cloud#spring-cloud-dependencies#Hoxton.SR6,这样各个组件之间的版本比较安全。不建议贸然尝试新版本。

3.3基础使用方法

使用SCStream框架集成RabbitMQ,编程模型非常的简单。我们先在本地搭建一个RabbitMQ服务,按照以下三步就可以完成与RabbitMQ的对接。

1、声明Sink 消息消费者

2、使用Source 消息生产者 发送消息

3、在SpringBoot的配置文件中,增加配置

这样三个步骤,就完成了与本地RabbitMQ的对接。

接下来增加SpringBoot启动类,以及测试用的Controller就可以启动测试了。

启动应用后,访问Controller提供的测试端口 http://localhost:8080/send?message=123 。后台就能收到这个消息。

这里可以看到,当前消费者不光收到了MQ消息,还收到了一些系统事件。这些系统事件需要添加@EventListener注解才能接收到。

3.4理解SpringCloudStream干了些什么

非常简单的几行代码,你甚至都不需要感知RabbitMQ的存在,就完成了与RabbitMQ的对接。但是,简单的背后,意味着,如果你要深入使用更多功能,需要有更扎实的技术基础,对SpringCloudStream有更深入的了解。

我们先来了解下,在刚才的简单示例当中,SpringCloudStream都干了些什么事情。

1、配置RabbitMQ服务器

在SpringBoot的autoconfigure包当中,有个 RabbitProperties类,这个类就会解析application.properties中以spring.rabbitmq开头的配置。里面配置了跟RabbitMQ相关的主要参数,包含服务器地址等。里面对每个参数也都提供了默认值。默认就是访问本地的RabbitMQ服务。

2、在RabbitMQ中声明Exchange和Queue

既然是要对接RabbitMQ,那么最终还是需要与RabbitMQ服务器进行交互的。从RabbitMQ的管理页面上来看,SCStream帮我们在RabbitMQ的根虚拟机上创建了一个topic类型的scstreamExchange交换机,然后在这个交换机上绑定了一个scstreamExchange.stream队列,绑定的RoutingKey是#。 而程序中的消息发送者是将消息发送到scstreamExchange交换机,然后RabbitMQ将消息转发到scstreamExchange.stream队列,消息接收者从队列接收到消息。这个流程,就是Spring Cloud Stream在背后为我们做的事情。 在这里可以尝试对应RabbitMQ的基础概念以及SCStream框架中的基础概念,整理一下他们之间的对应关系。

其实这个示例也演示了SCStream的特点。SCStream框架帮我们屏蔽了与消息中间件的交互细节,开发人员甚至都不需要感知消息中间件的存在,将更多的关注点放到业务处理的细节里。实际上,就我们这个简单的示例,只需要将maven中的spring-cloud-starter-stream-rabbit依赖,换成spring-cloud-starter-stream-kafka,就可以完成与本地Kafka服务的交互,代码不需要做任何的改动。

3.5深入玩转SCStream

这里需要注意,SCStream框架的设计目的,是为了实现一套简明的事件驱动模型。在这个事件驱动模型中,是没有RabbitMQ中这些Exchange、queue或者是kafka中的Topic之类的这些功能的,所以这也意味着,如果想要使用RabbitMQ的一些特性功能,比如Quorum队列,死信队列,懒加载队列等,反而会比较麻烦。这就需要对各种基础概念有更深的了解。

1、配置Binder

SCStream是通过Binder来定义一个外部消息服务器。具体对于RabbitMQ来说,Binder就是一个Exchange的抽象。默认情况下,RabbitMQ的binder使用了SpringBoot的ConnectionFactory,所以,他也支持spring-boot-starter-amqp组件中提供的对RabbitMQ的所有配置信息。这些配置信息在application.properties里都以spring.rabbitmq开头。

而在SCStream框架中,也支持配置多个Binder访问不同的外部消息服务器(例如同时访问kafka和rabbitmq,或者同时访问rabbitmq的多个virtual-host),就可以通过Binder进行定义。配置的方式都是通过spring.cloud.stream.binders.[bindername].environment.[props]=[value]的格式来进行配置。另外,如果配置了多个binder,也可以通过spring.cloud.stream.default-binder属性指定默认的binder。

例如:

这个配置方式就配置了一个名为testbinder的Binder。

2、Binding配置

Binding是SCStream中实际进行消息交互的桥梁。在RabbitMQ中,一个binding可以对应消费者的一个queue,在发送消息时,也可以直接对应一个exchange。在SCStream中,就是通过将Binding和Binder建立绑定关系,然后客户端就之需要通过Binding来进行实际的消息收发。

在SCStream框架中,配置Binding首先需要进行声明。声明Binding的方式是在应用中通过@EnableBinding注解,向Spring容器中逐日一个Binding接口对象。在这个接口中,增加@Input注解指定接收消息的Binding,而通过@Output注解指定发送消息的Binding。在SCStream中,默认提供了Source、Sink、Processor三个接口对象,这三个对象都是简单的接口,可以直接拿来使用,当然也可以配置自己的Binding接口对象。

比如Source,他的定义就是这样的:

通过这个@Output直接,就声明出了一个Binding对象,他的名字就叫做output。对于RabbitMQ,就对应一个queue。SCStream就会将这个output声明为一个消息发送队列。

接下来就可以在应用中使用@EnableBinding(Source.class),声明这个Binding对象。接下来,就可以在Spring应用中使用@Autowired注入,然后通过source.output()方法获取到MesasgeChannel对象,进而发送消息了。

这时,如果不对output这个Binding做任何配置,应用启动后,会在RabbitMQ中声明出一个默认的exchange和queue。但是默认的名字都太奇怪,而且很多细节功能不够好用。所以,通常都会对这个Binding进行配置。配置的方式都是在application.properties中配置。所有配置都是按照spring.cloud.stream.binding.[bindingname].[props]=[value]的格式进行指定。

例如:

这样就指定了output这个Binding对应的Exchange。

注意2点:

a、如果不希望每个Binding都单独指定Binder,就可以配置默认的Binder。

b、对于binding,可以指定group所属组的属性。Group这个概念在RabbitMQ中是不存在的,但是SCStream会按照这个group属性,声明一个名为scstreamExchange.myoutput的queue队列,并与scstreamExchange建立绑定关系。

3、SCStream的分组消费策略

通过Binding,即可以声明消息生产者,也可以声明消息消费者。基础的配置方式是差不多的。参见之前的示例,不难理解。

下面重点来看下这个group组属性。消费者组的概念,在RabbitMQ中是不存在的。但是,如果你接触过Kafka或者RocketMQ,那么对于组,你就不会陌生了。SCStream中的消费者分组策略,其实整体来看是一种类似于Kafka的分组消费机制。即,不同group的消费者,都会消费到所有的message消息,而在同一个goup中,每个message消息,只会被消费一次。这种分组消费的策略,严格来说,在RabbitMQ中是不存在的,RabbitMQ是通过不同类型的Exchange来实现不同的消费策略。而使用SCStream框架,就可以直接在RabbitMQ中实现这种分组消费的策略。

例如这样,就声明了两个消费者组。consumer1,consumer2,consumer3是一个组,consuemr4是另一个组。接下来,可以自行验证一下消息的分发过程。

对于这种分组消费的策略,SCStream框架不光提供了实现,同时还提供了扩展。可以对每个组进行分区(partition,是不是感觉越来越像Kafka了?)。

例如做这样的配置:

通过这样的分组策略,当前这个消费者实例就只会消费奇数编号的消息,而偶数编号的消息则不会发送到这个消费者中。注意:这并不是说偶数编号的消息就不会被消费,只是不会被当前这个实例消费而已。

SCStream框架虽然实现了这种分组策略机制,但是其实是不太严谨的,当把分区数量和分区ID不按套路分配时,并没有太多的检查和日志信息,但是就是收不到消息。

另外,在@StreamListener注解中还有condition属性也可以配置消费者的分配逻辑,该属性支持一个SPELl表达式,只接收满足条件的消息。

4、使用原生消息转发机制

SCStream其实自身实现了一套事件驱动的流程。这种流程,对于各种不同的MQ产品都是一样的。但是,毕竟每个MQ产品的实现机制和功能特性是不一样的,所以,SCStream还是提供了一套针对各个MQ产品的兼容机制。

在RabbitMQ的实现中,所有个性化的属性配置实现都是以spring.cloud.stream.rabbit开头,支持对binder、producer、consumer进行单独配置。

通过这些配置可以按照RabbitMQ原生的方式进行声明。例如,SCStream自动创建的Exchange都是Topic类型的,如果想要用其他类型的Exchange交换机,就可以手动创建交换机,然后在应用中声明不自动创建交换机。

所有可配置的属性,参见github仓库中的说明。例如,如果需要声明一个Quorum仲裁队列,那么只要给这个Binding配置quorum.enabled属性,值为true就可以了。

Stream队列目前尚不支持。RabbitMQ周边生态的发展肯定是比产品自身的发展速度要慢的,由此也可见,目前阶段,Stream队列离大规模使用还是有一点距离的。

5、使用SCStream配置死信队列

死信(Dead letter)队列是RabbitMQ中的一个高级功能,所谓死信,就是长期没有人消费的消息。RabbitMQ中有以下几种情况会产生死信:

a)消息被拒绝(basic.reject/baskc.nack)并且设置消息不重新返回队列 (配置 spring.rabbitmq.listener.default-requeue-rejected=true=false 。这个属性默认是true,就是消息处理失败后,就会重新返回队列,后续重新投递。但是这里需要注意,如果队列已经满了,那就会循环不断的报错,这时候就要考虑死信了)

b)队列达到最大长度

c)消息TTL过期

在RabbitMQ中,有一类专门处理死信的Exchange交换机和Queue队列。通过RabbitMQ的死信队列功能,可以很好的用来实现延迟队列或者消息补发之类的功能。

RabbitMQ的死信队列实现机制,是在正常队列上声明一个死信交换机dlExchange,然后这个死信交换机dlExchange可以像正常交换机Exchange一样,去绑定队列,分发消息等。其配置方式,就是在队列中增加声明几个属性来指定死信交换机。而这几个队列属性,即可以在服务器上直接配置,也可以用原生API配置,还可以用SpringBoot的方式声明Queue队列来实现,并且在SCStream框架中也支持定制。主要就是这几个属性:

配置完成后,在管理页面也能看到队列信息:

这样配置完成后,在当前队列中的消息,经过3秒无人消费,就会通过指定的死信交换机mirror.dlExchange,分发到对应的死信队列中。

关于如何配置这些属性,在之前声明Quorum仲裁队列和Stream队列时,都有说明。

而在SCStream框架中,就可以通过以下的方式进行配置:

通过这样的一组配置,从output这个Binding发送的消息,经过3秒后,就会被input这个Binding对应的消费者消费到了。

6、扩展的事件监听机制

另外,在SCStream框架的Sink消费者端,还可以添加@EventListener注解。加入这个注解后,这个Sink消费者,不光可以消费MQ消息,还能监控很多Spring内的事件,像 AsyncConsumerStartedEvent、ApplicationReadyEvent(springBoot启动事件)、ServletRequestHandledEvent(请求响应事件)等等。而使用这些功能,我们可以将Spring的应用事件作为业务事件一样处理,这对于构建统一的Spring应用监控体系是非常有用的。

7、SCStream框架总结

对于事件驱动这个应用场景来说,SCStream框架绝对是一个举足轻重的产品。一方面,他极大的简化的事件驱动的开发过程,让技术人员可以减少对于不同MQ产品的适应过程,更多的关注业务逻辑。另一方面,SCStream框架对各种五花八门的MQ产品提供了一种统一的实现流程,从而可以极大的减少应用对于具体MQ产品的依赖,极大提高应用的灵活性。例如如果应用某一天想要从RabbitMQ切换成Kafka或者RocketMQ等其他的MQ产品,如果采用其他框架,需要对应用程序做非常大的改动。但是,如果使用SCStream框架,那么基本上就是换Maven依赖,调整相关配置就可以了。应用代码基本不需要做任何改动。

当然,SCStream框架使用非常方便的背后,也意味着更高的学习门槛。如果只是最简单的使用MQ产品,那你当然可以不用感知MQ产品的存在,就用SCStream框架进行快速的开发。但是,当你需要深入的使用MQ产品时,那就不光需要学习MQ产品本身,还需要学习具体MQ产品模型如何与SCStream的基础模型对应以及转换。这其实对技术反而提出了更高的要求。所以SCStream在一些技术非常扎实的大厂用得比较多,而在一些传统的IT企业,反而有点Hold不住,还不如原生API和SpringBoot集成来得更方便。

你也可以试试,通过学习,能不能Hold住这个神奇的框架。

你可能感兴趣的:(RabbitMQ第三天)