SpringCloudStream 3.x 新玩法
在前文已经介绍过 SpringCloudStream 的玩法,没看过的小伙伴可以点进去看看。今天带来的是SpringCloudStream 3.x 的新玩法,通过四大函数式接口的方式进行数据的发送和监听。本文将通过 rabbitMQ 和 kafka 的方式进行 demo 演示,以及两种消息中间件的快速切换。
如果还有不知道四大函数式接口的小伙伴,建议先查阅资料后再来看本文。
3.x版本后是 可以看到 @StreamListener 和 @EnableBinding 都打上了@Deprecated 注解。后续的版本更新中会逐渐替换成函数式的方式实现。
既然通过四大函数式接口的方式替换了注解的方式 那么 该如何进行绑定呢?通过:spring.cloud.stream.function.definition: 名称 的方式进行绑定 公开 topic。不管是创建 Consumer 还是 Supplier 或者是 Function Stream都会将其的 方法名称 进行 一个 topic 拆封 和 绑定 假设 创建了一个 Consumer< String > myTopic 的方法,Stream 会将其 拆分成 In 和 out 两个通道
input - < functionName > + -in- + < index >
output - < functionName > + -out- + < index >
格式拆分
myTopic-in-0
myTopic-out-0
还是老规矩先引入 maven 坐标
本demo的springboot版本是 2.6.7 找到 SpringCloudStream kafka 对应的版本,先讲基于kafka。
<!-- kafka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
消费端Consumer
先创建好消费端Consumer 用于监听消息!
@Configuration
public class MgsConsumer {
@Bean
public Consumer<Message<String>> myTopicC() {
return (data) -> {
String payload = data.getPayload();
MessageHeaders headers = data.getHeaders();
Object headerFor = headers.get("for");
System.out.println("myTopicC 接收一条记录:" + payload);
System.out.println("getHeaders headerFor:" + headerFor);
};
}
}
生产端Producer
这种方式定义suppelier 会 默认1000ms 发送一次记录
@Configuration
public class MgsProducer {
Integer i = 1;
@Bean
public Supplier<Message<MsgUser>> myTopicP() {
return () -> {
MsgUser payload = new MsgUser();
payload.setId(i++);
payload.setName(Thread.currentThread().getName());
System.out.println("myTopicP 发送一条记录:" + payload);
return MessageBuilder
.withPayload(payload)
.build();
};
}
}
修改yml文件
spring:
cloud:
stream:
kafka:
binder:
brokers: localhost:9092
# -------------- 分割线 ---------------
function:
# 组装和绑定
definition: myTopicC;myTopicP
bindings:
myTopicC-in-0:
destination: my-topic
group: my_input_group
myTopicP-out-0:
destination: my-topic
打印
myTopicC 接收一条记录:{"name":"scheduling-1","id":1}
getHeaders headerFor:null
myTopicP 发送一条记录:MsgUser(name=scheduling-1, id=2)
myTopicC 接收一条记录:{"name":"scheduling-1","id":2}
getHeaders headerFor:null
myTopicP 发送一条记录:MsgUser(name=scheduling-1, id=3)
myTopicC 接收一条记录:{"name":"scheduling-1","id":3}
getHeaders headerFor:null
myTopicP 发送一条记录:MsgUser(name=scheduling-1, id=4)
myTopicC 接收一条记录:{"name":"scheduling-1","id":4}
getHeaders headerFor:null
myTopicP 发送一条记录:MsgUser(name=scheduling-1, id=5)
myTopicC 接收一条记录:{"name":"scheduling-1","id":5}
getHeaders headerFor:null
可以看到是正常进行创建消息以及消费的,但是实际使用中肯定是需要通过手动的方式进行发送,这个时候我们可以修改一下发送方式。
通过手动发送创建消息
关键对象 StreamBridge
@RequiredArgsConstructor
@Service
public class SendService {
private final StreamBridge streamBridgeTemplate;
public void sendMsg(String msg, Integer id) {
MsgUser payload = new MsgUser();
payload.setId(id);
payload.setName(msg);
System.out.println("sendMsg 发送一条记录:" + payload);
streamBridgeTemplate
.send(
"myTopicC-out-0",
MessageBuilder.withPayload(payload)
.setHeader("for", "这是一个请求头~")
.build());
}
}
修改一下 yml 文件 将 MgsProducer 类注释起来
spring:
cloud:
stream:
kafka:
binder:
brokers: localhost:9092
# -------------- 分割线 ---------------
function:
# 组装和绑定
definition: myTopicC
bindings:
myTopicC-in-0:
destination: my-topic
group: my_input_group
myTopicC-out-0:
destination: my-topic
通过Controller发送了一条记录
GET http://localhost:8080/send?id=331&name=张三
sendMsg 发送一条记录:MsgUser(name=张三, id=331)
myTopicC 接收一条记录:{"name":"张三","id":331}
getHeaders headerFor:这是一个请求头~
能够看到kafka正常的接收到了一条记录,到这里就是收发数据的一个简单示例。
Function的作用
Function< String,String > 范型中有两个参数 一个入参 一个出参,所以在Stream中 可以用来作于一个消息中转站来使用。相当于 top-1 接受到消息 但是我不想处理 我对其数据进行一次处理 发送到 top-2 通道,交给top-2 进行数据的最终处理。
新建一个MgsFunction
@Configuration
public class MgsFunction {
@Bean
public Function<String, String> testFunction() {
return value -> {
System.out.println("中转 testFunction: " + value);
return value.toUpperCase();
};
}
}
在MgsConsumer中新加一个处理方法
@Configuration
public class MgsConsumer {
@Bean
public Consumer<Message<String>> myTopicC() {
return (data) -> {
String payload = data.getPayload();
MessageHeaders headers = data.getHeaders();
Object headerFor = headers.get("for");
System.out.println("myTopicC 接收一条记录:" + payload);
System.out.println("getHeaders headerFor:" + headerFor);
};
}
@Bean
public Consumer<String> testFunctionQ() {
return (data) -> {
System.out.println("testFunctionQ 消息中转后接收一条记录:" + data);
};
}
}
修改SendService发送的通道
@RequiredArgsConstructor
@Service
public class SendService {
private final StreamBridge streamBridgeTemplate;
public void sendMsg(String msg, Integer id) {
MsgUser payload = new MsgUser();
payload.setId(id);
payload.setName(msg);
System.out.println("sendMsg 发送一条记录:" + payload);
streamBridgeTemplate
.send(
"myTopicP-out-0",
MessageBuilder.withPayload(payload)
.setHeader("for", "这是一个请求头~")
.build());
}
}
修改配置文件
spring:
cloud:
stream:
kafka:
binder:
brokers: localhost:9092
# -------------- 分割线 ---------------
# 组装和绑定
function:
definition: myTopicC;myTopicP;testFunction;testFunctionQ
bindings:
myTopicC-in-0:
destination: my-topic
group: my_input_group
myTopicC-out-0:
destination: my-topic
myTopicP-out-0:
destination: test-topic
testFunction-in-0:
destination: test-topic
group: my_input_group
testFunction-out-0:
destination: test-topic-Q
testFunctionQ-in-0:
destination: test-topic-Q
group: my_input_group
sendMsg 发送一条记录:MsgUser(name=张三, id=331)
2022-12-05 10:59:07.869 INFO 12888 --- [nio-8080-exec-3] o.s.c.s.binder.DefaultBinderFactory : Retrieving cached binder: kafka
中转 testFunction : {"name":"张三","id":331}
testFunctionQ 消息中转后接收一条记录:{"NAME":"张三","ID":331}
可以看到 我们这边写了一个消息中转 toUpperCase 的方法是正常使用到了 这边看起来会有点绕,仔细理解一下就会发现挺简单的。
1.发送通道 test-topic
myTopicP-out-0 《topic》 test-topic
2.消费通道 test-topic
testFunction-in-0 《topic》 test-topic
3.然后testFunction-out-0做了一个消息中转 发送给了 test-topic-Q
testFunction-out-0 《topic》 test-topic-Q
4.testFunctionQ-in-0 进行了 test-topic-Q 通道绑定 所以能接收到testFunction-out-0 中转发送出来的消息
testFunctionQ-in-0 《topic》 test-topic-Q
从 kafka切换到rabbtMq
切换起来很简单,修改配置文件即可
spring:
cloud:
stream:
# --------------- rabbitmq ---------------
rabbit:
binder:
admin-addresses: localhost:15672
# --------------- kafka ---------------
# kafka:
# binder:
# brokers: localhost:9092
# -------------- 分割线 ---------------
# 组装和绑定
function:
definition: myTopicC;myTopicP;testFunction;testFunctionQ
bindings:
myTopicC-in-0:
destination: my-topic
group: my_input_group
myTopicC-out-0:
destination: my-topic
myTopicP-out-0:
destination: test-topic
testFunction-in-0:
destination: test-topic
group: my_input_group
testFunction-out-0:
destination: test-topic-Q
testFunctionQ-in-0:
destination: test-topic-Q
group: my_input_group
sendMsg2Sup 发送一条记录:MsgUser(name=张三, id=331)
2022-12-05 11:21:04.221 INFO 14013 --- [nio-8080-exec-3] o.s.c.s.binder.DefaultBinderFactory : Retrieving cached binder: rabbit
中转 testFunction: {"name":"张三","id":331}
testFunctionQ 消息中转后接收一条记录:{"NAME":"张三","ID":331}