Spring Cloud Stream 是个构建消息驱动微服务的框架。应程序通过inputs(相当于消息消费者consumer)或者outputs(相当于消息生产者producer来与Spring Cloud Stream中的binder对象交互,Binder对象是⽤来屏蔽底层MQ细节的,它负责与具体的消息中间件交互。
解决痛点问题
MQ消息中间件⼴泛应⽤在应⽤解耦合、异步消息处理、流量削峰等场景中。
不同的MQ消息中间件内部机制包括使用方式都会有所不同,比如RabbitMQ中有Exchange(交换机/交换器)这⼀概念,kafka有Topic、Partition分区这些概念,MQ消息中间件的差异性不利于我们上层的开发应用,当我们的系统希望从原有的RabbitMQ切换到Kafka时,我们会发现比较困难,很多要操作可能重来(因为应⽤程序和具体的某⼀款MQ消息中间件耦合在⼀起了)。
Spring Cloud Stream进行了很好的上层抽象,可以让我们与具体消息中间件解耦合,屏蔽掉了底层具体MQ消息中间件的细节差异,就像Hibernate屏蔽掉了具体数据库(Mysql/Oracle⼀样)。如此⼀
来,我们学习、开发、维护MQ都会变得轻松。目前Spring Cloud Stream⽀持RabbitMQ和Kafka。
概念
inputs:(相当于消息消费者consumer)
outputs:(相当于消息⽣产者producer)
Binder绑定器: 通过它来屏蔽底层不同MQ消息中间件
的细节差异,当需要更换为其他消息中间件时,我们需要做的就是更换对应的Binder绑定器⽽不需要修改任何应⽤逻辑
架构图:
Stream编程注解
注解 | 描述 |
---|---|
@Input(在消费者工程中使用) | 注解标识输⼊通道,通过该输⼊通道接收到的消息进入应用程序 |
@Output(在生产者工程中使用) | 注解标识输出通道,发布的消息将通过该通道离开应⽤程序 |
@StreamListener(在消费者⼯程中使用,监听message的到来) | 监听队列,用于消费者的队列的消息的接收(有消息监听…) |
@EnableBinding | 把Channel和Exchange(对于RabbitMQ)绑定在⼀起 |
构建服务
生产者服务
- 在lagou_parent下新建⼦module:m-cloud-stream-producer-9090
- pom.xml中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- application.yml添加配置
server:
port: 9090
spring:
application:
name: m-cloud-stream-producer
cloud:
stream:
binders: # 绑定MQ服务信息(此处我们是RabbitMQ)
mRabbitMQbinder: # 给Binder定义的名称,⽤于后⾯的关联
type: rabbit
environment: # MQ环境配置(⽤户名、密码等)
spring:
rabbitmq:
host: 127.0.0.1
username: guest
password: guest
bindings: # 关联整合通道和binder对象
output: # output是我们定义的通道名称,此处不能乱改
destination: m-cloud-stream-producer-exchange
content-type: text/plain
binder: mRabbitMQbinder
#注册发现
eureka:
client:
service-url:
defaultZone: http://CloudEurekaServerA:8761/eureka,http://CloudEurekaServerB:8762/eureka
instance:
prefer-ip-address: true
instance-id: ${
spring.cloud.client.ip-address}:${
spring.application.name}:${
server.port}:@project.version@
- 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class MCloudStreamProducer9090 {
public static void main(String[] args) {
SpringApplication.run(MCloudStreamProducer9090.class, args);
}
}
- service
public interface IMessageProducer {
public void sendMessage(String content);
}
@EnableBinding(Source.class)
public class IMessageProducerImpl implements IMessageProducer {
@Autowired
private Source source;
@Override
public void sendMessage(String content) {
source.output().send(MessageBuilder.withPayload(content).build());
}
}
- 测试类
@SpringBootTest(classes = {
MCloudStreamProducer9090.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class MCloudStreamProducer9090Test {
@Autowired
private IMessageProducer iMessageProducer;
@Test
public void testSendMessage() {
iMessageProducer.sendMessage("hello world");
}
}
消费者服务
- 在lagou_parent下新建⼦module:m-cloud-stream-consumer-9091
- pom.xml中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- application.yml添加配置
server:
port: 9091
spring:
application:
name: m-cloud-stream-consumer
cloud:
stream:
binders: # 绑定MQ服务信息(此处我们是RabbitMQ)
mRabbitMQbinder: # 给Binder定义的名称,⽤于后⾯的关联
type: rabbit
environment: # MQ环境配置(⽤户名、密码等)
spring:
rabbitmq:
host: 127.0.0.1
username: guest
password: guest
bindings: # 关联整合通道和binder对象
input: # output是我们定义的通道名称,此处不能乱改
destination: m-cloud-stream-producer-exchange
content-type: text/plain
binder: mRabbitMQbinder
group: rabbit-a
#注册发现
eureka:
client:
service-url:
defaultZone: http://CloudEurekaServerA:8761/eureka,http://CloudEurekaServerB:8762/eureka
instance:
prefer-ip-address: true
instance-id: ${
spring.cloud.client.ip-address}:${
spring.application.name}:${
server.port}:@project.version@
- 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class McloudSteamConsumer9091 {
public static void main(String[] args) {
SpringApplication.run(McloudSteamConsumer9091.class, args);
}
}
- service
@EnableBinding(Sink.class)
public class MessageConsumerService {
@StreamListener(Sink.INPUT)
public void receMessage(Message<String> message){
System.out.println("=========接收到的消息:" + message);
}
}
- copy m-cloud-stream-consumer-9091 构建 m-cloud-stream-consumer-9092
验证
- 生产者发送消息,消费者接收
- 同组内消息不会重复消费(启动m-cloud-stream-consumer-9092)
Stream⾼级之自定义消息通道
Stream 内置了两种接⼝Source和Sink分别定义了 binding 为 “input” 的输⼊流和 “output” 的输出流,
我们也可以自定义各种输⼊输出流(通道),但实际我们可以在我们的服务中使⽤多个binder、多个输入通道和输出通道,然⽽默认就带了⼀个input的输⼊通道和⼀个output的输出通道,怎么办?我们是可以自定义消息通道的,学着Source和Sink的样⼦,给你的通道定义个自己的名字,多个输⼊通道和输出通道是可以写在⼀个类中的
- 定义接口
interface CustomChannel {
String INPUT_LOG = "inputLog";
String OUTPUT_LOG = "outputLog";
@Input(INPUT_LOG)
SubscribableChannel inputLog();
@Output(OUTPUT_LOG)
MessageChannel outputLog();
}
- 在
@EnableBinding
注解中,绑定自定义的接口 - 使⽤
@StreamListener
做监听的时候,需要指定 CustomChannel.INPUT_LOG
bindings:
inputLog:
destination: mmExchange
outputLog:
destination: eduExchange