第4章 微服务太多,配置文件怎么维护

在前面有提到,微服务系统是一种典型的分布式系统,我们会将每个功能都尽可能地拆分一个可独立部署、运行的服务,服务部署完成后,每一次请求的完成,都可能涉及到多个服务的协调作业,面对越来越多的微服务,我们需要有一个东西可以方便地管理配置文件、最好可以在一个地方管理所有微服务的配置,这个就是我们接下来要说的分布式配置组件了。在接下来的内容中,简称这个分布式配置组件为配置中心。

4.1 初识分布式配置

在开始介绍 Spring Cloud 的配置中心实现之前,我们可以先回忆下以前我们的系统配置是怎么做的。

最开始学习的时候,很多东西都是没有配置的,基本上都是硬编码,简单、快速,系统很快构建起来了,可是随着系统的功能越来越复杂,代码量越来越大,很多地方进行硬编码已经不能满足我们的需求,我们需要在不同的环境、不同的场景下通过修改配置项来达到不一样的效果,配置项放在代码里面难找,而且修改起来会很麻烦。

于是,我们将可能频繁变动的项目提出来放在了 xml、properties 文件中,方便修改,这个时候修改配置的时候已经不需要再去碰业务代码,只要找到对应的配置文件,修改、打包、重新部署就可以了。这样子看起来已经不错了,但是随着分布式架构逐渐流行,一个一个的集群被搭建起来,这个时候再给每个集群实例搭配一个配置文件,维护的工作量就变得很大了。

这种情况运维人员一般都有一个 config list,记录了每个实例的配置文件放在哪里、叫什么名字、现在的配置项是什么等等。这个清单如果维护出错,对整个系统来说都会是一个灾难。基于这种情况,分布式系统需要一个中心化的配置中心来替代这个 config list,并且可以方便地从不同维度来进行配置,最好支持配置项的随时,并且最好能够实时生效。

4.2 Spring Cloud Config

Spring Cloud 作为一个完善的微服务框架,提供了 Spring Cloud Config 作为推荐的分布式配置组件。

Spring Cloud Config 提供了服务端和客户端,可以很好地支持分布式系统的配置需求。使用服务端可以快速构建一个独立于业务服务之外的配置中心,配置存储默认使用 git,因此它拥有 git 所拥有的一切优点。同时,它还原生支持使用 Spring 的 Environment 和 PropertySource 来获取配置项。接下来,我们将使用 Spring Cloud Config 构建一个配置中心。

4.2.1 Config Server

新建 Spring Boot 工程,引入 Spring Cloud Config 的服务端 maven 依赖,为了方便测试,我们同时引入了 web 和 actuator 的相关依赖。

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <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>

pom 文件编写完成后,在 application.properties 中添加配置信息如下:

    spring.application.name=config-server
    server.port=8888

    # 配置git仓库地址
    # 这个地址是本书代码所在git仓库地址,这里用到了仓库中的book-config-repo目录
    spring.cloud.config.server.git.uri=https://github.com/hanbin/book

    # 配置仓库路径
    spring.cloud.config.server.git.search-paths=book-config-repo

    # 去掉git权限校验
    spring.cloud.config.server.git.skip-ssl-validation=true

    # 制定远程仓库中配置文件的customer name
    spring.config.name=customer

最后,在启动类中添加@EnableConfigServer注解开启 Spring Cloud Config 的服务端功能,然后启动即可。

我们在 book-config-repo 目录下新建了配置文件:customer-dev.properties 文件。我们可以通过 customer-server 服务提供的接口来访问这个配置文件。

    /{application}/{profile}[/{label}]
    /{application}-{profile}.yml
    /{label}/{application}-{profile}.yml
    /{application}-{profile}.properties
    /{label}/{application}-{profile}.properties

举例来说,customer-dev.properties 中的就是{application}-{profile}.properties 的形式,customer 即是应用名,profile 通常用来标示不同的环境,label 在使用 git 存储时代表的是 git 的分支,不写则默认为 master。

上面这些 URI 都可以获取到远程配置,可以看到使用上面格式的地址访问。

http://localhost:8888/customer/dev
http://localhost:8888/customer/dev/master

通过上面两个链接可以得到下面的结果:

    {
        "name": "customer",
        "profiles": [
            "dev"
        ],
        "label": "master",
        "version": "97ffd82376b9ba87c5a1ffcf60b67eb4866f2415",
        "state": null,
        "propertySources": [
            {
                "name": "https://github.com/hanbin/book/book-config-repo/customer-dev.properties",
                "source": {
                    "name": "icer123",
                    "age": "1001"
                }
            }
        ]
    }

通过其他格式接口可以直接获取到的结果都是:

age: 1001
name: icer123

age 和 name 就是我们需要的配置项。

构建完成配置中心后,接下来我们看看如何将微服务构建成为“配置中心客户端”,来获取配置中心的配置项。

4.2.2 Config Client

前面我们创建了 customer 服务,这节我们会将 customer 服务作为 config-server 的 client,通过远程获取的方式拿到 customer-dev.properties 中的配置项。

在开始之前,这里要提到一个新的 endpoint:env。env 端点主要用来显示服务的配置信息。在 Spring
Boot2 中,env 端点默认是关闭的。通过添加下面的代码到 application.properties 中,可以让 env 端点能够被访问。

    management.endpoints.web.exposure.include=health,info,env

在浏览器中可以看到如图 4.1 所示的页面。

图4.1 通过浏览器查看env端点
为 customer 工程添加 Spring Cloud Config 的支持,需要在 pom.xml 文件中添加下面的代码。

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

启动项目,可以在启动日志中看到如图 4.2 所示的内容。

图4.2 微服务启动过程中加载远程配置中心

可以看到,添加了 Spring Cloud Config 的依赖后,项目在启动过程中会默认向 http://localhost:8888 请求远程配置,附带的参数 name 为项目的 spring.application.name,profiles 为 default,label 为 null。

这里的默认配置不能拿到任何信息,因为 profiles 传的不对,在 customer 工程的 application.properties 配置文件中添加。

    spring.cloud.config.uri=http://localhost:8888
    spring.cloud.config.name=customer
    spring.cloud.config.profile=dev

重新启动程序,可以看到如图 4.3 所示的启动日志。

图4.3 自定义config配置后的启动日志

通过图 4.4 可以看到现在 customer 在启动时读取到了之前我们在 config server 中的配置文件 customer-dev.properties。通过本节最开始打开的 env 端点可以查看,如图 4.4 所示。

图4.4 微服务成功读取到远程配置文件

这里有两个配置文件需要注意下:spring.application.name 和 spring.cloud.config.name。

spring.application.name 一般配置的是微服务的实例名,这个名字是微服务之间调用时候的唯一名称。spring.cloud.config.name 配置的是 Spring Cloud Config 中涉及的 application 名称,在本节的例子中,对应的就是 customer。如果去掉 spring.cloud.config.name 这个配置项,Spring Cloud Config 会使用 spring.application.name 的配置去配置中心取值。

注意:充分使用 label,profile 可以完美达到一套代码部署在多个环境,仅通过在启动参数中修改 label 和 profile 就将各个环境进行区分的目的。

4.2.3 配置刷新

前面讲的都是在系统启动之前添加配置,那么在系统运行过程中,能否“热刷新”,让配置项在运行期可以被直接改动。

Spring Cloud Config 是支持“热刷新”的,这里需要用到 refresh 端点。

还是以 customer 工程为例。为了方便测试,我们将 application.properties 中的:

management.endpoints.web.exposure.include=health,info,env

换成:

management.endpoints.web.exposure.include=*

这样,customer 的所有端口就已经全部被放开了。

我们新建 UserController.java 文件,文件内容如下:

    package cn.com.hanbinit.customer.controller;

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RefreshScope
    @RestController
    public class UserController {

        @Value("${name}")
        private String name;

        @Value("${age}")
        private Long age;

        @GetMapping("/showinfo")
        public String showNameAndAge(){
            return "name: " + name + ", age: " + age;
        }

    }

上面代码中@RefreshScope 注解是实现变量热加载的必需配置。@Value 可以获取到配置文件以及环境变量中的配置。上面代码中的 name 和 age 就分别对应了配置文件中的 name 和 age。showinfo 接口返回了@Value 获取到的属性值。

启动项目,访问 http://localhost:8001/showinfo 接口,可以看到如图 4.5 的返回。

图4.5 通过showinfo接口打印配置项的值

接下来,我们将 customer-dev.properties 中的 icer 改为 icer123,100 改为 1001,push 到远程仓库。通过 postman 工具调用 refresh 端点,如图 4.6 所示。

图4.6 调用refresh端点,刷新name和age的值

这个时候,刷新 showinfo 的调用页面,可以看到如图 4.7 的结果。

图4.7 调用refresh端点后showinfo接口的返回

这里,就说明我们的热加载已经成功。

注意:这种刷新方式仅适用于非初始化对象的情况下,如果配置项需要在系统启动的时候加载并且使用。那么在这里使用 refresh 端点是不会生效的。配置项即使刷新过去,但是确没有能真正生效。

截止到这里,我们已经可以通过 Spring Cloud Config 搭建远程的配置中心,并且可以让微服务获取到配置中心的配置项,且可以通过 refresh 端点达到热加载的目的。那么能不能实现自动刷新呢?手动调用 refresh 端点还是比较麻烦的。自动刷新当然是可以的,自动刷新后面有机会再写。

4.3 小结

本章我们使用 Spring Cloud Config 创建了配置中心,并对之前的 customer 微服务进行了改造,让 customer 服务可以远程获取到配置中心的配置。