在实际的企业开发中,消息中间件是至关重要的组件之一。消息中间件主要解决应用解耦,异步消息,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构。不同的中间件其实现方式,内部构造不一样。如常见的RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic,partitions分区,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列迁移,这时候无疑就是灾难性的,一大堆东西都要重新推到重新做,因为它跟我们的系统耦合了,这时候springcloud stream给我们提供了一种解耦合的方式。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-streamartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-stream-binder-rabbitartifactId>
dependency>
server:
port: 7001
spring:
application:
name: stream-producer #指定服务名
rabbitmq:
addresses: 127.0.0.1
username: guest
password: guest
port: 5672
cloud:
stream:
bindings:
output:
destination: stream-default #指定消息发送的目的地,在rabbitmq中,发送到一个stream-default的exchange
binders: #配置绑定器
defaultRabbit:
type: rabbit
/***
* 1.引入依赖
* 2.配置application.yml
* 3.发送消息的话,定义一个通道接口,通过接口中内置的messagechannel
* springcloudstream中内置接口, Source
* 4.@EnableBinding:绑定对应的通道
* 5.发送消息的话,通过MessageChannel发送消息
* 如果需要MessageChannel ---> 通过绑定的内置接口获取
* */
@EnableBinding(Source.class)
@SpringBootApplication
public class StreamProducerApplication implements CommandLineRunner {
@Autowired
private MessageChannel output;
@Override
public void run(String...args) throws Exception{
//发送消息
//messageBuilder: 工具类:创建消息
output.send(MessageBuilder.withPayload("测试案例").build());
}
public static void main(String[] args){
SpringApplication.run(StreamProducerApplication.class,args);
}
}
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-streamartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-stream-binder-rabbitartifactId>
dependency>
server:
port: 7002
spring:
application:
name: stream-consumer #指定服务名
rabbitmq:
addresses: 127.0.0.1
username: guest
password: guest
port: 5672
cloud:
stream:
bindings:
input:
destination: stream-default #指定消息接收地,在rabbitmq中,从一个stream-default的exchange中获取消息
binders: #配置绑定器
defaultRabbit:
type: rabbit
/**
* 1.引入依赖
* 2.配置application.yml
* 3.需要配置一个通道的接口
* 内置获取消息的通道接口 sink
* 4.绑定通道
* 5.配置一个监听方法:当程序从消息件获取数据后,执行的业务逻辑方法
* 需要在监听方法上配置一个@StreamListener
* */
@SpringBootApplication
@EnableBinding(Sink.class)
public class StreamConsumerApplication {
//监听binding中的消息
@StreamListener(Sink.INPUT)
public void input(String message){
System.out.println("获取到新消息:"+message);
}
public static void main(String[] args) {
SpringApplication.run(StreamConsumerApplication.class,args);
}
}
public interface MyProcedure {
/***
* 消息生产者配置
* */
String MYOUTPUT = "myOutput";
@Output("myOutput")
MessageChannel myOutput();
/***
* 消息消费者配置
* */
String MYINPUT = "myInput";
@Input("myInput")
MessageChannel myInput();
}
@EnableBinding(MyProcedure.class)
//引入output的时候
@Autowired
private MessageChannel myOutput;
server:
port: 7001
spring:
application:
name: stream-producer #指定服务名
rabbitmq:
addresses: 127.0.0.1
username: guest
password: guest
port: 5672
cloud:
stream:
bindings:
output:
destination: stream-default #指定消息发送的目的地,在rabbitmq中,发送到一个stream-default的exchange
myOutput: #自定义的消息通道
destination: my-stream-default
binders: #配置绑定器
defaultRabbit:
type: rabbit
完成,这样就可以使用自定义的通道了。
通常在生产环境,我们的每个服务都不会以单节点的方式运行在生产环境,当同一个服务启动多个实例的时候,这些实例都会绑定到同一个消息通道的目标主题(Topic)上。默认情况下,当生产者发出一条消息绑定通道上,这条消息会产生多个副本被每个消费者实例接收和处理,但是有些业务场景之下,我们希望生产者的消息只被其中一个实例消费者,这个时候我们需要为这些消费者设置消费组来实现这样的功能。
spring:
cloud:
stream:
bindings:
input:
destination: stream-default #指定消息接收地,在rabbitmq中,从一个stream-default的exchange中获取消息
group: group1
其他的消费者也叫group1就可以了,整个group1分组,只会有一个消费者消费
有一些场景需要满足,同一个特征的数据被同一个实例消费,比如同一个id的传感器监测数据必须被同一个实例统计计算分析,否则可能无法获取全部的数据。又比如部分异步任务,首次请求启动task,二次请求取消task,此场景就必须保证两次请求至同一实例。
spring:
cloud:
stream:
bindings:
myoutput: #自定义的消息通道
destination: my-stream-default
producer:
partition-key-expression: payload #分区关键字 对象中的id
partition-count: 2 #分区大小
spring:
cloud:
stream:
bindings:
myinput: #自定义的消息通道
destination: my-stream-default
consumer:
partitioned: true #开启分区支持
binders: #配置绑定器
defaultRabbit:
type: rabbit
instance-count: 2 #消费者总数
instance-index-list: 0 #当前消费者索引
如果数据是一样的,那相同的数据就到达相同的消费者