上篇文章我们看了Spring Cloud Stream的基本使用,但是上篇文章中的消息我们是从RabbitMQ的web管理页面发来的,如果我们想要从代码中发送消息该如何实现呢?
自定义消息通道
上一篇我们使用的消息通道实际上是系统默认提供的,就是那个Sink.class,它里面有一个常量叫做INPUT,接下来我们来看一下自己定义消息通道,我们来看看怎么弄。
我们先来看一下,实际上默认情况下这里是定义了两个东西,一个是Sink,它是用来定义输入通道的:
除此之外还有一个叫Source的,它是用来定义输出通道的:
旧版本写法
因为这里的输入通道和输出通道不是同一个通道,所以如果要自己发消息,这里我们就模仿Sink和Source,来定义一个自己的消息通道MyChannel:
public interface MyChannel {
//输入输出通道
String INPUT_CHANNEL="INPUT_CHANNEL";
String OUTPUT_CHANNEL="INPUT_CHANNEL";
//输入通道
@Input(INPUT_CHANNEL)
SubscribableChannel input();
//输出通道
@Output(OUTPUT_CHANNEL)
MessageChannel output();
}
新版本写法
这里把输入通道和输出通道设置一样的值,因为要在输出通道发送消息,在输入通道接收消息,所以他们两个必须在一个通道上,但是这是F版以前的写法,现在我们用的Spring Cloud是G版,在G版中以通道的名字作为实例的名字,来自动创建相应的实例,实例的名字是不可以重复的,所以当它去创建OUTPUT_CHANNEL的时候,发现这个名字已经存在了,就好抛出异常,所以在新版中不能再用以上方法,而是像如下这样:
public interface MyChannel {
//输入输出通道
String INPUT_CHANNEL="INPUT_CHANNEL";
String OUTPUT_CHANNEL="OUTPUT_CHANNEL";
//输入通道
@Input(INPUT_CHANNEL)
SubscribableChannel input();
//输出通道
@Output(OUTPUT_CHANNEL)
MessageChannel output();
}
但是这样你就会发现不在同一个通道,消息就收不到了。那么怎么办?从F版开始,需要我们在配置文件再额外的添加如下配置:
#NPUT_CHANNEL通道的名字,最后的值随意写,但是两个通道的值要一致
spring.cloud.stream.bindings.INPUT_CHANNEL.destination=aaa-topic
spring.cloud.stream.bindings.OUTPUT_CHANNEL.destination=aaa-topic
然后我们定义一个消息接收类,如下:
@EnableBinding(MyChannel.class)
public class MyReceiver {
@StreamListener(MyChannel.INPUT_CHANNEL)
public void recevie(Object object){
System.out.println(object);
}
}
最后再写一个HelloController用于发送消息:
@RestController
public class HelloController {
/*
* 注意这个MyChannel会自动的注入到spring容器中,你不需要在MyChannel接口上加任何注解
* */
@Autowired
MyChannel myChannel;
@GetMapping("/hello")
public void hello(){
//发送一条消息
myChannel.output().send(MessageBuilder.withPayload("hello stream hehehe!").build());
}
}
测试:
在浏览器访问http://localhost:8080/hello这个接口,发送HelloController中的hello接口中的消息,然后接收类已经接收到了消息,并且打印在了控制台:
这样就实现了一个自己发消息,自己收消息的自定义消息通道。
消费组
由于我们的服务可能有多个实例同时在运行,如果不做任何处理,此时发送一条消息将会被所有的实例收到,但是有的时候我们可能只希望消息被一个实例所接收。
首先,在MyReceiver中注入端口号,方便去区分:
然后把stream-hello1这个项目打包并且启动8080和8082两个实例。
然后在浏览器执行http://localhost:8080/hello请求发送消息,这时候我们可以看见启动的这两个服务都收到了消息:
这样的话有时候就会不符合需求,如果我希望发送一条消息只被一个执行。这个需求我们可以通过消息分组来解决。我们需要给项目配置消息组。
#分组信息,在消息发送和接收的地方都要加
spring.cloud.stream.bindings.INPUT_CHANNEL.group=g1
spring.cloud.stream.bindings.OUTPUT_CHANNEL.group=g1
启动后这次发送好的消息就只会被其中的一个实例消费,至于被哪个消费,每次是不确定的,这里是被8080的实例消费了:
消息分区
有的时候,我们可能需要相同特征的消息能够总是被发送到同一个消费者上去处理,如果我们只是单纯的使用消费组则无法实现功能,此时我们需要借助于消息分区,消息分区之后,具有相同的特征的消息就可以总是被同一个消费者处理了,在前面消费分组配置的基础上在做如下配置:
在消费者上添加如下配置:
#表示开启消息分区
spring.cloud.stream.bindings.INPUT_CHANNEL.consumer.partitioned=true
#表示当前消息的总实例个数
spring.cloud.stream.instance-count=2
#表示当前实例的索引,从0开始,当我们启动多个实例时需要在命令行配置索引
spring.cloud.stream.instance-index=0
然后在给消息生产者添加如下配置(因为我这里消息消费者和生产者都创建在同一个工程,所以都在这个工程的配置文件下配置):
#通过消息内容自动查看消息的分区
spring.cloud.stream.bindings.OUTPUT_CHANNEL.producer.partitionKeyExpression=payload
spring.cloud.stream.bindings.OUTPUT_CHANNEL.producer.partitionCount=2