[TOC]


使用Nacos管理配置

为什么要需要配置管理?

在微服务架构中,每个微服务都有可能会存在多个实例,为了保证同一微服务不同实例的配置文件内容一致,我们就需要有一个服务可以对微服务项目的配置文件进行统一管理,通常我们将其称之为统一配置管理中心。因为如果配置文件的内容不一致,有可能会导致同一微服务的不同实例在行为上发生差异,从而导致一些错误。

除此之外,在企业开发中还要求项目在不同环境使用不同配置,以及可以在不重启服务的情况下实现配置文件的动态刷新,为了实现这些需求我们也得使用到统一配置管理中心

目前业界比较流行的统一配置管理中心组件有Spring Cloud的Config、Spring Cloud Alibaba的Nacos以及携程开源的Apollo。本文主要介绍Nacos作为统一配置管理中心的使用,关于其他配置中心组件以及Nacos的基本使用可以参考如下文章:

接下来,我们看看如何使用Nacos管理配置文件,现在我有一段接口代码如下:

@RestController
@RequiredArgsConstructor
public class TestController {

    @Value("${your.configuration}")
    private String yourConfiguration;

    @GetMapping("/getConfiguration")
    public String getConfiguration(){
        return yourConfiguration;
    }
}

这段代码读取了一个配置项,此时该配置项存在于项目的application.yml文件中,如下:

your:
  configuration: your_value

那么我们要如何使用Nacos管理这段配置呢?首先需要为项目整合Nacos配置管理的功能,在pom.xml文件中添加如下依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  • Tips:该项目使用的Spring Cloud版本为Greenwich.SR1,Alibaba的版本为2.1.0.RELEASE

然后在resources目录下新建一个名为bootstrap.yml的文件,添加如下配置项:

spring:
  cloud:
    nacos:
      config:
        # 指定作为配置中心的nacos server地址
        server-addr: 127.0.0.1:8848
        # 配置文件格式
        file-extension: yaml
      discovery:
        # 指定作为服务注册中心的nacos server地址
        server-addr: 127.0.0.1:8848
  application:
    name: content-center
  profiles:
    active: dev

到Nacos的管理页面的“配置列表”中新建配置:
Spring Cloud Alibaba之配置管理组件 - Nacos

新建的配置内容如下图:
Spring Cloud Alibaba之配置管理组件 - Nacos

注意,这里的Data ID是具有一定格式的,需要与项目中的配置对应上。如上图中的:content-center-dev.yaml,其中content-center是微服务名称,dev是环境,yaml是配置文件的格式。这里简单整理成了一张对照图,如下:
Spring Cloud Alibaba之配置管理组件 - Nacos

  • Tips:这里文件Data ID的后缀不一定必须是.yaml,也可以是.yml,因为这两种后缀其实都是表示同一种文件格式

到目前为止,我们就已经为项目整合了Nacos的配置管理功能,并在Nacos Server上新建了项目所需的配置。此时就可以将之前配置在application.yml文件中的配置项给删除掉。然后启动项目,访问/getConfiguration接口,返回结果如下则代表整合成功:
Spring Cloud Alibaba之配置管理组件 - Nacos


动态刷新配置及回滚

在实际的企业开发中,我们通常希望在配置中心上修改了配置项后,不需要重启项目就能够实现实时的动态配置刷新。对于整合了Nacos的微服务项目来说,想要实现这个功能是很简单的,只需要在读取了配置属性的类上加上@RefreshScope注解即可。如下示例:

// 该注解用于标识哪些地方需要动态刷新配置,可以写在类及方法上
@RefreshScope
@RestController
@RequiredArgsConstructor
public class TestController {

    @Value("${your.configuration}")
    private String yourConfiguration;

    @GetMapping("/getConfiguration")
    public String getConfiguration(){
        return yourConfiguration;
    }
}

添加完该注解后,重启项目,然后到Nacos上修改之前配置项的值,点击发布后会有一个内容比较的提示,可以看到我将之前的your_value改成了my_value
Spring Cloud Alibaba之配置管理组件 - Nacos

点击确认发布后,然后再访问/getConfiguration接口,返回的就是修改后的值了,而此时我们并没有重启项目:
Spring Cloud Alibaba之配置管理组件 - Nacos

除了以上所介绍的动态刷新之外,还有一个比较重要的功能:配置回滚。如果有一天修改某些配置项并发布后发现手滑改错了,导致项目运行出问题,那么就可以使用回滚功能可以帮助我快速回滚到某个特定的版本上。我们可以在Nacos的“历史版本”页面中查询指定配置的历史版本,如下图:
Spring Cloud Alibaba之配置管理组件 - Nacos

点击“详情”可以查看到具体的配置内容:
Spring Cloud Alibaba之配置管理组件 - Nacos

点击“回滚配置”则可以回滚到指定的历史版本,例如我这里就回滚到初始版本:
Spring Cloud Alibaba之配置管理组件 - Nacos

提示回滚成功后访问/getConfiguration接口,会发现报错了:
Spring Cloud Alibaba之配置管理组件 - Nacos

这是目前Nacos 1.1版本的一个Bug,当选择回滚的版本是最初版本时就会发生这个问题。官方称会在1.2版本中修复该问题,而我目前使用的是最新的1.1.3版本,仍属于1.1版本(2019-09-10),所以该bug还存在。相关的issues如下:

在1.1版本下,只要回滚的不是初始版本则不会触发这个bug,回滚其他版本是可以正常使用的,所以该问题注意一下即可规避

注:触发该bug后,配置内容由于回滚失败会丢失,此时需要重新创建配置


配置共享

以上我们介绍了Nacos作为配置中心的基本使用,而本小节将介绍相同应用下的配置共享,所谓的配置共享就是一些配置项可以在相同应用的不同环境中进行共享。

例如以上示例中的${your.configuration}配置项,我们希望在所有的环境下该配置项都是相同的值,而又不想在每个环境的配置文件里面都配置一遍,也就是说这个配置项是所有环境通用的,那么这时候就需要将该配置项在所有环境中共享了。

想要实现配置共享只需要新建一个配置文件即可,首先我们来看一段日志信息,当我们启动项目时,控制台会出输出如下一段日志信息:

2019-09-11 20:47:42.197  INFO 13778 --- [           main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name='NACOS', propertySources=[NacosPropertySource {name='content-center-dev.yaml'}, NacosPropertySource {name='content-center.yaml'}]}

从该日志信息可以看到,项目在启动时会到Nacos上读取两个配置文件,即content-center-dev.yamlcontent-center.yaml,其中content-center-dev.yaml里保存的是开发环境下特定的配置,而content-center.yaml里保存的则是所有环境下通用的配置。项目在启动时具体读取哪个环境的配置文件是由bootstrap.yml文件中的spring.profiles.active配置项所决定的。

既然我们知道保存在content-center.yaml里的配置项会在所有环境中共享,那么只需要到Nacos上新建这个配置文件即可。如下示例:
Spring Cloud Alibaba之配置管理组件 - Nacos

新建完成后,修改项目中的spring.profiles.active为其他环境,然后重启项目,此时访问/getConfiguration接口,可以看到返回的是通用配置文件里所配置的值:
Spring Cloud Alibaba之配置管理组件 - Nacos

  • Tips:特定环境下的配置优先级高于通用环境配置,例如${your.configuration}配置项同时存在于content-center-dev.yamlcontent-center.yaml中,若spring.profiles.active的值为dev,那么读取的将是content-center-dev.yaml里所配置的值。

以上介绍了相同应用在不同环境下的配置共享,但如何实现不同应用之间的配置共享呢?例如我们经常会遇到微服务A和微服务B的数据库配置是一样的,如果能将这段相同的配置在这两个服务之间共享,那么就可以省去一些重复的工作。

目前Nacos提供了两种方式可以实现不同应用之间的配置共享,第一种方式是使用shared-dataids。接下来我们进行一个简单的演示,首先需要到Nacos上创建一些共享配置,如下示例:
Spring Cloud Alibaba之配置管理组件 - Nacos

其中common1.yaml配置内容如下:

common1:
  configuration1: common1_value

common2.yaml配置内容如下:

common2:
  configuration2: common2_value

创建共享配置完成后,修改项目中的bootstrap.yml文件如下:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        # 指定共享配置的DataId,多个使用,分隔
        # 越靠后,优先级越高;common2.yml > common1.yaml
        # .yaml后缀不能少,目前只支持yaml/properties
        shared-dataids: common1.yaml,common2.yaml
        # 指定哪些共享配置支持动态刷新,多个使用,分隔
        refreshable-dataids: common1.yaml
        server-addr: 127.0.0.1:8848
        file-extension: yaml
  application:
    name: content-center
  profiles:
    active: dev

从以上的配置示例可以看出,其实shared-dataids的主要作用就是用来指定共享配置的Data ID,使得该微服务可以读取这些共享配置。同理,其他微服务若想读取这些共享配置,只需在项目的bootstrap.yml文件中添加相应的shared-dataids配置即可,如此一来就实现了多个微服务之间的配置共享。

然后我们来写两个简单的接口验证一下是否能正常读取这两个共享配置的值,代码如下:

@RefreshScope
@RestController
@RequiredArgsConstructor
public class TestController {

    @Value("${common1.configuration1}")
    private String common1Configuration;

    @Value("${common2.configuration2}")
    private String common2Configuration;

    @GetMapping("/getCommonConfiguration1")
    public String getCommonConfiguration1() {
        return common1Configuration;
    }

    @GetMapping("/getCommonConfiguration2")
    public String getCommonConfiguration2() {
        return common2Configuration;
    }
}

启动项目,访问结果如下:
Spring Cloud Alibaba之配置管理组件 - Nacos
Spring Cloud Alibaba之配置管理组件 - Nacos


第二种方式也是差不多的,只不过使用的配置项是ext-config,修改项目中的bootstrap.yml文件如下:

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        ext-config:
          # 需共享的DataId,yaml后缀不能少,目前只支持yaml/properties
          # 越靠后,优先级越高 优先级common2.yaml > common1.yaml
          - data-id: common1.yaml
            # common1.yaml所在的group
            group: DEFAULT_GROUP
            # 是否允许刷新,默认false
            refresh: true
          - data-id: common2.yaml
            group: DEFAULT_GROUP
            refresh: true
  application:
    name: content-center
  profiles:
    active: dev

ext-config提供了较为细化的配置方式,并且可以指定配置组。到目前为止,我们介绍了三种从Nacos上读取配置的方式,其优先级如下:

shared-dataids < ext-config < 自动

除此之外,当存在相同的远程配置和本地配置时,远程配置优先级要高于本地配置文件。但可以通过添加如下配置调整,需要注意的是这段配置需要放在远程配置才会生效:

spring:
  cloud:
    config:
      # 是否允许本地配置覆盖远程配置,默认true
      allow-override: true
      # 是否一切以本地配置为准,默认false
      override-none: false
      # 系统环境变量或系统属性才能覆盖远程配置文件的配置
      # 本地配置文件中配置的优先级低于远程配置,默认true
      override-system-properties: true

为什么要使用bootstrap.yml

在介绍为项目整合Nacos做配置管理服务的时候,示例里将Nacos Config的相关配置都写在一个bootstrap.yml文件里,但是为什么要使用bootstrap.yml呢?使用application.yml不可以吗?

这是其实是因为bootstrap.yml会被优先读取,也就是说如果项目里同时存在一个bootstrap.yml和一个application.yml的话,那么bootstrap.yml将会优先于application.yml被Spring Boot读取。这样才能实现连接外部的配置管理服务器,从远程读取一些必要的配置,避免项目在启动时缺失必要配置项而导致启动失败。

这里实际涉及到一个引导上下文的概念,它是ApplicationContext的父上下文,并且是由Spring Cloud提供的, 它所在的位置如下图所示:
Spring Cloud Alibaba之配置管理组件 - Nacos

引导上下文在prepareEnvironment的阶段就会被创建,创建时会读取bootstrap.yml的内容作为引导配置, 因此bootstrap.yml优先于application.yml加载。引导上下文与操作系统的引导程序非常类似,而bootstrap.yml就相当于设置引导程序的相关指令。


关于Nacos的数据持久化

Nacos会将数据持久化到本地,当Nacos作为服务发现组件时,默认会将服务的注册信息存储在如下目录中:

类Unix系统:~/nacos/naming
windows系统:C:\用户\{用户名}\nacos\naming

具体文件如下图所示:
Spring Cloud Alibaba之配置管理组件 - Nacos

打开其中一个文件,可以看到服务注册信息的格式如下:

{
    "metadata": {},
    "dom": "DEFAULT_GROUP@@content-center",
    "cacheMillis": 10000,
    "useSpecifiedURL": false,
    "hosts": [],
    "name": "DEFAULT_GROUP@@content-center",
    "checksum": "f797b857f5312a616a875924ecf6481a",
    "lastRefTime": 1564815242804,
    "env": "",
    "clusters": ""
}

当Nacos作为配置中心时,配置数据会分为两份并存储在两个地方,其中一份存储在内嵌的derby数据库中,这是Apache开源的一个完全由Java编写的内嵌数据库,而derby会有一个数据目录,如下:

$NACOS_HOME/data/derby-data

我们可以使用IDEA连接derby来查看相关的数据表及存储内容,在右边菜单栏中点开Database -> 点击+符号添加数据库连接 -> Data Source -> Apache Derby。如下图所示:
Spring Cloud Alibaba之配置管理组件 - Nacos

注意,此时需要将Nacos Server给停止掉,否则是无法连接的。停止Nacos Server之后,填写相关的连接信息,Path填的是derby的数据目录,用户和密码默认都是nacos。如下图所示:
Spring Cloud Alibaba之配置管理组件 - Nacos

连接成功后,可以看到derby中的数据表如下:
Spring Cloud Alibaba之配置管理组件 - Nacos

其中CONFIG_INFO表存储了配置数据,打开该表可以看到内容如下:
Spring Cloud Alibaba之配置管理组件 - Nacos

需要注意的是derby并不能用于生产环境,因为它是一个内嵌数据库,无法实现高可用和集群部署,所以一般搭建生产可用的Nacos时会将数据库改为使用MySQL等非内嵌数据库。

而另一份数据存储在如下目录,主要是一些快照等数据:

类Unix系统:~/nacos/config
windows系统:C:\用户\{用户名}\nacos\config

快照主要用于提升性能以及做容灾备份,具体文件如下图所示:
Spring Cloud Alibaba之配置管理组件 - Nacos