Chapter Nine《SpringCloud微服务实战》

消息总线: Spring Cloud Bus

1.什么是BUS?

spring cloud是按照spring的配置对一系列微服务框架的集成,spring cloud bus是其中一个微服务框架,用于实现微服务之间的通信。spring cloud bus整合 java的事件处理机制和消息中间件消息的发送和接受,主要由发送端、接收端和事件组成。针对不同的业务需求,可以设置不同的事件,发送端发送事件,接收端接受相应的事件,并进行相应的处理。

Spring cloud Bus将分布式系统的节点与轻量级消息代理链接。这可以用于广播状态更改(例如配置更改)或其他管理指令。一个关键的想法是,Bus就像一个扩展的Spring Boot应用程序的分布式执行器,但也可以用作应用程序之间的通信渠道。

如下架构图所示:

image.png

2.原理

spring cloud bus整合了java的事件处理机制和消息中间件,所以下面就从这两个方面来说明spring cloud bus的原理。

image.png

如图所示,作如下解释:

(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?

消息总线可支持的有:

image.png

ActiveMQ是比较老牌的消息系统,当然了不一定是大家第一个熟知的消息系统,因为现在电商、互联网规模越来越大,不断进入程序员眼帘的大多是Kafka和RocketMQ。ActiveMQ出现的要比他们早,而且涵盖的功能也特别全,路由、备份、查询、事务、集群等等。他的美中不足是不能支撑超大规模、超高并发的互联网应用,ActiveMQ的并发承受能力在百万级别,大概500次/s的消息频率。

image.png

Kafka是新一代的消息系统,相对于ActiveMQ来说增加了分片功能,类似于数据库分库分表,一台Broker仅负责一部分数据收发,从而使得他的伸缩性特别好,通过增加Broker就可以不断增加处理能力。一般来说,Kafka被用来处理日志流,作为流计算的接入点。在电商的订单、库存等系统里边一般不用,主要顾虑的是Kafka异步刷盘机制可能导致数据丢失。当然,对于数据丢失这一点不同的工程师也有不同的看法,认为Kafka的Master-Slave的多写机制,完全能够避免数据丢失。

image.png

RocketMQ是阿里开源的一款消息系统,开发的初衷就是要支撑阿里庞大的电商系统。RocketMQ和Kafka有很多相似之处,由于RocketMQ开发中很大程度上参考了Kafka的实现。RocketMQ同样提供了优秀的分片机制,RocketMQ的分片比Kafka的分片有所增强,区分了绝对有序和非绝对有序两种选项。另外RocketMQ采用的是同步刷盘,一般认为不会造成数据丢失。

image.png

RabbitMQ类似于ActiveMQ也是一个相对小型的消息系统,他的优势在于灵活的路由机制,可以进行自由配置。

image.png

Redis的pub/sub功能,由于Redis是内存级的系统,所以速度和单机的并发能力是上述四个消息系统不能比拟的,但是也是由于内存存储的缘故,在消息的保障上就更弱一些。

Kafka为例:

Kafak架构图如下:

image.png

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包的引用

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

增加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加入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>

加入以上依赖后,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.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容