SpringCloud组件之Stream
- 支持:
Kafka和RabbitMQ - 应用场景
抛出问题:当我们项目原本使用一款MQ组件(例如kafka)时,后续版本需要切换成另外一款MQ组件(例如RabbitMq),此时面临的代价是昂贵的,因为原本项目已经和之前的MQ组件产生了耦合。
总结:在项目中需要使用MQ(消息队列)组件时,并且需要MQ组件与项目解耦,可以让项目灵活适配不同MQ产品时可以考虑采用Springcloud的Stream组件。
- 原理
图中:
MiddleWare:消息中间件(例如:Kafka)
Binder:Stream中用来绑定特定MQ的binder,Stream为不同的MQ产品设计了不同的Binder实现
ApplicationCore:我们的项目应用,通过与Binder交互来达到间接与Middleware交互
通过这三者的关系可以得出:我们在项目中只需要使用不同的Binder就能够适配不同的MQ组件。
- Stream编程注解
注解 | 描述 |
---|---|
@Input(在消费者⼯程中使⽤) | 注解标识输⼊通道,通过该输⼊通道接收到的消息进⼊应⽤程序 |
@Output(在⽣产者⼯程中使⽤) | 注解标识输出通道,发布的消息将通过该通道离开应⽤程序 |
@StreamListener(在消费者⼯程中使⽤,监听message的到来) | 监听队列,⽤于消费者的队列的消息的接收(有消息监听…) |
@EnableBinding | 把Channel和Exchange(对于RabbitMQ)绑定在⼀起 |
- 快速入门
6.1 生产端
1.添加依赖:
<!--eureka client 客户端依赖引⼊-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eurekaclient</artifactId>
</dependency>
<!--spring cloud stream 依赖(rabbit)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
2.application.yml添加配置
spring:
cloud:
stream:
binders: # 绑定MQ服务信息(此处我们是RabbitMQ)
lagouRabbitBinder: # 给Binder定义的名称,⽤于后⾯的关联
type: rabbit # MQ类型,如果是Kafka的话,此处配置kafka
environment: # MQ环境配置(⽤户名、密码等)
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 关联整合通道和binder对象
output: # output是我们定义的通道名称,此处不能乱改
destination: xxExchange # 要使⽤的Exchange名称(消息队列主题名称)
content-type: text/plain # application/json # 消息类型设置,⽐如json
binder: xxRabbitBinder # 关联MQ服务
3.业务类开发
public interface IMessageProducer {
public void sendMessage(String content);
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.support.MessageBuilder;
// Source.class⾥⾯就是对输出通道的定义(这是Spring Cloud Stream内置的通道封装)
@EnableBinding(Source.class)
public class MessageProducerImpl implements IMessageProducer {
// 将MessageChannel的封装对象Source注⼊到这⾥使⽤
@Autowired
private Source source;
@Override
public void sendMessage(String content) {
// 向mq中发送消息(并不是直接操作mq,应该操作的是spring cloudstream)
// 使⽤通道向外发出消息(指的是Source⾥⾯的output通道)
source.output().send(MessageBuilder.withPayload(content).build());
}
}
@SpringBootTest(classes = {StreamProducerApplication9090.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class MessageProducerTest {
@Autowired
private IMessageProducer iMessageProducer;
@Test
public void testSendMessage() {
iMessageProducer.sendMessage("hello world");
}
}
6.2 消费端
1.引入依赖:同生产端
2.yaml文件:在生产端的基础上,将spring.cloud.stream.bindings.output改成spring.cloud.stream.bindings.input即可
3.业务代码
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
@EnableBinding(Sink.class)
public class MessageConsumerService {
@StreamListener(Sink.INPUT)
public void recevieMessages(Message<String> message) {
System.out.println("=========接收到的消息:" + message);
}
- 自定义消息通道
7.1 定义接口
interface CustomChannel {
String INPUT_LOG = "inputLog";
String OUTPUT_LOG = "outputLog";
@Input(INPUT_LOG)
SubscribableChannel inputLog();
@Output(OUTPUT_LOG)
MessageChannel outputLog();
}
7.2 如何使⽤
1)在 @EnableBinding 注解中,绑定⾃定义的接⼝
2)使⽤ @StreamListener 做监听的时候,需要指定 CustomChannel.INPUT_LOG
bindings:
inputLog:
destination: aaaExchange
outputLog:
destination: bbbExchange
- 消息分组
解决问题:
1.重复消费:当两个消费者同时订阅了一个主题,会产生重复消费
2.消费丢失:当一个主题没有被任何消费者订阅,此时产生一个消息,该消息会丢失
解决方法:
增加group属性:属于同一个group下的消费者不会产生重复消费,也不会产生消息丢失
bindings: # 关联整合通道和binder对象
output: # output是我们定义的通道名称,此处不能乱改
destination: xxExchange # 要使⽤的Exchange名称(消息队列主题名称)
content-type: text/plain # application/json # 消息类型设置,⽐如json
binder: xxRabbitBinder # 关联MQ服务
group: group001