八、配置中心SpringCloudConfig

一、介绍:

  在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。市面上开源的配置中心有很多,BAT每家都出过,360的QConf、淘宝的diamond、百度的disconf都是解决这类问题。国外也有很多开源的配置中心Apache的Apache Commons Configuration、owner、cfg4j等等。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。

一个配置中心提供的核心功能:

  • 提供服务端和客户端支持
  • 集中管理各环境的配置文件
  • 配置文件修改之后,可以快速的生效
  • 可以进行版本管理
  • 支持大的并发查询
  • 支持各种语言
    Spring Cloud Config可以完美的支持以上所有的需求。

二、ConfigServer从本地读取配置文件

  Config Server可以从本地仓库读取配置文件,也可以从远处Git仓库读取。本地仓库是指 将所有的配置文件统一写在ConfigServer工程目录下。ConfigSever暴露HttpAPI接口,ConfigClient通过调用ConfigSever的HttpAPI接口来读取配置文件 。

2.1构建ConfigServer

  在主Maven工程下,创建一个Module工程,工程名为config-server,其pom文件继承主Maven工程的pom文件, 并在pom文件中引入ConfigServer的起步依赖sring-cloud-config-server。pom文件代码如下:

<parent>
        <groupId>com.hand</groupId>
        <artifactId>macro-service</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/>

        <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    </dependencies>
    </parent>

在程序的启动类 ConfigServerApplication加上@EnableConfigServer 注解,开启ConfigServer的功能,代码如下:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }

}

在工程的配置文件application.yml中做相关的配置,包括指定程序名为config-server,端号为8678通过spring.profiles.active=native来配置ConfigServer从本地读取配置,读取配置的路径为classpath下的shared目录。 application.yml配置文件的代码如下:

spring:
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/shared
      name: config-client
  profiles:
    active: dev
  application:
    name: config-server
server:
  port: 8678

在工程的Resources目录下建一个shared文件夹,用于存放本地配置文件。在shared目录下,新建一个config-client-dev.yml文件,用作config-client工程的dev(开发环境)的配置文件。在config-client-dev.yml配置文件中,指定程序的端口号为8673,并定义一个变量foo,变量的值为foo version 1。 代码如下:

server:
  port: 8673

foo: foo version 1

2.2 构建ConfigClient

新建Module一个工程,取名为config-client,该工程作为ConfigClient从ConfigServer读取配置文件,该工程的pom文件继承了主Maven工程的porn文件,并在其pom文件引入Config的起步依赖spring-cloud-starter-config和web功能的起步依赖spring-boot-starter-web。代码如下:

<parent>
        <groupId>com.hand</groupId>
        <artifactId>macro-service</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/>
</parent>
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
</dependencies>

在其配置文件bootstrap.yml中做程序的配置,注意这里用的是bootstrap.yml,而不是application.yml, bootstrap相对于application具有优先的执行顺序。在bootstrap.yml配置文件中指定了程序名为config-client,向Uri地址为htψ://localhost:8678的ConfigSerever读取配置文件。 如果没有读取成功,则执行快速失败(fail-fast),读取的是dev文件。 bootstrap.yml配置文件中的变量{spring. application.name}和变量 {spring.profiles.active},两者以“·”相连,构成了 向ConfigServer读取的配置文件名,所以本案例在配置中心读取的配置文件名为config-client-dev.yml文件 。 配置文件bootstrap.yml 的代码如下:

spring:
  application:
    name: config-client
  cloud:
    config:
      uri: http://localhost:8678
      fail-fast: true
  profiles:
    active: dev

management:
  security:
    enabled: false
失败快速响应

不作任何额外配置的情况下,失败响应有点迟钝,举个简单的例子,关掉config-server,我们直接启动config-client,此时启动会报错,但是报错时间较晚,报错的时候系统已经打印了许多启动日志了,如果我们希望在启动失败时能够快速响应,方式很简单,config-client中添加如下配置即可:

spring:
  cloud:
    config:
      fail-fast: true

config-server工程启动成功后,启动config-client工程,你会在控制台的日志中发现eureka-client向Uri地址为http://localhost:8678的ConfigServer读取了配置文件。最终程序启动的端口为8763,这个端口是在eureka-server的Resouces/shared目录中的eureka-client-dev.yml的配置文件中配置的,可见eureka-client成功地向eureka-server读取了配置文件。
为了进一步验证, 在config-client工程写一个API接口,读取配置文件的foo变量, 并通过API 接口返回,代码如下:

@RefreshScope
@RestController
@SpringBootApplication
public class ConfigClientApplication {

    @Value("${foo}")
    String foo;

    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApplication.class, args);
    }

    @RequestMapping("/foo")
    public String foo(){
        return foo;
    }
}

打开浏览器,访问 http://localhost:8673/foo,浏览器显示:

可见 eureka-client工程成功地向 eureka-server工程读取了配置文件中 foo变量的值。

三、 ConfigServer从远程git仓库读取配置文件

  SpringCloudConfig支持从远程 Git仓库读取配置文件,即ConfigServer可以不从本地的仓库读取,而是从远程Git仓库读取。这样做的好处就是将配置统一管理,并且可以通过SpringCloudBus在不人工启动程序的情况下对ConfigClient的配置进行刷新。这里采用GitHub作为远程Git仓库。
首先,修改ConfigServer的配置文件application.yml,代码如下 :

server:
  port: 8678
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/Cheerman/mypro         #配置git仓库的地址
          search-paths: config-respo
          username: #自己的github用户名
          password: #自己的github密码
      name: config-client
      label: master
  application:
    name: config-server

  其中,uri为远程Git仓库的地址,serachPaths为搜索远程仓库的文件夹地址,usemame和password为Git仓库的登录名和密码。如果是私人Git仓库,登录名和密码是必须的;如果是公开的Git仓库,可以不需要。label为git仓库的分支名,本例从master读取。
  将上一节的config-client-dev.yml上传到远程仓库中,上传的路径为https://github.com/Cheerman/mypro。可以自己申请GitHub账号,并在GitHub上创建代码仓库,将config-client-dev.yml上传到自己的仓库。这里我使用的是我自己创建的仓库。重新启动config-server, config-server启动成功后,启动config-client,可以发现config-client的端口为8673。 像上一节一样,访问http://localhost:8673/foo,浏览器显示 :

Git仓库的配置文件信息:

可见,config-server从远程Git仓库读取了配置文件,config-client从config-server读取了配置文件。

四、 搭建高可用的Config Server

  在上一节讲解了 Config Client如何从配置中心 Config Server读取配置文件 ,配置 中心如 何从远程 Git 仓库读取配置文件。当服务实例很多时,所有的服务实例需要同时从配置中心 Config Server读取配置文件,这时可以考虑将配置中心 Config Server做成一个微服务,并且将 其集群化,从而达到高可用。配置中心 Config Server 高可用的架构图如下图所示。 ConfigServer和ConfigClient向EurekaServer注册,且将ConfigServer多实例集群部署。


高可用的 Config Server

4.1 构建EurekaServer

这里直接使用第三章创建的eureka-server项目。

4.2 改造ConfigServer

(1)pom文件引入eureka的依赖,代码如下:

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

(2)在工程的启动类 ConfigServerApplication加上@EnableEurekaClient注解,开启 EurekaClient 的功能,代码如下 :

@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }

}

(3)在工程的配置文件 application.yml 文件中添加服务注册的地址,代码如下 :

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8671/eureka/

4.3 改造Config Client

  和ConfigServer一样作为EurekaClient, 在pom文件加上spring-cloud-starter-eureka起步依赖,在工程的启动类加上@EnableEurekaClient注解,开启EurekaClient的功能。在工程的配置文件application.yml加上相关配置,指定服务注册中心的地址为http://localhost:8671/eureka/,向ServiceId为config-server的配置服务读取配置文件,代码如下:

spring:
  application:
    name: config-client
  cloud:
    config:
      fail-fast: true
      discovery:
        enabled: true   #开启配置服务发现
        serviceId: config-server #配置服务实例名称
  profiles:
    active: dev

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8671/eureka/

  依次启动 eureka-server、config-server和config-client工程,注意这里需要config-server启动成功井且向eureka-server注册完成后,才能启动config-client,否则config-client找不到config-server。
通过控制台可以发现,config-client向地址为http即://localhost:8679的config-server读取了配置文件。访问http://localhost:8673/foo,浏览器显示:

可见, config-server从远程Git仓库读取了配置文件, config-client从config-server读取了 配置文件。

  那么如何搭建高可用的ConfigServer呢?只需要将 ConfigServer多实例部署,用IDEA开启多个ConfigServer实例,端口分别为8678和8679。在浏览器上访问EurekaServer的主页http://localhost:8761/,如下图所示:


多次启动 config-client 工程,从控制台可以发现它会轮流地从 http://localhost:8768
http://localhost:8679的ConfigServer读取配置文件,并且做了负载均衡。

五、使用SpringCloudBus刷新配置

  SpringCloudBus是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件 的更改或者服务的监控管理。一个关键的思想就是,消息总线可以为微服务做监控,也可以实现应用程序之间相互通信。 SpringCloudBus可选的消息代理组建包括RabbitMQ、AMQP和Kafka等。这里是用RabbitMQ作为SpringCloud的消息组件去刷新更改微服务的配置文件。
  如果有几十个微服务,而每一个服务又是多实例,当更改配置时,需要重新启动多个微服务实例,会非常麻烦。SpringCloudBus的一个功能就是让这个过程变得简单,当远程Git仓库的配置更改后,只需要向某一个微服务实例发送一个Post请求,通过消息组件通知其他微服务实例重新拉取配置文件。 如下所示,当远程Git仓库的配置更改后,通过发送“/bus/refresh”Post请求给某一个微服务实例,通过消息组件,通知其他微服务实例,更新配置文件。



根据此图我们可以看出利用Spring Cloud Bus做配置更新的步骤:

  • 1、提交代码触发post给客户端A发送bus/refresh
  • 2、客户端A接收到请求从Server端更新配置并且发送给Spring Cloud Bus
  • 3、Spring Cloud bus接到消息并通知给其它客户端
  • 4、其它客户端接收到通知,请求Server端获取最新配置
  • 5、全部客户端均获取到最新的配置

5.1 代码实现

5.11 添加依赖

  这里是在上面的代码基础上进行改造的 ,只需要改造config-client工程。首先,需要在pom文件中引入用RabbitMQ实现的SpringCloudBus的起步依赖spring-cloud-starter-bus-amqp。 如 果读者需要自己实践,则需要安装RabbitMQ服务器。 pom文件添加的依赖如下:

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
5.12 配置信息

  在工程的配置文件application.yml添加RabbitMQ的相关配置, host为RabbitMQ服务器的IP地址, port为RabbitMQ服务器的端口,usemame和password为RabbitMQ服务器的用户名和密码。通过消息总线更改配置,需要经过安全验证,为了方便讲述 ,先把安全验证屏蔽掉,也就是将management.security.enabled改为false。代码清单如下 :

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

management:
  security:
    enabled: false
5.13 开启更新机制

  需要在更新的配置类上加@RefreshScope注解,只有加上了该注解,才会在不重启服务的情况下更新配置,如本例中更新配置文件foo变量的值。代码清单如下:

@RefreshScope
@RestController
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientApplication {

    @Value("${foo}")
    String foo;

    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApplication.class, args);
    }

    @RequestMapping("/foo")
    public String foo(){
        return foo;
    }
}

  依次启动工程,其中config-client开启两个实例,端口分别为8672和8673。启动完成后,在浏览器上访问http://localhost:8672/foo或者http://localhost:8673/foo , 浏览器显示“ foo version 2”,如下图:


  更改远程 Git仓库,将 foo的值改为“ foo version 3”。通过Postman或者其他工具发送一个Post请求http://localhost:8672/bus/refresh,请求发送成功,再访问http://localhost:8672/foo或者http://localhost:8673/foo,浏览器都会显示“ foo version 3”,如下图:
  可见,通过向8672端口的微服务实例发送Post请求 http://localhost:8672/bus/refresh,请求刷新配置,由于使用了SpringCloudBus,其他服务实例(如案例中的8673端口的服务实例) 会接收到刷新配置的消息,也会刷新配置。

5.2 刷新方式优化

  由于每次手动刷新客户端也很麻烦,这里考虑使用自动触发的方式刷新客户端或服务端。由于这里我们使用的是github作为配置中心,故使用github的webhook就可以解决自动刷新的目的。

5.21 webhook

  WebHook是当某个事件发生时,通过发送http post请求的方式来通知信息接收方。Webhook来监测你在Github.com上的各种事件,最常见的莫过于push事件。如果你设置了一个监测push事件的Webhook,那么每当你的这个项目有了任何提交,这个Webhook都会被触发,这时Github就会发送一个HTTP POST请求到你配置好的地址。
  如此一来,你就可以通过这种方式去自动完成一些重复性工作,比如,你可以用Webhook来自动触发一些持续集成(CI)工具的运作,比如Travis CI;又或者是通过Webhook去部署你的线上服务器。下图就是github上面的webhook配置。


webhook配置
5.22 配置域名

  由于webhook的触发后回调的URL需要使用域名形式。我们将使用ngrok来做到这一点,Ngrok作为内网穿透的工具,适用于所有主流操作系统。由于使用国内的Ngrok可以免费使用自定义域名,网速更快,而且配置更加方便,这里直接介绍国内的Sunny-Ngrok使用教程:
1、打开官网,下载客户端。国内Ngrok官网
2、接着注册账号,添加隧道。关于开通隧道的教程
下图为我的隧道配置:

隧道配置信息

3、配置好隧道信息,在控制台的ngrok目录执行下面代码

./sunny clientid 你的隧道id

这里就能看到自定义域名映射成功了。如下图:


至此,webhook和ngrok的配置完成了,下面就可以通过push配置信息的修改,来触发刷新客户端配置的功能。
(1)访问http://localhost:8672/foo结果如下:

(2)修改配置文件内容为:foo version 3 提交并push到Github

(3)在webhook里看到请求push触发刷新请求成功

(4)刷新http://localhost:8672/foo结果如下:

至此,就实现了使用webhook和ngrok动态刷新配置文件的功能。

5.3 改进版本

  在上面的流程中,我们已经到达了利用消息总线触发一个客户端bus/refresh,而刷新所有客户端的配置的目的。但这种方式并不优雅。原因如下:

  • 打破了微服务的职责单一性。微服务本身是业务模块,它本不应该承担配置刷新的职责。
  • 破坏了微服务各节点的对等性。

这时SpringCloudBus做配置更新步骤如下:

  • 1、提交代码触发post请求给bus/refresh
  • 2、server端接收到请求并发送给Spring Cloud Bus
  • 3、Spring Cloud bus接到消息并通知给其它客户端
  • 4、其它客户端接收到通知,请求Server端获取最新配置
  • 5、全部客户端均获取到最新的配置
5.3.1 修改config-server

pom添加依赖:

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

修改application.yml:

spring:
  rabbitmq:
      host: 192.168.0.6
      port: 5672
      username: guest
      password: guest


management:
  security:
    enabled: false

配置文件增加RebbitMq的相关配置,关闭安全验证。这样server端代码就改造完成了。重复刷新config-client端的操作,就会发现服务加载了更新的配置信息。这里就不一一展示了。

5.4 局部刷新

  某些场景下(例如灰度发布),我们可能只想刷新部分微服务的配置,此时可通过/bus/refresh端点的destination参数来定位要刷新的应用程序。
例如:/bus/refresh?destination=customers:8000,这样消息总线上的微服务实例就会根据destination参数的值来判断是否需要要刷新。其中,customers:8000指的是各个微服务的ApplicationContext ID。
destination参数也可以用来定位特定的微服务。例如:/bus/refresh?destination=customers:**,这样就可以触发customers微服务所有实例的配置刷新。

总结:这一章节主要学习了SpringCloudConfig配置中心的搭建及应用、以及高可用配置中心的搭建,还有如何使用SpringCloudBus自动、手动刷新配置以及ngrok内网穿透配置等内容。
github源码地址

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

推荐阅读更多精彩内容