前言
本文提供一个Spring Cloud Stream极简的入门demo。
概念
- Spring Cloud Stream是什么
Spring Cloud Stream使我们不再需要关注具体的消息中间件的细节,而只需要用一种适配绑定的方式,修改配置文件,就能自动的在各种消息中间件内切换,它能屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型,这使得Spring Cloud Stream具有很大的吸引力。 - 编程模式:Spring Cloud Stream提供了三个开箱即用的预定义接口。
- Source: 用于有单个输出(outbound)通道的应用。
- Sink: 用于有单个输入(inbound)通道的应用。
- Processor: 用于单个应用同时包含输入和输出通道的情况。
这几个接口就是Spring Cloud Stream提供的对于消息中间件的抽象,我们仅仅需要在配置文件中关注具体的中间件,而写代码的时候对具体的消息中间件是无感知的,无论是RabbitMQ、RocketMQ还是Kafka等,代码都是相同的。这也是Spring Cloud Stream最具有吸引力的一点,她定义了一种消息驱动编程模型,
如果刚接触Spring Cloud Stream,肯定会一头雾水,不过没有关系,下面通过一个极简的demo来帮助理解。
其实Sink和Source接口是位于不同的模块中的(生产者和消费者),但是为了演示方便,我放在了同一个项目里面。
//消费者
public interface MySink {
String INPUT = "orderInput";
@Input(MySink.INPUT)
SubscribableChannel input();
}
// 生产者
public interface MySource {
String OUTPUT = "orderOutput";
@Output(MySource.OUTPUT)
MessageChannel output();
}
@Slf4j
@Component
@Configuration
@EnableBinding({MySink.class,MySource.class})
public class Demo1Runner implements CommandLineRunner {
final MySink sink;
final MySource mySource;
public Demo1Runner(MySink sink, MySource mySource) {
this.sink = sink;
this.mySource = mySource;
}
@Override
public void run(String... args) {
sink.input().subscribe(message -> log.info(message.toString()));
for (int i=0;i<10;i++){
mySource.output().send(new GenericMessage<>("hello world"));
}
}
}
server:
host: your host
spring:
application:
name: stream-demo
cloud:
stream:
default-binder: rabbit
bindings:
orderInput:
binder: rabbit
destination: order-exchange
group: order-queue
orderOutput:
binder: rabbit
destination: order-exchange
group: order-queue
rocketmq:
binder:
name-server: ${server.host}:9876
rabbitmq:
host: ${server.host}
port: 5672
username: guest
password: guest
buld.gradle
plugins {
id 'org.springframework.boot' version '2.3.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.delicious.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '14'
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }
mavenCentral()
}
ext {
set('springCloudAlibabaVersion', "2.2.1.RELEASE")
set('springCloudVersion', "Hoxton.SR4")
}
dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'com.alibaba.cloud:spring-cloud-starter-stream-rocketmq'
implementation 'org.springframework.cloud:spring-cloud-stream'
implementation 'org.springframework.cloud:spring-cloud-stream-binder-rabbit'
compileOnly 'org.projectlombok:lombok'
testImplementation 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
// exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.springframework.amqp:spring-rabbit-test'
testImplementation 'org.springframework.cloud:spring-cloud-stream-test-support'
}
dependencyManagement {
imports {
mavenBom "com.alibaba.cloud:spring-cloud-alibaba-dependencies:${springCloudAlibabaVersion}"
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
这个例子是演示生产者发送订单消息到消息中间件,然后消费者监听并对消息进行消费。
启动Spring boot应用,控制台输出如下:
可以看到一个专门的线程监听订单队列。
通过RabbitMQ控制台也可以看到配置中指定的交换机和队列也已经创建 了。
生成的exchange的type默认是topic类型。
注意点
@EnableBinding(Sink.class)如果没有对Sink注册消息处理器消费消息,则会出现以下异常:
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
结语
到这完成了对Spring Cloud Stream的入门演示,如果要实现更复杂的功能,就需要在配置文件中进行更多的配置了。这里贴上一份RocketMQ官方的参考配置,以供学习。
#################################### common config : ####################################
spring.application.name=cloud_stream_demo
# 应用服务web访问端口
server.port=8080
# ActuatorWeb访问端口
management.server.port=8081
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# spring cloud access&secret config
# 可以访问如下地址查看: https://usercenter.console.aliyun.com/#/manage/ak
alibaba.cloud.access-key=****
alibaba.cloud.secret-key=****
#################################### rocketmq config : ####################################
## 控制台: https://ons.console.aliyun.com
## Spring Cloud Alibaba RocketMQ 文档:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc-zh/rocketmq.adoc
## RocketMQ 帮助文档:https://help.aliyun.com/product/29530.html
########## RocketMQ 通用配置
# 客户端接入点,必填
spring.cloud.stream.rocketmq.binder.name-server=111.229.90.24:9876
# 阿里云账号 AccessKey,默认为空;如果使用aliyun提供的rocketmq服务,该值必填
spring.cloud.stream.rocketmq.binder.access-key=
# 阿里云账号 SecretKey,默认为空;如果使用aliyun提供的rocketmq服务,该值必填
spring.cloud.stream.rocketmq.binder.secret-key=
# 日志级别
logging.level.com.alibaba.cloud.stream.binder.rocketmq=INFO
########## Consumer Config
# input1 的配置:
spring.cloud.stream.bindings.input1.destination=test-topic
spring.cloud.stream.bindings.input1.content-type=text/plain
spring.cloud.stream.bindings.input1.group=test-group1
# Consumer 是否同步消费消息模式。默认为false
spring.cloud.stream.rocketmq.bindings.input1.consumer.orderly=true
# Consumer 基于 TAGS 订阅,多个 tag 以 || 分割。默认值: empty(即空字符串"")
spring.cloud.stream.rocketmq.bindings.input1.consumer.tags=tagStr
# 异步消费消息模式下消费失败重试策略,默认值: 0
# -1 = 不重复,直接放入死信队列
# 0 = broker 控制重试策略
# >0 = client 控制重试策略
spring.cloud.stream.rocketmq.bindings.input1.consumer.delayLevelWhenNextConsume=0
# 同步消费消息模式下消费失败后再次消费的时间间隔。默认值: 1000
spring.cloud.stream.rocketmq.bindings.input1.consumer.suspendCurrentQueueTimeMillis=1000
# 更多配置参数,可查看源码:com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties
# input2 的配置:
spring.cloud.stream.bindings.input2.destination=test-topic
spring.cloud.stream.bindings.input2.content-type=text/plain
spring.cloud.stream.bindings.input2.group=test-group2
spring.cloud.stream.bindings.input2.consumer.concurrency=20
spring.cloud.stream.bindings.input2.consumer.maxAttempts=1
spring.cloud.stream.rocketmq.bindings.input2.consumer.orderly=false
spring.cloud.stream.rocketmq.bindings.input2.consumer.tags=tagStr
# input3 的配置:
spring.cloud.stream.bindings.input3.destination=test-topic
spring.cloud.stream.bindings.input3.content-type=application/json
spring.cloud.stream.bindings.input3.group=test-group3
spring.cloud.stream.bindings.input3.consumer.concurrency=20
spring.cloud.stream.rocketmq.bindings.input3.consumer.tags=tagObj
# input4 的配置:
spring.cloud.stream.bindings.input4.destination=TransactionTopic
spring.cloud.stream.bindings.input4.content-type=text/plain
spring.cloud.stream.bindings.input4.group=transaction-group
spring.cloud.stream.bindings.input4.consumer.concurrency=5
# input5 的配置:
spring.cloud.stream.bindings.input5.destination=pull-topic
spring.cloud.stream.bindings.input5.content-type=text/plain
spring.cloud.stream.bindings.input5.group=pull-topic-group
########## Produce Config
# output1 的配置如下:
spring.cloud.stream.bindings.output1.destination=test-topic
spring.cloud.stream.bindings.output1.content-type=text/plain
# Producer group name。默认值为empty(即"")
spring.cloud.stream.rocketmq.bindings.output1.producer.group=my_group
# 是否发送事务消息。默认值为false
spring.cloud.stream.rocketmq.bindings.output1.producer.transactional=false
# 是否使用同步得方式发送消息。默认值为false
spring.cloud.stream.rocketmq.bindings.output1.producer.sync=true
# 更多配置参数,可查看源码:com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties
# output2 的配置如下:
spring.cloud.stream.bindings.output2.destination=test-topic
spring.cloud.stream.bindings.output2.content-type=application/json
spring.cloud.stream.rocketmq.bindings.output2.producer.group=myTxProducerGroup
spring.cloud.stream.rocketmq.bindings.output2.producer.transactional=true
spring.cloud.stream.rocketmq.bindings.output2.producer.sync=false