参考资料:
参考demo
参考视频1
参考视频2
官方文档(推荐)
官方文档中文版
关于Kafka和rabbitMQ的安装教程,见本人之前的博客
rocketMq的安装教程
rocketMq仪表盘安装教程
重!!!
个人整理SpringCloud-Stream各部分概念、配置方法、配置项大全
Stream基本配置(Kafka、RabbitMQ和RocketMQ)
Stream消费者组(解决Kafka消费者组只有一个消费者的问题)
Stream消费分区
相关说明及注意事项:
- springcloud-stream是springCloud的一项功能,所以需要和springBoot有严格的版本对应关系,参照
- 上述参考demo中有单个服务的搭建方式,也有多个微服务的搭建方式
- 函数式编程从Stream2.1版本开始支持,3.1.x版本后只支持函数式编程

- 且git仓库并没有出现中文乱码的情况,下载到本地出现中文乱码注意编码格式

函数式概念:
1.官方介绍
- 中文文档
- 英文文档介绍:Learn标签选择对应版本说明文档→overview→Programming Model (Producing and Consuming Messages ( Spring Cloud Function support)) Learn标签选择对应版本说明文档
overview

Programming Model(Producing and Consuming Messages( Spring Cloud Function support))

2.函数式编程介绍
2.1.基础知识
2.1.1 Stream函数式编程
- 参考资料部分的二、Java8内置的四大核心函数式接口
- 如果不会泛型的话可以参考或者参考
- 具体原理参照上面的文档,下面只介绍怎么使用
①、只出不进,即没有入参,只有返回值的:Supplier<T>,泛型为返回值类型
public Supplier<String> func01(){
return ()->{
String message = "hello world";
return message;
};
}②、有进有出,即有入参,又有返回值的:Function<T,T>,第一个泛型为输入值,第二个泛型为返回值
public Function<String,String> func02(){
return message->{
return message+"!";
};
}③、只进不出,有入参,但没有返回值的:Consumer<T>,泛型为输入值类型
public Consumer<String> func03(){
return message->{
System.out.println("===========received:"+message+"=========");
};
}③、然后对上述接口的使用如下:
@Test
public void test01(){
System.out.println(func01().get());
System.out.println(func02().apply("hello world"));
func03().accept("hello world");
}2.1.2 Flux函数
- 用于数组操作,这里只讲使用
- 有两个数组:"a","b","c"和"1","2","3"
- 进行输出操作
Flux<String> f1 = Flux.just("a", "b", "c");
f1.subscribe(System.out::println);
Flux<String> f2 = Flux.just("1", "2", "3");
f2.subscribe(System.out::println);- 对相同下标的元素,进行合并,以:进行连接
Flux<String> f3 = Flux.combineLatest(f1, f2, (str1, str2) -> str1 + ":" + str2);
f3.subscribe(System.out::println);- 对两个数组进行首尾对接
Flux<String> f4 = Flux.merge(f1, f2);
f4.subscribe(System.out::println);
2.1.3 多通道合流
- 由上述可知Function<T,T>和Consumer<T>只有一个入参,当有两个及以上入参如果就需要辅助来进行
- 通常使用Tuple和Flux的组合来完成多参数的输入,其中Tuple类型及用途多样,常用的如下:
Tuple2 | 2个入参 |
Tuple3 | 3个入参 |
Tuple4 | 4个入参 |
Tuple5 | 5个入参 |
Tuple6 | 6个入参 |
Tuple7 | 7个入参 |
Tuple8 | 8个入参 |
- 具体使用方式如下:
- 如使Function<T,T>具有两个入参
public Function<Tuple2<Flux<String>,Flux<String>>,Flux<String>> func04(){
return tuple->{
Flux<String> t1 = tuple.getT1();
Flux<String> t2 = tuple.getT2();
//对 t1 t2两个入参进行合并操作, 并返回结果f3
Flux<String> f3 = Flux.combineLatest(t1, t2, (str1, str2) -> str1 + ":" + str2);
return f3;
};
}

- 如使Function<T,T>具有四个入参
public Function<Tuple4<Flux<String>,Flux<String>,Flux<String>,Flux<String>>,Flux<String>> func05(){
return tuple->{
Flux<String> t1 = tuple.getT1();
Flux<String> t2 = tuple.getT2();
Flux<String> t3 = tuple.getT3();
Flux<String> t4 = tuple.getT4();
//对 t1 t2两个入参进行合并操作, 并返回结果f3
Flux<String> f3 = Flux.combineLatest(t1, t2, (str1, str2) -> str1 + ":" + str2);
Flux<String> f4 = Flux.combineLatest(t3, t4, (str1, str2) -> str1 + ":" + str2);
Flux<String> f5 = Flux.combineLatest(f3, f4, (str1, str2) -> str1 + ":" + str2);
return f5;
};
}
2.2.概念
2.2.1 概述
- 重要:函数式编程并不是对以往Stream的颠覆,而是改进了生产者和消费者的配置方式,其他不变
- 所以概念篇的各项概念,及配置方式依然适用,只不过生产者和消费者的配置方式被替换掉了
- 为了应对更加复杂的业务场景,使消息能够在MQ消息通道和JVM方法之间不断流动,而不是仅仅局限于生产者发送,消费者接收,提出了函数式编程


- 在上图中,函数结构为2.1基础知识中说的Supplier<T>,Function<T,T>,Consumer<T>的其中一个或者他们的组合(Group),根据项目的需要以及函数的特点进行选择。
Supplier<T> | 只出不进 | 无接收功能,有产出功能 |
Function<T,T> | 有进有出 | 有接收功能,有产出功能 |
Consumer<T> | 只进不出 | 有接收功能,无产出功能 |
*注:上述图表中,接收功能可以用来替换原先的消费者,产出功能可以用来替换原先的生产者,不同的是函数式编程更加灵活多变;
- 因取消了生产者和消费者的限制,由上述三种接口来取代,进而消息就可以像转接头和水管的合作一样不断地传递下去。

2.2.2 函数式编程的使用
- 函数式编程仅仅是对生产者和消费者的升级改进,其他配置不变,为的是适用更复杂的使用场景
- ①、首先明确数据流向,并标注相关相关实例名,以上述图例为例

- ②、除交换机外,将相关方法进行注入
selffun1
@Bean
public Supplier<String> selffun1(){
return ()->{
String message = "hello world01";
System.out.println("===========selffun1=============");
return message;
};
}selffun2
@Bean
public Supplier<String> selffun2(){
return ()->{
String message = "hello world02";
System.out.println("===========selffun2=============");
return message;
};
}selffun3(多通道合流见上述)
@Bean
public Function<Tuple2<Flux<String>,Flux<String>>,Flux<String>> selffun3(){
return tuple->{
Flux<String> t1 = tuple.getT1();
Flux<String> t2 = tuple.getT2();
Flux<String> f3 = Flux.combineLatest(t1, t2, (str1, str2) -> str1 + ":" + str2);
return f3;
};
}selffun4
@Bean
public Function<String,String> selffun4(){
return message->{
System.out.println("===========selffun4:"+message+"=========");
return message;
};
}selffun5
@Bean
public Consumer<String> selffun5(){
return message->{
System.out.println("===========selffun5:"+message+"=========");
};
}
- ③、配置文件中进行声明,使注入的方法生效
spring.cloud.stream.function.definition=selffun1;selffun2;selffun3;selffun4;selffun5- ④、按照概念篇的配置步骤:配置Binders→编写注入生产者、消费者→编写Bindings,生产者和消费者被上述函数编程取代。
由概念篇得知,在声明默认Binders后,Bindings如果不指明binder,则绑定默认的Binder
本例为突出重点,所以声明默认Binder后,binding不指明,进行默认绑定。
- ⑤、配置Binders,并设置默认Binder
spring.cloud.stream.binders.mykafka.type=kafka
spring.cloud.stream.binders.mykafka.environment.spring.cloud.stream.kafka.binder.brokers=192.168.188.203:9092
spring.cloud.stream.default-binder=mykafka- ⑥、函数式编程Bindings参数说明
上文讲过,函数式编程注入的三种函数:入参相当于消费者,(输出)返回值相当于生产者
所以在注入并声明生效的函数后,还需要表明它的入参以及输出发挥着怎样的作用
通常形式如下:
<FunctionName>-in/out-<param-index>
FunctionName: | 为注入,并且在配置文件中声明生效的方法名 |
in/out : | in表示入参,out表示输入(返回值) |
param-index | 第n个参数的来源 / 第n个输出值的去向 |
- 根据消息流向图,注入并声明生效的方法,以及上述Binding的概念,得到如下配置
#如果不填写binder的话 默认链接上述默认MQ链接
spring.cloud.stream.bindings.selffun1-out-0.destination=selfexchange1
spring.cloud.stream.bindings.selffun2-out-0.destination=selfexchange2
spring.cloud.stream.bindings.selffun3-in-0.destination=selfexchange1
spring.cloud.stream.bindings.selffun3-in-1.destination=selfexchange2
spring.cloud.stream.bindings.selffun3-out-0.destination=selfexchange3
spring.cloud.stream.bindings.selffun4-in-0.destination=selfexchange3
spring.cloud.stream.bindings.selffun4-out-0.destination=selfexchange4
spring.cloud.stream.bindings.selffun5-in-0.destination=selfexchange4- 最后得到的配置如下:

- 上面我们介绍了消息的自动发送,但是通常情况下我们是需要手动发送
- 手动发送,不需要注入,不需要声明,在配置文件中直接配置,然后使用StreamBridge的send方法,绑定bindingName为该配置名即可
- 如,最简单的手动发送

- ①、只需要注入selffun6
@Configuration
public class HandleFlowConfig {
@Bean
public Consumer<String> selffun6(){
return message->{
System.out.println("===========selffun6:"+message+"=========");
};
}
}- ②、在配置文件中声明注入的selffun6起作用,
spring.cloud.stream.function.definition=selffun6- ③、声明默认Binder
# 配置 两个生产者 四个消费者 两个消费者组 共用两个交换机 mykafkaexchange
#首先配置自定义连接
spring.cloud.stream.binders.mykafka.type=kafka
spring.cloud.stream.binders.mykafka.environment.spring.cloud.stream.kafka.binder.brokers=192.168.227.203:9092
spring.cloud.stream.default-binder=mykafka- ④、这里为了突出重点,使用默认binder,手动发送可以直接在配置文件中声明
#如果不填写binder的话 默认链接上述默认MQ链接
spring.cloud.stream.bindings.handlesend.destination=selfexchange5
spring.cloud.stream.bindings.selffun6-in-0.destination=selfexchange5- ⑤、配置文件关系如下:

- StreamBridge进行发送
@RestController
public class Controller {
@Autowired
private StreamBridge streamBridge;
@RequestMapping("send_msg01")
public String sendMsg01(String msg){
streamBridge.send("handlesend",msg);
return "sucess";
}
}
- 如果要前面的自动发送,改为手动发送,只需要将配置文件中的声明去掉即可(当然最好也将对应地函数注销掉),然后换成手动发送

server.port=8080
# 配置 两个生产者 四个消费者 两个消费者组 共用两个交换机 mykafkaexchange
#首先配置自定义连接
spring.cloud.stream.binders.mykafka.type=kafka
spring.cloud.stream.binders.mykafka.environment.spring.cloud.stream.kafka.binder.brokers=192.168.227.203:9092
spring.cloud.stream.default-binder=mykafka
#spring.cloud.stream.function.definition=selffun1;selffun2;selffun3;selffun4;selffun5
spring.cloud.stream.function.definition=selffun3;selffun4;selffun5
#如果不填写binder的话 默认链接上述默认MQ链接
spring.cloud.stream.bindings.handlesend1.destination=selfexchange1
spring.cloud.stream.bindings.handlesend2.destination=selfexchange2
spring.cloud.stream.bindings.selffun3-in-0.destination=selfexchange1
spring.cloud.stream.bindings.selffun3-in-1.destination=selfexchange2
spring.cloud.stream.bindings.selffun3-out-0.destination=selfexchange3
spring.cloud.stream.bindings.selffun4-in-0.destination=selfexchange3
spring.cloud.stream.bindings.selffun4-out-0.destination=selfexchange4
spring.cloud.stream.bindings.selffun5-in-0.destination=selfexchange4@RestController
public class Controller {
@Autowired
private StreamBridge streamBridge;
@RequestMapping("send_msg02")
public String sendMsg02(String msg){
streamBridge.send("handlesend1",msg);
streamBridge.send("handlesend2",msg);
return "sucess";
}
}
总结:就Stream函数式编程的使用,需要经历:①注入→②声明→③Binding配置关系

如果是手动发送,则直接配置,然后使用即可

2.2.3 和传统生产/消费者方式的比较
- 上面已经讲过,函数式编程仅仅是对生产者消费者的替换,其他的配置依然生效
- 结合之前的博客:概念篇,基本配置篇,消费组篇,分区篇,观察配置如下:

可以看到除增加了声明有效的已注入函数外,其他配置几乎一致
代码方面也仅仅是平替即可

替换为:

或者直接使用StreamBridge进行发送

可以看到更加简单灵活了,是对生产者、消费者的升级
Stream函数式编程项目搭建流程:
1.项目说明
- 本项目旨在表现Stream函数式编程消费组、分区的搭建,相关概念及重要代码上文已经贴出,该搭建教程的成本见GitHub中的spring-cloud-stream-mq-patition服务

2. 项目框架搭建
- 搭建参考,消费者组使用微服务的搭建方式
- 配置文件中尽量不要出现中文,如果有中文,参照修改字符集
- 微服务一定要进行版本的管理(不然就会出错),因为springBoot,springcloud以及stream有着严格的版本控制

- 即父项目的pom一定要具有下面的形式

<!--定义springcloud使用版本号 太高的版本将不能使用java8进行编译-->
<properties>
<java.version>1.8</java.version>
<spring.cloud.version>2021.0.2</spring.cloud.version>
<spring.version>2.7.7</spring.version>
<lombok.version>1.18.12</lombok.version>
</properties>
<!--全局管理springcloud版本,并不会引入具体依赖-->
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 然后在公共依赖项目中 要连接MQ的依赖,当然你有其他依赖也可以加进去,这里以RabbitMQ和Kafka为例
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
- 最后得到的结果为

3. 创建子服务
- 创建子服务的具体流程就不详述了,具体参照
- 创建子微服务:new-version-consumer01

4.创建消费组

- 首先对需要函数的进行注入:kafkaconsumer02、kafkaconsumer01、kafkaconsumer01last进行注入:
@Configuration
public class KafkaConfig {
@Bean
public Function<String,String> kafkaconsumer01(){
return message->{
System.out.println("===========consumer01_stage_01:"+message+"=========");
return message+"!";
};
}
@Bean
public Consumer<String> kafkaconsumer02(){
return message->{
System.out.println("===========consumer02_stage_01:"+message+"=========");
};
}
@Bean
public Consumer<String> kafkaconsumer01last(){
return message->{
System.out.println("===========consumer01_stage_02:"+message+"=========");
};
}
}- Binders:然后连接MQ并创建自定义实例,并设置为默认,具体参照概念篇,其中kafka需要设置分区,并且开启自动创建,见消费组篇
# 配置 两个生产者 四个消费者 两个消费者组 共用两个交换机 mykafkaexchange
#首先配置自定义连接
spring.cloud.stream.binders.mykafka.type=kafka
spring.cloud.stream.binders.mykafka.environment.spring.cloud.stream.kafka.binder.brokers=192.168.188.203:9092
#这句话一定要添加 因为默认是关闭的
spring.cloud.stream.binders.mykafka.environment.spring.cloud.stream.kafka.binder.auto-add-partitions=true
#-----注销掉Kafka的消息将只被一个消费者消费----
spring.cloud.stream.binders.mykafka.environment.spring.cloud.stream.kafka.binder.minPartitionCount=2
spring.cloud.stream.binders.mykafka.environment.spring.cloud.stream.kafka.binder.autoAddPartitions=true
spring.cloud.stream.default-binder=mykafka
- 然后声明,生效的方法
spring.cloud.stream.function.definition=kafkaconsumer01;kafkaconsumer02;kafkaconsumer01last- Bindings:绑定交换机(消息通道),声明消费者组,手动发送直接配置即可
# 如果不填写binder的话 默认链接上述默认MQ链接 mykafka
spring.cloud.stream.bindings.kafkaselfsender.destination=mykafkaexchange1
spring.cloud.stream.bindings.kafkaconsumer01-in-0.destination=mykafkaexchange1
spring.cloud.stream.bindings.kafkaconsumer01-in-0.group=group-a
spring.cloud.stream.bindings.kafkaconsumer02-in-0.destination=mykafkaexchange1
spring.cloud.stream.bindings.kafkaconsumer02-in-0.group=group-a
spring.cloud.stream.bindings.kafkaconsumer01-out-0.destination=mykafkaexchange2
spring.cloud.stream.bindings.kafkaconsumer01last-in-0.destination=mykafkaexchange2
- 然后在Controller进行消息的发送
@RestController
public class SendController {
@Autowired
private StreamBridge streamBridge;
@RequestMapping("consumer_group")
public String consumerGroup(String msg){
System.out.println("===========kafkaselfsender=========");
streamBridge.send("kafkaselfsender",msg);
return "sucess";
}
}
5.创建消费分区

- 为了表现多个MQ连接的实例,这里不使用默认的binder,创建另一个Binder:mykafka02
- 首先对需要函数的进行注入:rabbitconsumer01;rabbitconsumer02;rabbitconsumer03;rabbitconsumer04;rabbitconsumer05;rabbitconsumer06;rabbitconsumer07进行注入:
package org.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @program: spring-cloud-stream-new-version
* @description:
* @author: wjl
* @create: 2023-06-14 21:00
**/
@Configuration
public class RabbitMqConfig {
@Bean
public Consumer<String> rabbitconsumer01(){
return message->{
System.out.println("===========consumer02_stage_01:"+message+"=========");
};
}
@Bean
public Consumer<String> rabbitconsumer02(){
return message->{
System.out.println("===========consumer02_stage_01:"+message+"=========");
};
}
@Bean
public Function<String,String> rabbitconsumer03(){
return message->{
System.out.println("===========consumer01_stage_01:"+message+"=========");
return message+"!";
};
}
@Bean
public Function<String,String> rabbitconsumer04(){
return message->{
System.out.println("===========consumer02_stage_01:"+message+"=========");
return message+"!";
};
}
@Bean
public Consumer<String> rabbitconsumer05(){
return message->{
System.out.println("===========consumer02_stage_01:"+message+"=========");
};
}
@Bean
public Consumer<String> rabbitconsumer06(){
return message->{
System.out.println("===========consumer02_stage_01:"+message+"=========");
};
}
@Bean
public Consumer<String> rabbitconsumer07(){
return message->{
System.out.println("===========consumer02_stage_01:"+message+"=========");
};
}
}- Binders:然后连接MQ并创建自定义实例,具体参照概念篇,其中kafka需要设置分区,并且开启自动创建,见消费组篇以及分区篇
spring.cloud.stream.binders.mykafka02.type=kafka
spring.cloud.stream.binders.mykafka02.environment.spring.cloud.stream.kafka.binder.brokers=192.168.188.203:9092
spring.cloud.stream.binders.mykafka02.environment.spring.cloud.stream.kafka.binder.auto-add-partitions=true
spring.cloud.stream.binders.mykafka02.environment.spring.cloud.stream.kafka.binder.minPartitionCount=2
- 然后声明,生效的方法
spring.cloud.stream.function.definition=rabbitconsumer01;rabbitconsumer02;rabbitconsumer03;rabbitconsumer04;rabbitconsumer05;rabbitconsumer06;rabbitconsumer07- Bindings:绑定交换机(消息通道),绑定非默认的binder,声明消费者组,手动发送直接配置即可
#如果不使用默认binder的话 需要声明
spring.cloud.stream.bindings.rabbitselfsender.binder=mykafka02
spring.cloud.stream.bindings.rabbitselfsender.destination=myrabbitexchange1
spring.cloud.stream.bindings.rabbitselfsender.producer.partition-count=2
spring.cloud.stream.bindings.rabbitselfsender.producer.partition-key-expression=payload
spring.cloud.stream.bindings.rabbitconsumer01.binder=mykafka02
spring.cloud.stream.bindings.rabbitconsumer01-in-0.destination=myrabbitexchange1
spring.cloud.stream.bindings.rabbitconsumer01-in-0.consumer.partitioned=true
spring.cloud.stream.bindings.rabbitconsumer01-in-0.consumer.instance-count=2
spring.cloud.stream.bindings.rabbitconsumer01-in-0.consumer.instance-index=0
spring.cloud.stream.bindings.rabbitconsumer01-in-0.group=group-b
spring.cloud.stream.bindings.rabbitconsumer02.binder=mykafka02
spring.cloud.stream.bindings.rabbitconsumer02-in-0.destination=myrabbitexchange1
spring.cloud.stream.bindings.rabbitconsumer02-in-0.consumer.partitioned=true
spring.cloud.stream.bindings.rabbitconsumer02-in-0.consumer.instance-count=2
spring.cloud.stream.bindings.rabbitconsumer02-in-0.consumer.instance-index=0
spring.cloud.stream.bindings.rabbitconsumer02-in-0.group=group-b
spring.cloud.stream.bindings.rabbitconsumer03.binder=mykafka02
spring.cloud.stream.bindings.rabbitconsumer03-in-0.destination=myrabbitexchange1
spring.cloud.stream.bindings.rabbitconsumer03-in-0.consumer.partitioned=true
spring.cloud.stream.bindings.rabbitconsumer03-in-0.consumer.instance-count=2
spring.cloud.stream.bindings.rabbitconsumer03-in-0.consumer.instance-index=1
spring.cloud.stream.bindings.rabbitconsumer03-out-0.destination=myrabbitexchange2
spring.cloud.stream.bindings.rabbitconsumer03-in-0.group=group-c
spring.cloud.stream.bindings.rabbitconsumer04.binder=mykafka02
spring.cloud.stream.bindings.rabbitconsumer04-in-0.destination=myrabbitexchange2
spring.cloud.stream.bindings.rabbitconsumer05.binder=mykafka02
spring.cloud.stream.bindings.rabbitconsumer05-in-0.destination=myrabbitexchange1
spring.cloud.stream.bindings.rabbitconsumer05-in-0.consumer.partitioned=true
spring.cloud.stream.bindings.rabbitconsumer05-in-0.consumer.instance-count=2
spring.cloud.stream.bindings.rabbitconsumer05-in-0.consumer.instance-index=1
spring.cloud.stream.bindings.rabbitconsumer05-out-0.destination=myrabbitexchange3
spring.cloud.stream.bindings.rabbitconsumer05-in-0.group=group-c
spring.cloud.stream.bindings.rabbitconsumer06.binder=mykafka02
spring.cloud.stream.bindings.rabbitconsumer06-in-0.destination=myrabbitexchange3
spring.cloud.stream.bindings.rabbitconsumer06-in-0.group=group-d
spring.cloud.stream.bindings.rabbitconsumer07.binder=mykafka02
spring.cloud.stream.bindings.rabbitconsumer07-in-0.destination=myrabbitexchange3
spring.cloud.stream.bindings.rabbitconsumer07-in-0.group=group-d
- 然后在Controller进行消息的发送
@RestController
public class SendController {
@Autowired
private StreamBridge streamBridge;
@RequestMapping("patition")
public String patition(String msg){
System.out.println("===========patition=========");
streamBridge.send("rabbitselfsender",msg);
return "sucess";
}
}
















