上一章大部分都介绍完了,这一章主要是代码的实现和一些细节。
RabbitMQ:
pom.xml
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> <version>1.3.0.RELEASE</version> </dependency>
配置文件:
producer:
server.port=8081 spring.application.name=producer #通道的主题 spring.cloud.stream.bindings.output.destination=stream-demo #发送消息的格式 spring.cloud.stream.bindings.output.contentType=text/plain
代码:
package com.yjp.stream.rabbitmq; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.messaging.Source; import org.springframework.messaging.support.MessageBuilder; /** * @EnableBinding, 该注解用来指定一个或多个定义了@Input 或@Output 注解的接口 * 以此实现对消息通道(Channel)的绑定。 * 在上面的例子中我们通过@EnableBinding(Source.class)绑定了 Source接口, * 该接口是 Spring CloudStream中默认实现的对输出消息通道绑定的定义. */ @EnableBinding(Source.class) public class Producer { @Autowired private Source source; /** * 消息发送者 * * @param payload 需要发送的消息 */ public void receive(Object payload) { //Source是一个接口 source.output().send(MessageBuilder.withPayload(payload).build()); } }
进入Source接口里面:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.stream.messaging; import org.springframework.cloud.stream.annotation.Output; import org.springframework.messaging.MessageChannel; public interface Source { String OUTPUT = "output"; @Output("output") MessageChannel output(); }
output通道,在我们的配置文件中spring.cloud.stream.bindings.XXX.destination=通道的名字
spring.cloud.stream.bindings.XXX.contentType=发送的格式
这里的XXX必须和@output里面的字符串的名字一致。这样在我们都用output这个方法的时候才能找到通道。
consumer:
server.port=8083 spring.application.name=rabbit-2 spring.cloud.stream.bindings.input.destination=stream-demo
代码:
package com.yjp.stream.rabbitmq; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.messaging.Sink; /** * @EnableBinding, 该注解用来指定一个或多个定义了@input 或@Output 注解的接口 * 以此实现对消息通道(Channel)的绑定。 * 在上面的例子中我们通过@EnableBinding(Sink.class)绑定了 Sink接口, * 该接口是 Spring CloudStream中默认实现的对输入消息通道绑定的定义. */ @EnableBinding(Sink.class) public class SinkReceiver { /** * 消息接受者 * * @param payload 接收到的消息 */ @StreamListener(Sink.INPUT) public void receive(Object payload) { System.err.println("------>" + payload); } }
进入Sink接口
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.stream.messaging; import org.springframework.cloud.stream.annotation.Input; import org.springframework.messaging.SubscribableChannel; public interface Sink { String INPUT = "input"; @Input("input") SubscribableChannel input(); }
我们在配置文件中spring.cloud.stream.bindings.XXX.destination=通道的名字
XXX必须和@Input注解中的名字保持一致。
我们也可以自定义注解
自定义MySource接口:
package com.yjp.stream.rabbitmq; import org.springframework.cloud.stream.annotation.Output; import org.springframework.messaging.MessageChannel; public interface MySource { @Output("myOutput") MessageChannel output(); }
发送:
package com.yjp.stream.rabbitmq; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.messaging.support.MessageBuilder; @EnableBinding(MySource.class) public class MyProducer { @Autowired private MySource mySource; /** * 消息发送者 * * @param payload 需要发送的消息 */ public void receive(Object payload) { mySource.output().send(MessageBuilder.withPayload(payload).build()); } }
配置:
server.port=8081 spring.application.name=producer #通道的主题 #spring.cloud.stream.bindings.output.destination=stream-demo #发送消息的格式 #spring.cloud.stream.bindings.output.contentType=text/plain spring.cloud.stream.bindings.myOutput.destination=stream-demo-1 spring.cloud.stream.bindings.myOutput.contentType=text/plainconsumer端自定义Sink接口
package com.yjp.stream.rabbitmq; import org.springframework.cloud.stream.annotation.Input; import org.springframework.messaging.SubscribableChannel; public interface MySink { String myInput = "myInput"; @Input("myInput") SubscribableChannel input(); }
接受:
package com.yjp.stream.rabbitmq; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; @EnableBinding(MySink.class) public class MySinkReceiver { @StreamListener(MySink.myInput) public void receive(Object payload) { System.err.println("myInput------>" + payload); } }
配置:
server.port=8082 spring.application.name=rabbit-1 spring.cloud.stream.bindings.myInput.destination=stream-demo-1 spring.cloud.stream.bindings.input.destination=stream-demo
这样就完成了我们的自定义注解,也可以在producer端一次向两个通道发送消息。
消息分组:
此时我们在启动一个消费者,和第一个一样,修改端口号,启动。此时producer往通道中发送消息的时候,两个消费者都是可以收到消息的。但是有些场景我们只想一个消费者收到消息,那么就涉及到分组的概念。
设置分组,在配置中设置就可以了,将两个消费者的组名设置为相同那么此时就只有一个消费者能够收到消息。
spring.cloud.stream.bindings.input.group=group
消息分区:
分完组后,有一个问题,我们无法控制消息被那个消费者消费,但是有些场景需要对一些具有相同特征的消息设置每次都被同一个消费实例处理,比如, 一些用于监控服务, 为了统计某段时间内消息生产者发送的报告内容, 监控服务需要在自身聚合这些数据, 那么消息生产者可以为消息增加一个固有的特征ID来进行分区, 使得拥有这些ID的消息每次都能被发送到一个特定的实例上实现累计统计的效果, 否则这些数据就会分散到各个不同的节点导致监控结果不一致的情况。当生产者将消息数据发送给多个消费者实例时, 保证拥有共同特征的消息数据始终是由同一个消费者实例接收和处理。
producer:修改配置
server.port=8081 spring.application.name=producer #通道的主题 spring.cloud.stream.bindings.output.destination=stream-demo #发送消息的格式 spring.cloud.stream.bindings.output.contentType=text/plain #以什么规则分区 # 通过该参数指定了分区键的表达式规则,我们可以根据实际的输出消息规则配置 SpEL来生成合适的分区键 spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload #分区数量 spring.cloud.stream.bindings.output.producer.partitionCount=2 spring.cloud.stream.bindings.myOutput.destination=stream-demo-1 spring.cloud.stream.bindings.myOutput.contentType=text/plain
两个消费者:
第一个配置:
server.port=8082 spring.application.name=rabbit-1 spring.cloud.stream.bindings.myInput.destination=stream-demo-1 spring.cloud.stream.bindings.input.destination=stream-demo spring.cloud.stream.bindings.input.group=group-1 #开启分区 spring.cloud.stream.bindings.input.consumer.partitioned=true #设置分区的数量 spring.cloud.stream.instance-count=2 #: 该参数设置当前实例的索引号, 从0开始,最大值为 spring.cloud.stream.instanceCount参数-1 spring.cloud.stream.instance-index=0
第二个配置:
server.port=8083 spring.application.name=rabbit-2 spring.cloud.stream.bindings.input.destination=stream-demo spring.cloud.stream.bindings.input.group=group-1 #开启分区 spring.cloud.stream.bindings.input.consumer.partitioned=true #设置分区的数量 spring.cloud.stream.instance-count=2 #该参数设置当前实例的索引号, 从0开始,最大值为 spring.cloud.stream.instanceCount参数-1 spring.cloud.stream.instance-index=1
这样就可以完成消息的分区了。
对指定通道的消息进行转换:
@Transformer(inputChannel = Sink.INPUT, outputChannel = Sink.INPUT) public Object transform(Date message) { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(message); }
这样就完成了一个消息的转换。
@StrearnListener这个注解:
@StrearnListener 和 @ServiceActivator 注解都实现了对输入消息通道的监听,但是@StearnListener 相比@ServiceActivator 更为强大, 因为它还内置了一 系列的消息转换功能, 这使得基于@StearnListener 注解实现的消息处理模型更为简单。
大部分情况下, 我们通过消息来对接服务或系统时, 消息生产者都会以结构化的字符串形式来发送, 比如JSON或XML。 当消息到达的时候, 输入通道的监听器需要对该字符串做一定的转化, 将JSON或XML转换成具体的对象, 然后再做后续的处理。。在消息消费逻辑执行之前,消息转换机制会根据消息头信息中声明的消息类型(即上面对 input 通道配置的content-type 参数,spring.cloud.stream.bindings.input.content-type=application/json), 找到对应的消息转换器并实现对消息的自动转换。
消息反馈:很多时候在处理完输入消息之后, 需要反馈 一个消息给对方, 这时候可以通过@SendTo 注解来指定返回内容的输出通道。
发送和接受方:
package com.yjp.stream.rabbitmq; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.messaging.Processor; import org.springframework.cloud.stream.messaging.Source; import org.springframework.messaging.support.MessageBuilder; @EnableBinding(value = Processor.class) public class ProcessReceiver { @Autowired private Source source; public void receive(Object payload) { source.output().send(MessageBuilder.withPayload(payload).build()); } @StreamListener(Processor.INPUT) public void receiveFromOutput(Object payload) { System.err.println(payload); } }
接受反馈:
package com.yjp.stream.rabbitmq; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.messaging.Processor; import org.springframework.messaging.handler.annotation.SendTo; @EnableBinding(value = Processor.class) public class ProcessReceiver { @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public Object receiveFromInput(Object payload) { return "From Input Channel Return - " + payload; } }
利用Transformer也可以做到,和SendTo的效果是一样的。
我们也可以自己重写Processor接口,来实现自己定义的通道。
package com.yjp.stream.rabbitmq; 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 SendProcessor { @Output("XXX") MessageChannel output(); @Input("XXX") SubscribableChannel input(); }在使用的时候将@EnableBinding中的需要注入的类换成重写的类就可以。@EnableBinding也可以同时注入多个类。
也可以做响应式编程:也可以达到我们消息反馈的效果,但是那个注解以及过时了就不在详细说明了。
当我们使用多个绑定器的时候,也就是多个队列的时候。可以将主要的队列设置为默认的。
举个栗子:将RabbitMQ设置为默认的绑定器
spring.cloud.stream.defaultBinder=rabbit
在设置了默认绑定器之后, 再为其他一些少数的消息通道单独设置绑定器, 比如:
spring.cloud.stream.bindings.input.binder=kafka
需要注意的是, 上面我们设置参数时用来指定具体绑定器的值并不是消息中间件的名称, 而是在每个绑定器实现的 META-INF/spring.binders 文件中定义的标识(一个绑定器实现的标识可以定义多个, 以逗号分隔), 所以上面配置的 rabb江和kafka 分别来自于各自的配置定义, 它们的具体内容如下所示:
rabbit:\org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration
kafka: \org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration
有需要可以自己设置。
当需要在一个应用程序中使用同一类型不同环境的绑定器时, 我们也可以通过配置轻松实现通道绑定。 比如, 当需要连接两个不同的 RabbitMQ 实例的时候, 可以参照如下配置:
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=
spring.cloud.stream.binders.rabbit1.environment.spring.rabbitmq.port=
spring.cloud.stream.binders.rabbit1.environment.spring.rabbitmq.username=
spring.cloud.stream.binders.rabbit1.environment.spring.rabbitmq.password=
spring.cloud.stream.binders.rabbit2.type=rabbit
spring.cloud.stream.binders.rabbit2.environment.spring.rabbitmq.host=
spring.cloud.stream.binders.rabbit2.environment.spring.rabbitmq.port=
spring.cloud.stream.binders.rabbit2.environment.spring.rabbitmq.username=
spring.cloud.stream.binders.rabbit2.environment.spring.rabbitmq.password=
剩下就是一些配置的说明。
这篇到这里就结束了。
努力吧,皮卡丘。