11 消息驱动 Stream
消息驱动 Stream
SpringCloud Stream 可以屏蔽底层消息中间件的差异,降低切换成本,同意消息的编程模型
官网:https://spring.io/projects/spring-cloud-stream#overview
API:https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.1.RELEASE/reference/html/
中文指导手册:https://m.wang1314.com/doc/webapp/topic/20971999.html
设计思想
Binder:
Spring Cloud Stream标准流程套路
编码API和常用注解
消息驱动之生产者
- 新建模块cloud-stream-rabbitmq-provider8801
- pom
<dependencies>
<!--stream rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- yml
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: #设置rabbitmq的相关环境配置
spring:
rabbitmq:
host: 112.124.22.24 #RabbitMQ在本机的用localhost,在服务器的用服务器的ip地址
port: 5672
username: admin
password: 123
bindings: #服务的整合处理
output: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的Exchange名称定义
content-type: application/json #设置消息类型,本次为json,本文要设置为“text/plain”
binder: defaultRabbit #设置要绑定的消息服务的具体设置(爆红不影响使用,位置没错)
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30S)
lease-expiration-duration-in-seconds: 5 #如果超过5S间隔就注销节点 默认是90s
instance-id: send-8801.com #在信息列表时显示主机名称
prefer-ip-address: true #访问的路径变为IP地址
- 主启动类
@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class, args);
}
}
- 新建service.IMessageProvider接口 发送消息接口、
public interface IMessageProvider {
public String send();
}
- 在service下新建impl.IMessageProviderImpl实现类
import com.atuigu.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
import javax.annotation.Resource;
import java.util.UUID;
@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output;//消息发送通道
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
//发送消息到 RabbitMQ
System.out.println("*****serial"+serial);
return null;
}
}
- 新建controller.SendMessageController
@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
@GetMapping("/sendMessage")
public String sengMessage(){
return messageProvider.send();
}
}
- 测试 启动 7001 RabbitMQ 8801
在浏览器输入:http://localhost:8801/sendMessage
,多次刷新,后台打印的数据:
在RabbitMQ后台可以看到新生成的交换机 和 消息的曲线图
消息驱动之消费者
- 新建模块cloud-stream-rabbitmq-consumer8802
- pom
<dependencies>
<!--stream rabbit -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- yml
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的名称,用于binding整合
type: rabbit #消息组件类型
environment: #设置rabbitmq的相关环境配置
spring:
rabbitmq:
host: 112.124.22.24 #RabbitMQ在本机的用localhost,在服务器的用服务器的ip地址
port: 5672
username: admin
password: 123
bindings: #服务的整合处理
input: #这个名字是一个通道的名称
destination: studyExchange #表示要使用的Exchange名称定义
content-type: application/json #设置消息类型,本次为json,本文要设置为“text/plain”
binder: defaultRabbit #设置要绑定的消息服务的具体设置(爆红不影响使用,位置没错)
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30S)
lease-expiration-duration-in-seconds: 5 #如果超过5S间隔就注销节点 默认是90s
instance-id: receive-8802.com #在信息列表时显示主机名称
prefer-ip-address: true #访问的路径变为IP地址
- 主启动类
@SpringBootApplication
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class, args);
}
}
- 新建controller.ReceiveMessageListenerController
import org.springframework.beans.factory.annotation.Value;
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;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(Sink.class)
public class ReceiverMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message){
System.out.println("消费者一号,---"+message.getPayload() + "port" + serverPort);
}
}
- 测试
启动7001,8801,8802
http://localhost:8801/sendMessage
(8801发送消息)
8802接收到消息:控制台可以看到输出的信息
分组消费与持久化
又根据8802 创建了一个 消费者 8803
同组是竞争关系,只有其中一个可以消费
8802 8803 在不同的组
故障现象:重复消费
导致原因:默认分组group 不同的 ,组流水号不同,被认为不同组,可以消费
自定义分组 再自定义配置分为同一个组,解决重复消费 问题
分组(队列)
测试分组
8802 8803 设置不同组
修改8802的yml
修改8803的yml
测试:
设置相同分组
修改8803的yml中group为angeninA,然后重启8003。
结论:同一个组的微服务实例,每次只会有一个拿到
持久化
- 停掉8802和8803,去掉8802的
group: atguiguA
。 - 然后8801发送4条消息。
- 启动8802,8802并没有去拿取消息。(因为8802去掉了
group: atguiguA
,所以启动后会再新建一个队列) - 启动8803,启动后获取到8801的消息。(因为8803没删除
group: atguiguA
,atguiguA队列是在8801发送消息前存在的,所以当8803停机后再启动,就可以获取到停机时8801发送的信息(如果此时同组(队列)里有别的消费者,那么消息会被别的消费者消费掉))
没有设置分组属性的情况下,stream会自动帮你生成一个临时队列,服务器下线就会删除队列,而带有分组属性的,就是一个持久化队列,服务器下线也不会被删除,重新上线就会获取到消息
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)