这是我参与更文挑战的第 16 天,活动详情查看: 更文挑战
Spring Cloud Bus
一、概述
消息总线是分布式自动刷新配置功能,Spring Cloud Bus 配合 Spring Cloud config 实现配置的动态刷新。 Spring Cloud Bus整合了Java事件处理机制和消息中间件的功能,Bus支持两张消息中间件,RabbitMQ和Kafka。 Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改,事件推送等。也可以当做微服务间的通信通道。
二、环境搭建
给配置中心的服务端发送通知,由服务端传播给客户端。使用RabbitMQ做消息中间件。
在服务端3344添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
服务端3344添加RabbitMQ的配置
...
spring:
application:
name: cloud-config-center
# MQ 配置
rabbitmq:
host: 47.95.226.96
port: 5672
username: guest
password: guest
...
# MQ相关配置,暴露bus刷新端点
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
客户端3355/3366添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
客户端3355/3366添加MQ配置
spring:
application:
name: config-client
# MQ 配置
rabbitmq:
host: 47.95.226.96
port: 5672
username: guest
password: guest
测试:做完以上的配置后,再次修改配置文件的内容,并且向分布式配置中心的服务端发送一次post请求http://localhost:3344/actuator/bus-refresh
,即可广播到下面的所有客户端.
原理:ConfigClient实例都监听MQ中同一个topic,当一个服务刷新数据的时候,它会把这个信息放入到topic中,这样监听同一topic的服务就能得到通知,更新自身的配置。
动态定点刷新
每次修改配置文件后刷新指定的服务应用。比如:修改GitHub上的配置文件,只要3355可见。
向 http://localhost:3344/actuator/bus-refresh/定点刷新的服务名称:端口号
发送post请求。
如: http://localhost:3344/actuator/bus-refresh/config-client:3355
消息驱动-Spring Cloud Stream
一、概述
1、为什么引入Stream
现在消息中间件种类繁多,学习起来负担重,并且不同平台使用不同的消息中间件,同一个系统可能存在多个MQ,在实际使用中困难重重。 现在只需要一种适配绑定的方式,自动在不同的MQ之间切换。 屏蔽底层消息中间件的差异,降低切换成本,同意消息编程模型。
2、什么是Spring Cloud Stream
Spring Cloud Stream 是一个构建消息驱动微服务的框架。 应用程序通过inputs(消费者)或者outputs(发送者)来与Stream中的binder对象交互。通过配置binder(绑定),而Stream的binder对象负责与消息中间件交互。所以只需要知道如何与Stream交互就可以方便的使用消息驱动。 通过Spring Integration来连接消息代理中间件以实现消息事件驱动,引用了发布-订阅、消费组、分区三个核心概念。
3、设计思想
没有使用Spring Cloud Stream之前的消息中间件
生产者和消费者之间用消息媒介传递消息内容。消息必须走特定通道。由MessageChannel的子接口SubscribableChannel发送消息,由MessageHandler消息处理器处理所订阅的消费者获取消息。 如果一个系统即使用RabbitMQ还使用了Kafka,由于两个消息中间件的架构上的不同,所以无法直接通信。
使用Spring Cloud Stream后
Spring Cloud Stream 屏蔽底层差异的原因:通过实现定义绑定器(banner)作为中间层,完美的实现了应用程序与消费中间件的隔离。通过向应用程序暴露统一的Channel,使得应用程序不再考虑各种不同的消息中间件实现。 Banner分为inputs(消费者)或者outputs(生产者)
通过Banner绑定器作为中间层,实现了消息中间件与应用程序细节之间的隔离。
Spring Cloud Stream 标准流程和常用注解
- Banner:绑定器,连接的中间件,屏蔽差异。
- Channel:通道,类似与队列,在消息通讯系统中实现储存和转发的媒介,通过Channel对队列配置。
- Source和Sink:输出和输入。
- @Input:标识输入通道,通过该输入通道接受到的消息进入应用程序。
- @Output:标识输出通道,发布的消息通过该通道离开应用程序。
- @StreamListener:监听队列,用于消费组队列的消息接收。
- @EnableBinding:Channel和exchange绑定在一起。
二、环境搭建
1、生产者
创建
cloud-stream-rabbitmq-provider8801
模块,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
配置文件
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 配置需要绑定的rabbitmq的服务信息
defaultRabbit: # 表示定义名称,用于binding整合
type: rabbit # 消息组件类型
environment: # rabbitmq相关环境配置
spring:
rabbitmq:
host: 47.95.226.96
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 通道名称
destination: studyExchange # 表示要使用的Exchange的名称
content-type: application/json # 设置消息类型为json
binder: defaultRabbit # 设置绑定的消息服务的具体设置,此处爆红不影响启动
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 心跳时间间隔 默认30秒
lease-expiration-duration-in-seconds: 5 # 超过5秒无响应,默认90秒
instance-id: send-8801.com # 再信息列表显示主机名称
prefer-ip-address: true # 访问路径变为ip地址
生产消息的Service
@EnableBinding(Source.class) //定义消息的推送管道
public class IMessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; //消息发送管道
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println(serial);
return null;
}
}
Controller
@RestController
public class SendMessageController {
@Resource
private IMessageProvider iMessageProvider;
@GetMapping("/sendMessage")
public String sendMessage(){
return iMessageProvider.send();
}
}
访问http://localhost:8801/sendMessage
即可向RabbitMQ中发送一条消息。
2、消费者
创建
cloud-stream-rabbitmq-consumer8802
模块,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
配置文件
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 配置需要绑定的rabbitmq的服务信息
defaultRabbit: # 表示定义名称,用于binding整合
type: rabbit # 消息组件类型
environment: # rabbitmq相关环境配置
spring:
rabbitmq:
host: 47.95.226.96
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 通道名称
destination: studyExchange # 表示要使用的Exchange的名称
content-type: application/json # 设置消息类型为json
binder: defaultRabbit # 设置绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 心跳时间间隔 默认30秒
lease-expiration-duration-in-seconds: 5 # 超过5秒无响应,默认90秒
instance-id: receive-8802.com # 再信息列表显示主机名称
prefer-ip-address: true # 访问路径变为ip地址
用来接收消息的Controller
@RestController
@EnableBinding(Sink.class) //接收消息的管道
public class ReceiveMessageController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT) //监听队列
public void input(Message<String> message) {
System.out.println("consumer8802:" + message.getPayload() + "\t" + serverPort);
}
}
三、Stream之重复消费
再创建一个消费组模块cloud-stream-rabbitmq-consumer8803
,两个消费者去消费消息会出现两个问题,一个是重复消费问题,一个是消息持久化问题。
重复消费问题:现在向MQ发送一条消息,两个消费者都会消费该消息,存在重复消费问题,如果一个订单消息被两个消费者消费,就会造成数据错误。这时就可以使用Stream中的消费分组来解决,在Stream中处于同一个组的消费者是竞争关系,能够保证消息只会被一个消费者消费。
自定义分组,在8802和8803的配置文件加一个group
bindings: # 服务的整合处理
input: # 通道名称
destination: studyExchange # 表示要使用的Exchange的名称
content-type: application/json # 设置消息类型为json
binder: defaultRabbit # 设置绑定的消息服务的具体设置
group: yylmA
再次发送消息,就只会被一个消费者消费了。
四、Stream之消息持久化
如果生产者在发送消息的时候,消费者的服务器没有启动或者宕机,那么就会错过消息。同样使用上面的Stream分组解决该问题。