基础概念
Spring Cloud Stream是一个用来为微服务应用构建消息驱动能力的框架。为使用消息中间件产品映入了自动化配置实现,支持发布-订阅、消费组这三个核心概念,可以简化开发人员对消息中间件使用的复杂度。
快速入门
下面通过一个发布订阅的例子来介绍如何使用Spring cloud steam,程序基于Spring Boot开发。
安装Kafka
安装消息中间件(Kafka),因为Kafka的安装相对简单,所以这里使用Kafka来做为消息中间件,当然使用RabbitMq也是可以的。首先到kafka官网下载程序包,并解压到任意目录下,按照以下步骤执行就可。本例只是简单使用,不会涉及到太多kafka的教程。
- 执行
bin
/zookeeper-server-start
.sh config
/zookeeper
.properties 启动zookeeper
- 执行
bin
/kafka-server-start
.sh config
/server
.properties 启动kafka
- 执行
bin
/kafka-topics
.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic
test
创建test主题
- 执行
bin
/kafka-console-producer
.sh --broker-list localhost:9092 --topic
test
向主题发布消息
,执行这条只能后直接在控制台输入消息内容按回车即可发布一条消息,结束按Ctrl+C.
执行 bin
/kafka-console-consumer
.sh --bootstrap-server localhost:9092 --topic
test
--from-beginning 创建一个test主题的消费者。
如果启动成功的话,就会在第5步中看到第4步输入的信息。
创建Spring Cloud Stream项目
- 使用任一Java IDE工具创建一个maven项目,并添加两个子模块streamcomsumer和streamproducer,在pom.xml中写入如下内容。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>stream-demo1</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>streamcomsumer</module>
<module>streamproducer</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.为每一个子模块添加一个入口文件代码如下:
@SpringBootApplication
public class StreamDemoApplication {
public static void main(String[] args) {
SpringApplication.run(StreamDemoApplication.class, args);
}
}
3.在消费者模块中添加一下代码
@EnableBinding(Sink.class) //绑定消息输入通道,Sink.class为Spring Cloud Stream自带的一个消息通道绑定接口,可以自行定义
public class SinkReceiver {
Logger logger = LoggerFactory.getLogger(SinkReceiver.class);
@StreamListener(Sink.INPUT) //Sink.INPUT为一个静态String变量,值为"input",此处监听消息通道"input",并打印收到的信息
public void receive(String payload){
logger.info("Receive收到:"+payload);
}
}
4.在生产者模块中添加以下代码:
@EnableBinding(Source.class)
public class SinkSender {
@Bean
@InboundChannelAdapter(value=Source.OUTPUT,poller = @Poller(fixedDelay = "2000"))
public MessageSource<String> timerMessageSource(){
return ()->new GenericMessage<String>(new Date().toString());
}
}
其中timerMessageSource方法创建了一个定时发送消息的任务,以上代码表示每隔两秒发送一条消息到输出通道。
编辑application.properties,添加以下属性:
spring.cloud.stream.bindings.output.destination=input
添加这行的主要作用是绑定名为input的消息队列,如果不设置改行,消息输出消息通道将默认半丁output队列。因为消费者程序没有设置该行默认绑定到input队列,为了能让生产程序能够发布消息到消费者,需要添加此行。
5.分别启动这两个程序,如果不出意外的话,每隔两秒就可以在消费者程序的控制台看到时间输出信息。
可能有人会觉得很奇怪,没有填写关于消息队列的配置,程序就能够跑起来,是不是没有连接到消息队列。因为Spring boot 的开发理念为约定由于配置,所以spring cloud stream已经为我们提供了默认配置,只要你的消息队列使用默认的接口,并与程序运行在同一台电脑,就无需做任何配置。
在上面的程序中最终要的部分是@EnableBinding注解,该注解接受一个或多个消息通道绑定接口,上面用到了Sink和Source接口是Spring默认提供的消息通道绑定接口,他们对应的名称为input的输入通道和名称为output的输出通道。除此之外,Spring也提供了另外一个接口Processor,这接口实现了Sink和Source接口,相当于同时使用Sink和Source接口。
EnableBinding如何使用Sink或Source接口
通过Sink或Source的源码可值,其实这两个接口都是非常简单的接口。
public interface Source {
String OUTPUT = "output";
@Output("output")
MessageChannel output();
}
public interface Sink {
String INPUT = "input";
@Input("input")
SubscribableChannel input();
}
其重要的部分是使用@Input和@Output注解的方法,@EnableBinding通过这两个接口创建输入和输出通道,并在上下文中创建了 名称为input的类型为SubscribableChannel 的 Bean和名称为output的类型为MessageChannel的Bean。用于订阅通道的消息和发布消息。@Input和@Output的参数为消息通道的名称。至于接口的其他属性如OUTPUT和INPUT属性对于创建消息队列无关。实现自己的消息通道,参照上面即可,注意使用@Input返回的是SubscribableChannel,使用@Ouput 的方法返回类型为MessageChannel。
消息组及消息分区
默认的情况下不同的程序只要绑定同一个主题的消息通道,在生产者发布一个消息,每个程序都能收到消息。如果这些程序都是相同的实例,意味着消息只需要被一个应用程序实例处理即可,所以需要用到消息组。消息组的配置十分简单,只需要在application.properties中添加如下的属性即可。
spring.cloud.stream.bindings.input.group=comsumer1
消息组能够保证消息只能被被一个程序接收,当时有些情况下我们希望具有某一特征的消息让同一程序实例来处理,比如想让同一姓名的用户的消息都交个同一程序实例消费(这里只是举例,不考究是否实际意义)。实现如下
- 在消费者端的application.properties添加以下属性:
spring.cloud.stream.bindings.input.comsumer.partitioned=true
spring.cloud.stream.instanceCount=2
spring.cloud.stream.instanceIndex=0
partitioned等于true开启消息分区,默认是false
instanceCount表示运行该程序实例有多少
instanceIndex表现当前实例的编号,范围为0到instanceCount-1
2.在生产端的application.properties添加以下属性:
spring.cloud.stream.bindings.output.destination=input
spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload
spring.cloud.stream.bindings.output.producer.partitionCount=2
从上面的配置中,我们可以看到增加了以下这两个参数。
- spring.cloud.stream.bindings.output.producer.partitionKeyExpression:通过该参数指定了分区键值的表达规则,我们可以根据实际的输出消息规则配置SpEl来生成合适的分区键。该表达式作用于传递给MessageChannel的send方法的参数,该参数是实现 org.springframework.messaging.Message接口的类, GenericMessage类是Spring为我们提供的一个实现Message接口的类,我们封装的信息将会放在payload属性上,如果partitionKeyExpression的值是payload,将会使用整个我们放在GenericMessage中的信息做分区数据。
- spring.cloud.stream.bindings.output.producer.partitionCount该参数指定了消息分区的数量。