目前市面上有许多消息代理中间件(下面简称为消息代理),例如 ActiveMQ RabbitMQ、Kafka 、RocketMQ等,在使用这些框架时,我们需要调用它们的API用于发送和接收消息。本篇博客我们使用RabbitMQ作为示例。在单独使用RabbitMq时,我们需要创建RabbitMQ客户端连接,创建Channel,声明一个队列,然后通过Channel发布消息。代码如下所示:
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//指定连接服务地址
factory.setHost("localhost");
//获取RabbitMQ连接
try (Connection connection = factory.newConnection(){
//创建一个Channel
Channel channel = connection.createChannel());
//声明一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
//发布消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
}
如果要使用Kafka或者RocketMQ,则需要修改代码创建Kafka和RocketMQ客户端,并且获取相应的连接发布消息,而Spring Cloud Stream要做的就是将消息中间件的连接得到创建、主题或者渠道、队列等的创建、以及消息的接收封装起来,让我们只需要关注业务逻辑,而不需要关注消息是如何发布出去的。Spring Cloud Stream应用由第三方中间件组成,应用间的通信通过输入通道和输出通道完成,而通道与外部的代理的连接通过Binder实现,如下图为Spring Cloud Stream的基本应用结构。
上图中的消息中间件是指RabbitMQ、Kafka、RocketMQ等消息中间件服务器,Binder为消息中间件适配器,不同的中间件对应不同的Binder。而Channel表示通道,通道通过一个明确的Binder与消息中间件通信。在Spring Cloud Stream中,我们需要通过Channel接收和发送消息,而Channel通过Binder与中间件通信。Application Core为Stream自己实现的消息机制封装,包括分区、分组发布订阅语义,与具体的消息中间件无关,那么下面我们以一个简单的案例介绍Spring Cloud Stream的使用。在使用过程中我们关注点主要在Channel和Binder。
首先我们要看的是Channel,在Spring Cloud Stream中,可以使用注解@Output和@Input表示一个输出通道和一个输入通道。注解用可以传入一个字符串表示通道名称,如下源码为Spring Cloud Stream为我们提供的默认输入输出通道:
//定义了一个名称为input的输入通道Channel
public interface Sink {
String INPUT = "input";
//名称为input的输入通道Channel
@Input("input")
SubscribableChannel input();
}
//定义了一个名称为input的输出通道Channel
public interface Source {
String OUTPUT = "output";
//名称为output的输出通道Channel
@Output("output")
MessageChannel output();
}
在接收消息和发送消息时,我们只需要与输入通道和输出通道交互,而无需关注消息是如何发送和接收的,在前面我们说过Channel需要绑定Binder。在Spring Cloud Stream中使用@EnableBinding绑定输入输出通道,它的参数为输入或者输出通道的Class实例。比如我们要绑定默认的通道只需要使用下面的注解方式即可:
//用于接收消息
@EnableBinding(Sink.class)
public class SinkReceiver {
}
//用于发送消息
@EnableBinding(Source.class)
public class SinkSender {
}
如上代码,我们使用@EnableBinding注解绑定了输入和输出通道,但是并没有实现任何逻辑,以后的任何消息的接收与发送只需要与输入输出通道交互即可,首先我们先看消息的发送。在Source中提供了output方法返回一个MessageChannel实例,该实例可以用于发送消息,代码如下所示:
@EnableBinding(Source.class)
public class SinkSender {
private static Logger logger = LoggerFactory.getLogger(SinkReceiver.class);
//注入Source实例
@Autowired
private Source source;
public void sendMessage() {
//获取MessageChannel实例,并且发送消息到中间件
source.output().send(MessageBuilder.withPayload("hello").build());
}
}
在编写完发送消息的逻辑之后,我们现在编写接收消息的逻辑,与发送消息不同直接获取Source实例获取一个通道,接收消息需要使用@StreamListener注解绑定一个输入通道比如:@StreamListener(Sink.INPUT),如下代码为消息的接收示例:
@EnableBinding(Sink.class)
public class SinkReceiver {
private static Logger logger = LoggerFactory.getLogger(SinkReceiver.class);
//使用注解绑定一个输入通道
@StreamListener(Sink.INPUT)
public void receive(String payload) {
logger.info("Received: " + payload);
}
}
在编写消息接受和发送通道之后,我们需要配置将通道绑定到Binder,如下配置我们将input和output通道绑定到名称为hello1的binder,而binder绑定消息中间件。
spring:
cloud:
stream:
bindings: #用于绑定通道到binder
input: #ChannelName 这里是输入通道的名称,如果@Input不指定默认为方法名
destination: hello #从哪里接收消息,这里指topic或者队列名称,在rabbit中为exchange
binder: hello1 #绑定到名为hello1的binder
output: #ChannelName 这里是输出通道的名称,如果@Output不指定默认为方法名
destination: hello #将消息发送到哪里,这里指topic或者队列名称,在rabbit中为exchange
binder: hello1 #绑定到名为hello1的binder
binders: #配置binder
hello1: #配置名称为hello1的binder
type: rabbit #binder类型为rabbitMq
environment: #配置运行环境
spring:
rabbitmq:
host: 127.0.0.1 #地址
port: 5672 #端口
username: guest #用户名
password: guest #密码
server:
port: 8092
在示例运行起来之后,我们可以登陆RabbitMQ控制台,可以看到已经产生一个名称为hello的exchange和名称为hello.anonymous.8gUlpsRTQNqThYfM52Krgw的Queue。相对于前面我们的结构图,或许使用下面的图例更能表达出Spring Cloud Stream中的消息流程,图示如下:
消息发送通道接口Source用于与外界通道的绑定,我们可以通过注解@Output定义通道的名称。当使用该通道发消息时,Spring Cloud Stream会将消息序列化,然后通过接口提供的MessageChannel将消息发送到响应的消息中间件。
消息通道是对消息队列的一种抽象,用来存放消息发布者发布的消息或者消费者所要消费的消息。在向消息中间件发送消息时,需要指定所要发送的消息队列或主题的名称,而在这里Spring Cloud Stream进行了抽象,开发者只需要定义好消息通道消息通道具体发送到哪个消息队列则在项目配置文件中进行配置,比如我们前面配置的配置文件。
Spring Cloud Stream通过定义绑定器作为中间层,实现了应用程序与具体消息中间件细节之间的隔离,向应用程序暴露统一的消息通道,使应用程序不需要考虑与各种不同的消息中间件的对接。当需要升级或者更改不同的消息中间件时,应用程序只需要更换对应的绑定器即可,而不需要修改任何应用逻辑。
在配置通道的时候,我们还可以自定义输入类型或者输出的消息类型,前面的例子,我们使用的是字符串类型,其实它默认的类型为json,我们可以通过spring.cloud.stream.bindings.
application/json
application/octet-stream
text/plain
application/x-java-object;type=foo.bar.Cat
本篇我们只是简单介绍Spring Cloud Stream的使用,下一篇我们会介绍Spring Cloud Stream如何自定义通道和Spring Cloud Stream的更多特性。