消息总线: Spring Cloud Bus
1.什么是BUS?
spring cloud是按照spring的配置对一系列微服务框架的集成,spring cloud bus是其中一个微服务框架,用于实现微服务之间的通信。spring cloud bus
整合 java的事件处理机制和消息中间件消息的发送和接受,主要由发送端、接收端和事件组成。针对不同的业务需求,可以设置不同的事件,发送端发送事件,接收端接受相应的事件,并进行相应的处理。
Spring cloud Bus
将分布式系统的节点与轻量级消息代理链接。这可以用于广播状态更改(例如配置更改)或其他管理指令。一个关键的想法是,Bus就像一个扩展的Spring Boot应用程序的分布式执行器,但也可以用作应用程序之间的通信渠道。
如下架构图所示:
2.原理
spring cloud bus
整合了java的事件处理机制和消息中间件,所以下面就从这两个方面来说明spring cloud bus的原理。
如图所示,作如下解释:
(1)完整流程:发送端(endpoint)构造事件event,将其publish到context上下文中(
spring cloud bus有一个父上下文,bootstrap
),然后将事件发送到channel中(json串message),接收端从channel中获取到message,将message转为事件event(转换过程这一块没有深究),然后将event事件publish到context上下文中,最后接收端(Listener)收到event,调用服务进行处理。整个流程中,只有发送/接收端从context上下文中取事件和发送事件是需要我们在代码中明确写出来的,其它部分都由框架封装完成。
(2)先大致描述了一下流程,关于封装的部分流程,我们基本上可以在BusAutoConfiguration.class中找到,下面的代码都是这个类中的代码:
@EventListener(classes = RemoteApplicationEvent.class)
public void acceptLocal(RemoteApplicationEvent event) {
if (this.serviceMatcher.isFromSelf(event)
&& !(event instanceof AckRemoteApplicationEvent)) {
this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build());
}
}
这是封装了java事件处理机制,当收到RemoteApplicationEvent时,如果这个event是从这个服务发出的,而且不是ack事件,那么就会把这个事件发送到channel中。
@StreamListener(SpringCloudBusClient.INPUT)
public void acceptRemote(RemoteApplicationEvent event) {
if (event instanceof AckRemoteApplicationEvent) {
if (this.bus.getTrace().isEnabled() && !this.serviceMatcher.isFromSelf(event)
&& this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(event);
}
// If it's an ACK we are finished processing at this point
return;
}
if (this.serviceMatcher.isForSelf(event)
&& this.applicationEventPublisher != null) {
if (!this.serviceMatcher.isFromSelf(event)) {
this.applicationEventPublisher.publishEvent(event);
}
if (this.bus.getAck().isEnabled()) {
AckRemoteApplicationEvent ack = new AckRemoteApplicationEvent(this,
this.serviceMatcher.getServiceId(),
this.bus.getAck().getDestinationService(),
event.getDestinationService(), event.getId(), event.getClass());
this.cloudBusOutboundChannel
.send(MessageBuilder.withPayload(ack).build());
this.applicationEventPublisher.publishEvent(ack);
}
}
if (this.bus.getTrace().isEnabled() && this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(new SentApplicationEvent(this,
event.getOriginService(), event.getDestinationService(),
event.getId(), event.getClass()));
}
}
@StreamListener
标签,这个方法就是从channel中取出事件进行处理的过程(message转事件部分需要自行了解,我没有深入研究),根据事件的类型、发送方和接收方来处理这个事件:如果是ack事件,发送到context上下文中;如果自己是接收端且不是发送端,就会将事件发送到context上下文。
3.如何整合BUS?
消息总线可支持的有:
ActiveMQ是比较老牌的消息系统,当然了不一定是大家第一个熟知的消息系统,因为现在电商、互联网规模越来越大,不断进入程序员眼帘的大多是Kafka和RocketMQ。ActiveMQ出现的要比他们早,而且涵盖的功能也特别全,路由、备份、查询、事务、集群等等。他的美中不足是不能支撑超大规模、超高并发的互联网应用,ActiveMQ的并发承受能力在百万级别,大概500次/s的消息频率。
Kafka是新一代的消息系统,相对于ActiveMQ来说增加了分片功能,类似于数据库分库分表,一台Broker仅负责一部分数据收发,从而使得他的伸缩性特别好,通过增加Broker就可以不断增加处理能力。一般来说,Kafka被用来处理日志流,作为流计算的接入点。在电商的订单、库存等系统里边一般不用,主要顾虑的是Kafka异步刷盘机制可能导致数据丢失。当然,对于数据丢失这一点不同的工程师也有不同的看法,认为Kafka的Master-Slave的多写机制,完全能够避免数据丢失。
RocketMQ是阿里开源的一款消息系统,开发的初衷就是要支撑阿里庞大的电商系统。RocketMQ和Kafka有很多相似之处,由于RocketMQ开发中很大程度上参考了Kafka的实现。RocketMQ同样提供了优秀的分片机制,RocketMQ的分片比Kafka的分片有所增强,区分了绝对有序和非绝对有序两种选项。另外RocketMQ采用的是同步刷盘,一般认为不会造成数据丢失。
RabbitMQ类似于ActiveMQ也是一个相对小型的消息系统,他的优势在于灵活的路由机制,可以进行自由配置。
Redis的pub/sub功能,由于Redis是内存级的系统,所以速度和单机的并发能力是上述四个消息系统不能比拟的,但是也是由于内存存储的缘故,在消息的保障上就更弱一些。
Kafka为例:
Kafak架构图如下:
Kafka是基于消息发布/订阅模式实现的消息系统,其主要设计目标如下:
1.消息持久化:以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间复杂度的访问性能。
2.高吞吐:在廉价的商用机器上也能支持单机每秒100K条以上的吞吐量
3.分布式:支持消息分区以及分布式消费,并保证分区内的消息顺序
4.跨平台:支持不同技术平台的客户端(如:Java、PHP、Python等)
5.实时性:支持实时数据处理和离线数据处理
6.伸缩性:支持水平扩展
Kafka中涉及的一些基本概念:
1.Broker:Kafka集群包含一个或多个服务器,这些服务器被称为Broker。
2.Topic:逻辑上同Rabbit的Queue队列相似,每条发布到Kafka集群的消息都必须有一个Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个Broker上,但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)
3.Partition:Partition是物理概念上的分区,为了提供系统吞吐率,在物理上每个Topic会分成一个或多个Partition,每个Partition对应一个文件夹(存储对应分区的消息内容和索引文件)。
4.Producer:消息生产者,负责生产消息并发送到Kafka Broker。
5.Consumer:消息消费者,向Kafka Broker读取消息并处理的客户端。
6.Consumer Group:每个Consumer属于一个特定的组(可为每个Consumer指定属于一个组,若不指定则属于默认组),组可以用来实现一条消息被组内多个成员消费等功能。
可以从kafka的架构图看到Kafka是需要Zookeeper支持的,你需要在你的Kafka配置里面指定Zookeeper在哪里,它是通过Zookeeper做一些可靠性的保证,做broker的主从,我们还要知道Kafka的消息是以topic形式作为组织的,Producers发送topic形式的消息,
Consumer是按照组来分的,所以,一组Consumers都会都要同样的topic形式的消息。在服务端,它还做了一些分片,那么一个Topic可能分布在不同的分片上面,方便我们拓展部署多个机器,Kafka是天生分布式的。
4.什么时候用cloud bus
spring cloud bus
在整个后端服务中起到联通的作用,联通后端的多台服务器。我们为什么需要他做联通呢?
后端服务器一般都做了集群化,很多台服务器,而且在大促活动期经常发生服务的扩容、缩容、上线、下线。这样,后端服务器的数量、IP就会变来变去,如果我们想进行一些线上的管理和维护工作,就需要维护服务器的IP。
比如我们需要更新配置、比如我们需要同时失效所有服务器上的某个缓存,都需要向所有的相关服务器发送命令,也就是调用一个接口。
你可能会说,我们一般会采用zookeeper的方式,统一存储服务器的ip地址,需要的时候,向对应服务器发送命令。这是一个方案,但是他的解耦性、灵活性、实时性相比消息总线都差那么一点。
总的来说,就是在我们需要把一个操作散发到所有后端相关服务器的时候,就可以选择使用cloud bus了。
spring cloud config 配合spring cloud bus实现配置信息更新
spring cloud config 配置更新有两种方式:1.配置git仓库的web hook,当git仓库有更新时自动调用bus提供的刷新接口,刷新缓存;2.手工调用bus提供的刷新接口。
不论一方案还是二方案区别仅在于是不同的人触发了刷新接口。实际上,线上服务器一般很少采用自动刷新的机制,都会在修改后,确认无误后再执行刷新。
关键的修改点是把所有的后端服务器连接到同一个消息系统上,然后监听配置更新消息。
安装RabbitMQ
安装方法很简单,直接在官网下载对应的安装文件就可以了。
因为RabbitMQ是Erlang语言写的,所以如果你的机器上没有安装Erlang,那么需要先安装Erlang。
增加bus包的引用
org.springframework.cloud
spring-cloud-starter-bus-amqp
增加RabbitMQ配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5671
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
增加@RefreshScope注解
@SpringBootApplication
@RestController
@RefreshScope
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
@Value("${app-name}")
private String app_name;
@RequestMapping("hi")
public String hi(){
return "hello "+ app_name;
}
}
spring cloud扩展消息总线方法
可以参考spring cloud bus 扩展消息总线方法
5.Spring Cloud 集成Kafka
pom.xml加入依赖
org.springframework.cloud
spring-cloud-starter-bus-kafka
加入以上依赖后,Spring Cloud 消息总线 Kafka 已经集成完成,使用的配置则是默认启动 Kafka 和 ZooKeeper 时的配置。确定 ZooKeeper 和 Kafka 已经启动,然后再启动有上面依赖的 Spring Boot 应用 ,这时 Kafka 会新增一个名为 springCloudBus 的 Topic,可以使用命令 kafka-topics --list --zookeeper localhost:2181 来查看当前 Kafka 中的 Topic。
集成后Kafka 配置
以上的例子中 Kafka、ZooKeeper 均运行于本地,但实际应用中,Kafka 和 ZooKeeper 一般会独立部署,所以需要为Kafka 和 ZooKeeper 配置一些连接信息,Spring Boot 1.3.7 没有为 Kafka 直接提供 Starter 模块,而是使用 Spring Cloud Stream 的 Kafka 模块,配置的时候则采用 spring.cloud.stream.kafka 前缀
spring.cloud.stream.kafka 配置
spring:
cloud:
stream:
binders:
#binderName
kafka1:
type: kafka
environment:
spring:
cloud:
stream:
kafka:
binder:
#kafka地址
brokers: localhost:9092
#zookeeper节点地址
zk-nodes: localhost:2181
bindings:
#channelName
channelKafka:
#binderName
binder: kafka1
destination: event-demo
content-type: text/plain
producer:
partitionCount: 1
spring.kafka 配置
在启动Kafka的时候有这样一个配置 config/server.properties 的 zookeeper.connect 指定 ZooKeeper连接地址,但是在 spring.kafka 中并没有看到可以配置 ZooKeeper 连接地址 的地方
spring:
kafka:
consumer:
#消费者服务器地址
bootstrap-servers: localhost:9092
producer:
#生产者服务器地址
bootstrap-servers: localhost:9092
cloud:
stream:
bindings:
channel1:
destination: event-demo
content-type: text/plain
producer:
partitionCount: 1
Less is more.