Spring Cloud系列之 ConfigBusStreamSleuth

Posted AC_Jobim

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud系列之 ConfigBusStreamSleuth相关的知识,希望对你有一定的参考价值。

Spring Cloud系列(四)之 Config、Bus、Stream、Sleuth

使用SpringCloud版本:2.1.1.RELEASE

一、Config分布式配置中心

1.1 概述

分布式系统面临的问题:

  • 分布式系统中,由于服务数量非常多,配置文件分散在不同微服务项目中,管理极其不方便。为了方便配置文件集中管理,需要分布式配置中心组件。

是什么?

  • 在Spring Cloud中,提供了Spring Cloud Config,每个服务单元从config server中获取具体的配置文件,它支持配置文件放在配置服务的本地,也支持配置文件放在远程仓库Git(GitHub、码云)。配置中心本质上是一个微服务,同样需要注册到Eureka服务中心!

Spring Cloud Config 分为服务端和客户端两部分。

  • 服务端也成为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器,并为客户端提供获取配置信息、加密解密信息灯访问接口
  • 客户端则是通过指定的配置中心来管理应用资源以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息,配置服务器默认使用 git 来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过 git 客户端工具来方便的管理和访问配置内容

Config 结构图:

配置中心整合步骤:

  1. 配置文件集中放在码云
  2. 配置中心获取码云配置文件
  3. 用户服务获取配置中心文件

1.2 基本使用

1.2.1 Config服务端配置与测试

  1. 配置文件集中放在码云
  2. 配置中心获取码云配置文件
  3. 用户服务获取配置中心文件

1.2.1.1 Git 远程服务器配置

  • 在码云上新建一个名为springcloud-config的新Repository,并创建如下文件:

1.2.1.2 服务端配置测试

服务端:也称为 分布式配置中心,它是一个独立的微服务应用

  • 新建Module模块 cloud-config-center-3344,它就是Cloud的配置中心模块 cloudConfig Center

  • 引入 pom.xml 依赖

    <!--引入spring-cloud-config-server依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    
  • 配置文件 application.yml 修改

    server:
      port: 3344
    
    spring:
      application:
        name:  cloud-config-center # 注册进Eureka服务器的微服务名
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/jobim/springcloud-config.git # GitHub上面的git仓库名字
              # 搜索目录
              search-paths:
                - springcloud-config
          # 读取分支
          label: master
    
    # 服务注册到eureka地址
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
  • 主启动类,配置@EnableConfigServer注解

    @SpringBootApplication
    @EnableConfigServer
    public class ConfigCenterMain3344 
        public static void main(String[] args) 
                SpringApplication.run(ConfigCenterMain3344.class, args);
        
    
    
  • 启动服务端模块,测试

    启动 Config 模块后,测试通过 Config 微服务是否可以从上获取配置内容。我们通过地址:http://localhost:3344/master/config-dev.yml 进行配置内容的访问。

1.2.1.3 git远程仓库配置文件读取规则

  • 远程 GitHub 仓库,配置文件的命名也是有具体规则的。Spring Cloud Config 官方共支持 5 种方式的配置。5种配置规则见:

    /application/profile/label
    /application-profile.yml (这种不带label方式,默认使用 application.yml 配置)
    /label/application-profile.yml (推荐使用第三种)
    /application-profile.properties
    /label/application-profile.properties
        
    参数说明:
    1. `label`:GitHub 分支(branch)名称
    2. `application`:服务名
    3. `profile`:环境(`dev/test/prod`)
    
  1. /application/profile/label 这种方式,返回的是 Json 对象,需要自己解析所要的内容;

  2. /application-profile.yml 这种不带 label 方式,因为 applicaiton.yml 文件已经有配置过 label,不带label 方式,默认走的就是 yml 配置的 label,返回的是配置内容;

  3. /label/application-profile.yml 推荐使用第3种,这种方式简明扼要,条理清晰,返回的是配置内容;

  4. application-profile.properties 同第2种;

  5. label/application-profile.properties 同第3种

1.2.2 Config客户端配置与测试

客服端:在启动的时候从 配置中心(Config Server) 获取和加载配置信息。

  • 创建客户端模块,用来读取ConfigServer配置。此处模块名称为:cloud-config-center-3355

  • 引入 pom.xml 依赖

    <!--引入spring-cloud-starter-config依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    
  • 配置文件 bootstrap.yml 修改

    注意:此处需要增加的是一个 bootstap.yml 文件。application.yml 是**(用户级)的资源配置项。bootstrap.yml 是(系统级)**的资源配置项,优先级更高。

    Spring Cloud 会创建一个 “Bootstrap Context”,作为 Spring 应用的 “Application Context” 的 父上下文。初始化的时候,“Bootstrap Context” 负责从 外部源 加载配置属性并解析配置。这两个上下文共享一个从外部获取的 “Environment”。

    "Bootstrap" 属性有高优先级。默认情况下,它们不会被本地配置覆盖。 “Bootstrap Context” 和 “Application Context” 有这不同的约定,所以新增了一个 "bootstrap.yml" 文件,保证"Bootstrap Context" 和 "Application Context" 配置的分离。

    所以,将客户端模块下的 application.yml 文件改为bootstrap.yml,这是很关键的。 因为 bootstrap.yml 是比 application.yml 优先加载的。bootstrap.yml 优先级高于 applicaiton.yml。

    bootstrap.yml 配置内容:

  • 主启动类 配置@EnableEurekaClient注解

    @EnableEurekaClient
    @SpringBootApplication
    public class ConfigClientMain3355 
        public static void main(String[] args) 
            SpringApplication.run(ConfigClientMain3355.class,args);
        
    
    
  • controller业务类

    @RestController
    @RefreshScope
    public class ConfigClientController 
    
        @Value("$config.info")
        private String configInfo;
    
        @GetMapping("/configInfo")
        public String getConfigInfo() 
            return configInfo;
        
    
    
  • 启动客户端模块,测试

    Config 模块需要注册到 Eureka Server,先启动 Eureka 服务。然后启动 Config Server 3344 模块,最后启动 Client 3355 模块。我们通过地址:http://localhost:3355/configInfo 发现可以成功读取到 ConfigServer 中的 config-dev.yml 配置。

    需要获取 testprod 等环境配置信息,只需要修改 bootstrap.yml 配置内容 中的相关属性,即可获取响应环境的配置信息。

1.3 分布式配置的动态刷新问题

我们实现了客户端3355通过Config3344获取GithHub上的配置信息,那么现在面临另外一个问题:

当 GitHub 上的配置文件内容有调整,Github中配置变更后,ConfigServer 配置中心会立刻响应,然而客户端却没有任何响应,除非客户端重启或者重新加载,才能够获取最新的配置。 难道每次修改配置文件,客户端都需要重启吗?

为了避免每次修改 GitHub 配置文件后,客户端都需要重启的问题,此处就引出了客户端 动态刷新 的问题。

接下来对客户端3355模块进行 动态刷新 配置。

  • pom.xml 引入依赖

    <!--引入actuator监控-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  • 修改YML,新增暴露监控端口配置

    #暴露监控端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"  #此处有很多选项可以配置,为了省事 ,直接配置 *
    
  • Controller层添加@RefreshScope注解

    @RestController
    @RefreshScope
    public class ConfigClientController 
    
        @Value("$config.info")
        private String configInfo;
    
        @GetMapping("/configInfo")
        public String getConfigInfo() 
            return configInfo;
        
    
    
  • 修改远程 GitHub 配置后,手动发送Post请求刷新客户端(3355端口)

    • 发现GitHub上修改后,3355还是没有刷新

    • 还需要让运维人员发送一下POST请求,刷新一下3355 必须是Post请求,curl -X POST "http://localhost:3355/actuator/refresh"

    • 发送post请求刷新后,客户端3355正常

手动版动态刷新,存在的问题:

  • 实现了动态刷新,解决了 ConfigClient 重启才能获取最新配置信息问题。假如有 N 多个台,就需要 N 多次的 curl -X POST "http://微服务地址:端口号/actuator/refresh"。这仍然是一个噩梦,还是没有解决根本问题。

  • 解决方法——Bus总线

二、SpringCloud Bus 消息总线

2.1 基本介绍

  • Spring Cloud Bus 目前支持两种消息代理:RabbitMQ、Kafka

  • Spring Cloud Config 配合 Spring Cloud Bus 使用可以实现配置的动态刷新

基本原理:

  • Config 客户端示例,都去监听 MQ 中的同一个 topic(默认是 springCloudBus)。当一个服务刷新数据的时候,它会把这个消息放入到 Topic 中,这样其他监听同一 Topic 的服务就能够得到通知,然后去更新自身的配置。就是通过 MQ 消息队列的 Topic 机制,达到广播的效果。

bus 动态刷新全局广播有两种设计思想:

  1. 利用消息总线触发一个客户端的 /bus/refresh,进而刷新所有客户端的配置
  2. 利用消息总线触发一个服务端 ConfigServer 的 /bus/refresh,进而刷新所有客户端的配置

我们采用第二种,第一种方式不适合的原因有三:

  1. 打破了微服务的职责单一性。负责业务模块的微服务不应该承担配置刷新的职责
  2. 破坏了微服务各节点的对等性
  3. 有一定的局限性。例如微服务迁移时,它的网络地址常常发生变化,如果想要做到自动刷新,还需要增加更多的配置

2.2 Bus 动态刷新全局广播配置

前提:必须先具备良好的RabbitMQ环境

  • 添加一个新的cloud-config-client-3366,与 3355 组成集群模式。

    参考 3355,此处不再介绍

  • 服务端配置中心 Config Server (3344)、客户端集群(3355/3366) 中引入 Bus 总线依赖

    <!--添加消息总线RabbitMQ支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
  • 服务端配置中心 application.yml 修改 (添加 rabbitmq 相关配置)

    server:
      port: 3344
    
    spring:
      application:
        name:  cloud-config-center # 注册进Eureka服务器的微服务名
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/jobim/springcloud-config.git # GitHub上面的git仓库名字
              # 搜索目录
              search-paths:
                - springcloud-config
          # 读取分支
          label: master
      #rabbitmq相关配置
      rabbitmq:
        host: 192.168.2.4
        port: 5672
        username: admin
        password: 123
    
    # 服务注册到eureka地址
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
    ##rabbitmq相关配置,暴露bus刷新配置的端点
    management:
      endpoints: #暴露bus刷新配置的端点
        web:
          exposure:
            include: 'bus-refresh'
    
  • 客户端 application.yml 修改(添加 rabbitmq 相关配置)

    #添加rabbitmq相关支持
    spring:
      rabbitmq:
        host: 192.168.2.4
        port: 5672
        username: admin
        password: 123
    
    #暴露监控端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"  #此处有很多选项可以配置,为了省事 ,直接配置 *
    
  • 启动模块,开始测试

    启动:服务端配置中心 Config Server (3344)、客户端集群(3355/3366)、Eureka Server(7001)。修改码云参数配置,然后向 服务端 发送 Post 请求,命令:curl -X POST "http://localhost:3344/actuator/bus-refresh"

    当向 Config Server 发送 Post 请求后,总线上的各个实例(客户端 3355/3366)都能够及时 监听和消费 配置的变更。使用广播的方式,真正的实现 一处通知,处处生效。测试如图所示:

  • 基本原理回顾

    使用 MQ 广播的方式,实现 一处通知,处处生效 的效果。此时我们登陆 Rabbit MQ 客户端,在 Exchanges 模块,就能够看到一个叫做 springCloudBus 的 Topic。

    Config 客户端示例,都去监听 MQ 中的同一个 topic(默认是 springCloudBus)。当一个服务刷新数据的时候,它会把这个消息放入到 Topic 中,这样其他监听同一 Topic 的服务就能够得到通知,然后去更新自身的配置。

2.3 Bus 动态刷新定点通知配置

  • 如果需要 差异化通知,并不想进行全局广播,此时就用到了 Bus 的 定点通知 功能。

  • 此次我们通过客户端集群(3344/3355)演示。GitHub 远程配置修改后 ,进行差异化定点通知,只通知 3355,不通知 3366。此处命令和全局广播有点不同,命令为:http://配置中心IP:配置中心的端口号/actuator/bus-refresh/destination,通过指定 /bus/refresh请求 不再发送到具体的服务实例上,而是发给 Config Server 并通过 destination 参数 来指定需要更新配置的服务或实例。

  • destination 参数 = 微服务名 :端口号。3355 微服务名为:config-client。此处最终发送的 Post 请求命令为:curl -X POST http://localhost:3344/actuator/bus-refresh/config-client:3355,真正的实现 精确通知 功能。

    成功:3355刷新,3366未刷新

三、Stream 消息驱动

微服务面临的问题

  • 自己学的是 RabbitMQ,公司用的却是 Kafka 。再学 Kafka?学习成本太高,负担太重

  • 有没有一种技术,可以让我们不再关注 MQ 的细节,只需要用一种适配绑定的方式,就可以帮助我们自动的在各种 MQ 之间切换呢?Spring Cloud Stream 消息驱动,它来了。

3.1 简介

  • Spring Cloud Stream 一个构建消息微服务驱动的框架,它可以屏蔽底层 MQ 之间的细节差异。我们只需要操作Spring Cloud Stream 就可以操作底层多种多样的MQ(目前仅支持 RabbitMQ 和 Kafka)。从而解决我们在 MQ 切换维护开发 方面的难度问题。

标准MQ:

  • 生产者/消费者 之间通过 消息媒介 传递消息内容

Spring Cloud Stream 的设计思想

  • **通过定义绑定器 Binder 作为中间层,实现了应用程序与消息中间件细节之间的隔离。**向应用程序暴露统一的 Channel 通道,使得应用程序不需要再考虑各种消息中间件的实现
  • inputs 对应消费者,outputs 对应生产者
  • Stream中的消息通信方式遵循了发布-订阅模式,用 Topic 主题进行广播(在RabbitMQ就是Exchange,在Kafka中就是Topic)

工作流程:

  • Binder:绑定器,很方便的连接中间件,屏蔽差异

  • Channel:通道,是队列 Queue 的一种抽象,在消息通讯系统中就是实现存储与转发的媒介,通过 Channel 对队列进行配置

  • Source 和 Sink:简单理解就是参照物是 Spring Cloud Stream 本身,从 Stream 发布消息就是输出,接收消息就是输入

编码 API 和常用注解:

3.2 基本使用

项目结构:

3.2.1 消息生产者

消息生产者模块,命名为:cloud-stream-rabbitmq-provider8801

1、引入pom依赖(引入stream-rabbit依赖)

<dependencies>
    <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.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--stream-rabbit依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
</dependencies>

2、修改application.yml文件

server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 192.168.2.4
                port: 5672
                username: admin
                password: 123
      bindings: # 服务的整合处理
        output: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
          binder: defaultRabbit # 设置要绑定的消息服务的具体设置


eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost: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地址

3、启动类

@SpringBootApplication
public class StreamMQMain8801 
    public static void main(String[] args) 
        SpringApplication.run(StreamMQMain8801.class,args);
    

3、业务类编写

  1. interface接口

    public interface IMessageProvider 
        String send() ;
    
    
  2. service

    import com.zb.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 指信道channel和exchange绑定在一起
    //@EnableBinding(Source.class) 就是将 Source(源) 放到 Channel 的意思
    @EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
    public class MessageProviderImpl implements IMessageProvider 
    
        @Resource
        private MessageChannel output; // 消息的发送管道
    
        @Override
        public String send() 
            String serial = UUID.randomUUID().toString();
            this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
            System.out.println("***serial: "+serial);
            return serial;
        
    
    
  3. controller

    @RestController
    public class SendMessageController 
    
        @Resource
        private IMessageProvider messageProvider;
    
        @GetMapping(value = "/sendMessage")
        public String sendMessage() 
            return messageProvider.send();
        
    
    
    

4、客户端测试

启动RabbitMQ、微服务相关模块,通过接口调用 http://localhost:8801/sendMessage 进行消息发送,可以看到后台有显示发送消息,进入 RabbitMQ 可视化界面,可以看到有发送消息波峰出现。

3.2.2 消息消费者

消息消费者模块,命名为:cloud-stream-rabbitmq-provider8802

1、引入pom依赖(引入stream-rabbit依赖)

<dependencies>
    <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.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--stream-rabbit依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
</dependencies>

2、修改application.yml文件

server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
        

以上是关于Spring Cloud系列之 ConfigBusStreamSleuth的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud系列之链路追踪

Spring Cloud系列之 RibbonOpenFeign

Spring Cloud系列之 RibbonOpenFeign

Spring Cloud 系列之 Config 配置中心

Spring Cloud 系列之 Config 配置中心

Spring Cloud系列之 EurekaZookeeperConsul