SpringCloud10-Stream
SpringCloud10-Stream
1.Stream
-
常见的消息中间件有ActiveMQ、RabbitMQ、RocketMQ、Kafka,多种消息中间件增加了维护成本,而Stream屏蔽底层消息中间件的差异,降低降低切换成本;同时提供统一消息的编程模型,自动的给我们在各种MQ内切换。
-
Stream官网。https://docs.spring.io/spring-cloud-stream/docs/3.1.4/reference/html/。
-
Stream目前仅支持RabbitMQ、 Kafka之间的切换。
2.Stream核心概念
- Binder,很方便的连接中间件,屏蔽差异。
- Channel,通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置。
- Source和Sink,以Spring Cloud Stream自身为参照对象,从Stream发布消息就是输出,接受消息就是输入。
3.Stream工作流程
- 应用程序通过inputs或者outputs来与Stream中Binder对象交互。
- Stream的Binder对象负责与消息中间件交互。
- 工作流程图。
4.创建消息生产者微服务cloud-stream-rabbitmq-provider8801
- pom.xml
<!-- stream-rabbitMQ -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- yml
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders:
defaultRabbit: # 表示定义的名称,用于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
bindings:
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
- Main
@EnableEurekaClient
@SpringBootApplication
public class CloudStreamRabbitMQProvider8001Main {
public static void main(String[] args) {
SpringApplication.run(CloudStreamRabbitMQProvider8001Main.class, args);
}
}
- controller省略
- service
public interface IMessageProvider {
String send();
}
@Slf4j
@EnableBinding(value = {
Source.class
}) //3.1过时,可以使用 Input和Output
public class MessageProviderImpl implements IMessageProvider {
/**
* Stream核心
* Source或者Sink
* Channel。output,消息的提供者,生产者,相对于当前应用将消息输出到 RabbitMQ中
* Binding
*/
@Resource
private MessageChannel output;
@Override
public String send() {
UUID randomUUID = UUID.randomUUID();
output.send(MessageBuilder.withPayload(randomUUID).build());
log.info("message : {}", randomUUID);
return randomUUID.toString();
}
}
- 服务启动后会在,RabbitMQ中创建一个studyExchage的topic。
7.创建消息消费者cloud-stream-rabbitmq-consumer8802
- pom.xml、yml、Main不变。
- controller
@Slf4j
@RestController
@EnableBinding(Sink.class)
public class MessageController {
@Value("${server.port}")
private String port;
// 监听
@StreamListener(Sink.INPUT)
public void receive(Message<String> message) {
log.info("message {}", message.getPayload() + "=====" + port);
}
}
- 创建消息消费者cloud-stream-rabbitmq-consumer8803和8802保持一致。
- 使用8801生产消息,8802和8803同时消费消息。
8.重复消费消息的问题
- 不同的组是可以重复消费的,同一个组内会发生竞争关系,只有其中一个可以消费。
- stream在创建8802和8803时,默认情况下,没有指定分组,RabbitMQ会使用流水号作为组名。如 studyExchange.anonymous.CCrLMvYjQbmhtI7xrl0rnw。所以两个消费者会被放入不同的组中,产生重复消费的问题。
- 解决方法。将8802和8803放在相同的组中。同一个组内会发生竞争关系,只有其中一个可以消费。
- 为消息的消费者添加分组。
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
# 8802和8803同时放在cloud.stream.rabbitmq.a组中,将不会产生消息重复消费的问题。
group: cloud.stream.rabbitmq.a
9.消息的持久化
- 当消费的消费者停机后再启动,是不会接受到停机这段时间RabbitMQ的消息,会造成消除丢失。可以使用分组类解决。
- 配置了group分组,停机后启动,可以接受到停机这段时间的消息的。但是如果8802和8803都停机了,8802和8803都配置了分组,且在同一个组中,8802消费完了消费停机时的消息,8803在启动时不会重复消费的。