12 配置中心:基于 Naco 集中管理应用配置
12 配置中心:基于 Naco 集中管理应用配置
上一讲我们讲解 Sentinel 中熔断与限流的处理方式,了解了 Sentinel 对系统实施保护的原理。
本讲咱们对原有主线内容做一个扩展,先来讲解基于 Nacos 如何集中管理应用配置,实现微服务架构中"配置中心"组件。本讲内容与本章"系统保护"的主题并不冲突,因为构建 Nacos 配置中心正是 Sentinel 实现集群保护的基础所在,前面咱们在 Dashboard 配置的一系列规则都要在生产环境中基于 Nacos 配置中心进行持久化存储。
本讲咱们将讲解以下三方面内容:
为什么微服务架构必须部署配置中心;
Nacos 配置中心的快速部署与持久化配置;
Nacos 生产环境中的配置技巧。
为什么微服务架构必须部署配置中心
现在微服务开发的主流技术是基于 Spring Boot 进行的,我们都知道 Spring Boot 默认配置文件为 application.yml 或者 application.properties。它保存了应用的主要配置信息,这些配置文件会随着应用发布被打包放入 Jar 文件,随着应用加载并运行。

::: {nodeid="1502"}
配置文件分散在应用中
:::
当我们的应用只有几个微服务时这些配置文件分散的存放在各个 Jar 中还没有问题。但是如果我们开发了大型互联网应用,涉及几十个研发团队、上百台服务器、上千个服务实例时,互联网的运维团队就要面对因为数量级增加带来的挑战了,总结下运维主要来自三个方面。
第一,纯粹的工作量增加。假设微服务 A 有 400 个实例,这些配置文件 application.yml 分散存储在每一个 Jar 中,此时因为机房环境变化,数据库服务器的 IP 变更,运维人员就不得不在 400 个实例中对每一个数据库连接 URL 进行调整,这个过程费时费力还容易出错。
第二,版本管理的需求。因为生产环境的状况远比开发、测试环境复杂,谁都无法保证新版本服务上线时新应用一定不会出问题。如果出现重大故障,生产环境下必须具备应用版本回滚的机制,保证生产可用的前提下再分析故障原因,而这个场景中如何对配置文件进行版本管理也是必须要考虑到的。
第三,多环境之间的切换。在成熟的软件研发流程中,是拥有多套不同环境的,例如:开发环境、测试环境、UAT 环境、仿真环境、生产环境。不同环境中各种组件的 IP、用户名、密码、配置项都会有差异,在不同环境下运行要求应用具备快速切换并加载对应的配置文件的能力,显然将配置写死在 Jar 中是无法满足这个要求的。
为了解决这些问题,在现有的微服务架构下,必须额外的引入"配置中心"这一组件,配置中心的职责就是集中管理微服务架构中每一个服务实例的配置数据。当微服务架构引入配置中心后,微服务应用只需持有应用启动的最小化配置,在应用启动时微服务应用所需的其他配置数据,诸如数据库连接字符串、各种用户名密码、IP 等信息均从配置中心远程下载,不再本地保存。同时,作为开发应用的程序员,在书写应用配置时也不再直接写入 application.yml 配置,而是直接在配置中心提供的 UI 进行设置。

::: {nodeid="1509"}
Nacos 配置管理界面
:::
当引入配置中心后,微服务的架构会产生如下变化。

::: {nodeid="1512"}
配置中心的作用
:::
研发运维人员在配置中心提前定义各种环境的配置信息,之后在微服务实例启动时根据服务名、环境等从配置中心查询配置数据并下载到服务实例本地,最后服务实例加载这些来自配置中心的配置信息完成应用的启动。
说到这想必你对配置中心的作用已经了解,在 Spring Cloud Alibaba 这个架构下,Nacos 除了能作为注册中心,还提供了配置中心的功能。别看 Nacos 身兼多职,但每一项职责也并不平庸,Nacos 作为配置中心,除了基本的配置存储,还提供了版本管理、变更推送、监听查询以及友好的中文 UI 界面,无论是研发人员还是运维人员都可以快速上手实现应用配置。

::: {nodeid="1516"}
Nacos 动态配置服务
:::
下面咱们来讲解如何对 Nacos 配置中心进行部署与服务接入。
部署 Nacos 配置中心与服务接入
部署 Nacos 配置中心
因为 Nacos 本身就同时具备注册中心与配置中心职责,在部署方面与之前部署 Nacos 集群基本一致。唯一不同点是因为 Nacos 要将应用的配置数据保存在数据库以防丢失,所以要配置 Nacos 的数据库访问地址,这里咱们快速过一遍。
第一步,下载并解压缩 Nacos。
tar -xvf nacos-server-1.4.0.tar.gz第二步,配置数据库,这一步最重要,在 MySQL 加载执行 /nacos/conf/nacos-mysql.sql,完成建表工作。

::: {nodeid="1525"}
nacos_config 数据库初始化脚本
:::

::: {nodeid="1527"}
nacos_config 表结构
:::
其中所有 config_ 开头的表都是 Nacos 配置中心使用时保存应用配置的表。
第三步,配置 Nacos 数据源。
打开 /usr/local/nacos/conf/application.properties,定位到 36 行 Count of DB "数据源"配置附近,按下方示例配置数据源即可。
### Count of DB: 数据库总数
db.num=1
### Connect URL of DB: 数据库连接,根据你的实际情况调整
db.url.0=jdbc:mysql://192.168.31.10:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user=root
db.password=root第四步,复制命令创建 cluster.conf 文件。
cp cluster.conf.example cluster.conf打开 cluster.conf,添加所有 Nacos 集群节点 IP 及端口,为了方便演示,咱们只部署一个节点。
#vim cluster.conf
192.168.31.10:8848第五步,按集群模式启动 Nacos 即可。
sh /usr/local/nacos/bin/startup.sh最后,访问下面网址可以看到配置列表页面,未来所有的应用配置都可以通过配置列表页面进行展示。
http://192.168.31.10:8848/nacos/#/configurationManagement?dataId=&group=&appName=
::: {nodeid="1541"}
配置列表页面
:::
下面咱们让微服务接入 Nacos 配置中心,实现配置的集中管理。
微服务接入 Nacos 配置中心
第一步,创建工程引入依赖。
利用 Spring Initializr 向导创建 order-service 订单服务工程,确保 pom.xml 引入以下 3 个依赖。
<!-- Spring Boot Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos注册中心starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos配置中心starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>第二步,在工程创建配置文件。
在接入 Nacos 配置中心之前,要确保 Spring Boot 配置是完整的。application.yml 文件内容如下,注意最后两行,custom 开头的配置项是自定义的,等一下用于演示环境切换。
server:
port: 8000
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 192.168.31.10:8848 #Nacos通信地址
username: nacos
password: nacos
custom: #自定义配置项
flag: development
database: 192.168.10.31第三步,创建演示代码。
新建 TestController,增加 /test 接口,将配置文件中两个自定义配置输出。
@RestController
public class TestController {
@Value("${custom.flag}")
private String flag;
@Value("${custom.database}")
private String database;
@GetMapping("/test")
public String test(){
return "flag:" + flag + "<br/> database:" + database;
}
}第四步,启动应用查看结果。
打开浏览器,访问下面网址。
http://localhost:8000/test
结果如下:
flag:development
database:192.168.10.31其中 flag:development 说明当前是"开发环境"。
一切准备就绪,下面我们接入 Nacos 配置中心。
第一步,打开 Nacos 配置中心页面,点击右上角"+"号新建配置。

::: {nodeid="1559"}
创建新的配置
:::
在新建配置页面包含六个选项:Data ID、Group、描述、说明、配置格式与配置内容,我们分别了解下这些选项的作用。

::: {nodeid="1562"}
order-service-dev.yml 配置
:::
Data ID:配置的唯一标识,格式固定为:{微服务id}-{环境名}.yml,这里填写 order-service-dev.yml,其中 dev 就是环境名代表这个配置文件是 order-service 的开发环境配置文件。
Group:指定配置文件的分组,这里设置默认分组 DEFAULT_GROUP 即可。
描述:说明 order-service-dev.yml 配置文件的用途。
配置格式:指定"配置内容"的类型,这里选择 YAML 即可。
配置内容:将 order-service 工程的 application.yml 文件内容粘贴过来。
之后点击右下角的"发布"按钮完成设置。

::: {nodeid="1576"}
创建成功后的配置列表
:::
与此同时,在 nacos_config 数据库的 config_info 表中也出现了对应配置数据。

::: {nodeid="1579"}
config_info 表
:::
第二步,回到 order-service 工程,删除 application.yml,因为这个文件内容已经保存在 Nacos 配置中心中了。
第三步,在 resources 目录下创建 bootstrap.yml 引导文件,对 Nacos 配置中心地址进行设置。注意,bootstrap.yml 文件名是固定的,不要随意改变。
spring:
application:
name: order-service #微服务id
profiles:
active: dev #环境名
cloud:
nacos:
config: #Nacos配置中心配置
file-extension: yml #文件扩展名
server-addr: 192.168.31.10:8848
username: nacos
password: nacos
logging: #开启debug日志,仅为学习时使用
level:
root: debug在上面的配置中,包含了两部分内容,第一部分说明 Nacos 配置中心的 IP 端口等信息,第二部分是通过文件中的 "微服务 id"-"环境名"."文件扩展名" 三部分组合为有效的 data id,即order-service-dev.yml。
这个 data id 要和 Nacos 的设置大小写保持完全一致,这样在微服务启动时便自动会从 Nacos配置中心获取 order-service-dev.yml 配置并下载到本地完成启动过程。
下面咱们启动应用看一下日志输出。
...
[main] c.a.c.n.c.NacosPropertySourceBuilder : Loading nacos data, dataId: 'order-service-dev.yml', group: 'DEFAULT_GROUP', data: server:
server:
port: 8000
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 192.168.31.10:8848
username: nacos
password: nacos
custom: #自定义配置项
flag: development
database: 192.168.10.31
[main] o.s.b.factory.config.YamlMapFactoryBean : Loading from YAML: Byte array resource [resource loaded from byte array]
...
[main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8000 (http) with context path ''通过 Debug 日志发现,在启动时微服务向 Nacos 查询 order-service-dev.yml 的内容,并将配置内容打印在 Debug 级别日志中,之后作为应用配置加载完成启动。
访问http://localhost:8000/test运行结果如下:
flag:development
database:192.168.10.31到这里我们完成了 Nacos 配置中心的接入。这里做个小总结,因为我们把配置数据放在 Nacos 配置中心中,微服务在启动时自动进行下载,因此同一个微服务的所有实例得到的配置信息都是一致的,如果需要调整里面的配置,只需在 Nacos 中进行调整,然后让微服务实例重启即可重新下载生效。
说到这你肯定又联想到另一个问题,一旦配置变更就必须手动重启,那运维的效率还是太低,如果微服务能自动监听到配置变化自动加载新配置那岂不是更好。答案是肯定的,Nacos 通过主动推送方式允许程序在运行期间重新下载配置,下面我们就来介绍几个在生产中实用的配置技巧。
Nacos 生产环境中的配置技巧
配置热加载技术
在 Nacos 中支持配置热加载,在运行过程中允许直接对新的配置项进行重新加载而不需要手动重启。首先咱们了解下热加载背后的处理机制。
Nacos 采用的是 Pull 拉取模式,但并不是简单的 Pull,而是一种长轮训机制。客户端采用长轮训的方式定时发起 Pull 请求,去检查服务端配置信息是否发生了变更,如果发生了变更,则客户端会根据变更的数据获得最新的配置。所谓的长轮训,是客户端发起轮训请求之后,服务端如果有配置发生变更,就直接返回。
如果客户端发起 Pull 请求后,发现服务端的配置和客户端的配置是保持一致的,那么服务端会先"Hold"住这个请求,也就是服务端拿到这个连接之后在指定的时间段内一直不返回结果,直到这段时间内配置发生变化,服务端会把原来"Hold"住的请求进行返回,如图所示:

::: {nodeid="1846"}
Nacos 配置中心长轮询机制
:::
Nacos 服务端收到请求之后,先检查配置是否发生了变更,如果没有,则设置一个定时任务,延期 29.5s 执行,并且把当前的客户端长轮询连接加入 allSubs 队列。这时候有两种方式触发该连接结果的返回:
• 第一种是在等待 29.5s 后触发自动检查机制,这时候不管配置有没有发生变化,都会把结果返回客户端。而 29.5s 就是这个长连接保持的时间。
• 第二种是在 29.5s 内任意一个时刻,通过 Nacos Dashboard 或者 API 的方式对配置进行了修改,这会触发一个事件机制,监听到该事件的任务会遍历 allSubs 队列,找到发生变更的配置项对应的 ClientLongPolling 任务,将变更的数据通过该任务中的连接进行返回,就完成了一次"推送"操作。
这样既能够保证客户端实时感知配置的变化,也降低了服务端的压力。其中,这个长连接的会话超时时间默认为 30s。
为了支持热加载,服务 A 的程序针对热加载需要作出如下变动:
第一,配置数据必须被封装到单独的配置 Bean 中;
第二,这个配置 Bean 需要被 @Configuration 与 @RefreshScope 两个注解描述。
下面我们通过实例讲解:
对原有 order-server 作出修改,将 flag 与 database 两个属性移到单独的配置 Bean,并加入 @Configuration 与 @RefreshScope 注解。
package com.lagou.orderservice.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
@Configuration
@RefreshScope
public class CustomConfig {
@Value("${custom.flag}")
private String flag;
@Value("${custom.database}")
private String database;
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
}其中,@Configuration 说明当前 Bean 是一个配置 Bean。是 Spring Boot 自带的 Java Config 注解。而 @RefreshScope 则用于监听,当 Nacos 推送新的配置后,由这个注解负责接收并为属性重新赋值。
此外,原有的 TestController 代码变更为引用 CustomConfig 对象。
@RestController
public class TestController {
@Resource
private CustomConfig customConfig;
@GetMapping("/test")
public String test(){
return "flag:" + customConfig.getFlag() + "<br/> database:" + customConfig.getDatabase();
}
}启动应用后,咱们在 Nacos 手动改变 database 配置项从 31 变为 33。

::: {nodeid="1611"}
配置项热加载
:::
点击发布后,出现前后配置对比,左边是新版本,右边是上一个旧版本,之间的差异使用红绿线已经标出。

::: {nodeid="1614"}
配置变更对比
:::
确认发布后,在 order-service 服务的日志立即产生重新加载的信息,提示"Refresh Nacos config"。
[192.168.31.10_8848] c.a.c.n.refresh.NacosContextRefresher : Refresh Nacos config group=DEFAULT_GROUP,dataId=order-service-dev.yml,configInfo=server:
port: 8000
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 192.168.31.10:8848
username: nacos
password: nacos
custom: #自定义配置项
flag: development
database: 192.168.10.33
[192.168.31.10_8848] c.a.nacos.client.config.impl.CacheData : [fixed-192.168.31.10_8848] [notify-ok] dataId=order-service-dev.yml, group=DEFAULT_GROUP, md5=aeee8dceea709b3081d3c882ae2464b2, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1@5bcff640访问http://localhsot:8000/test
运行结果如下,新的配置已即时生效:
flag:development
database:192.168.10.33到这里,热加载的所有配置已设置完毕。下面咱们介绍如何进行环境切换。
切换环境配置文件
假如产品开发完成准备投产,便可利用 Nacos 提供的环境配置迅速完成从开发到生产环境的切换,来看我演示。
第一步,在 Nacos 中设置生产环境的配置,Data Id 为 order-service-prd.yml,其中 prd 是 production 的缩写,代表生产环境配置。

::: {nodeid="1624"}
生产环境配置
:::
这份配置最大的变化是 Nacos 通信地址与自定义配置均指向生产环境 IP,同时 flag 也变为production 代表生产环境。
server:
port: 80
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 10.181.36.10:8848
username: nacos
password: nacos
custom: #自定义配置项
flag: production
database: 10.181.36.22第二步,调整 order-service 的 bootstrap.yml 引导文件,最重要的地方是修改环境名为 prd,同时更换为生产环境 Nacos 的通信地址,打包后发布。
spring:
application:
name: order-service #微服务id
profiles:
active: prd #环境名
cloud:
nacos:
config: #这里配置的是Nacos配置中心
file-extension: yml #指定文件扩展名
server-addr: 192.168.31.10:8848
username: nacos
password: nacos访问http://localhsot:8000/test
运行结果如下,这里看到 flag:production 说明已切换到生产环境。
flag:development
database:10.181.36.22管理基础配置数据
对比 order-service-dev.yml 与 order-service-prd.yml 发现,在不同环境的配置文件中普遍存在固定的配置项,例如:spring.application.name=order-service 配置项就是稳定的,且修改它会影响所有环境配置文件。对于这种基础的全局配置,我们可以将其存放到单独的 order-service.yml 配置中,在 order-service 服务启动时,这个不带环境名的配置文件必然会被加载。

::: {nodeid="1634"}
order-service.yml 包含基础配置
:::
其中,order-service.yml 包含了应用名称,是其他环境配置都需要包含的内容。
spring:
application:
name: order-serviceorder-service-dev.yml 只包含与开发环境的相关配置。
server:
port: 8000
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.31.10:8848
username: nacos
password: nacos
custom: #自定义配置项
flag: development
database: 192.168.10.33order-service-prd.yml 只包含与生产环境的相关配置。
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 10.181.36.10:8848
username: nacos
password: nacos
custom: #自定义配置项
flag: production
database: 10.181.36.22以上就是在 Nacos 配置中心的设置技巧,你可以根据实际情况在项目中加以利用,下面咱们进行下内容总结。
小结与预告
本讲咱们学习了三方面内容,首先咱们了解到配置中心在微服务架构中的重要性,配置中心将各服务实例的配置数据进行集中管理,让运维人员摆脱烦琐的重复工作;其次介绍了 Nacos 作为配置中心是如何部署以及微服务的接入过程,微服务通过 bootstrap.yml 引导文件加载 Nacos 指定 data id 的配置数据;最后我介绍了三种常用在生产环境中的 Nacos 使用技巧,包括配置热加载技术、切换环境配置文件与管理基础配置数据。
这里为你留一道思考题:假如没有 Nacos 这种成熟的配置中心产品,Redis 能否作为配置中心的替代品呢,请把你的理解写在评论中一起探讨?
下一讲咱们将基于 Nacos 配置中心的基础上讲解如何利用 Sentinel 对服务集群实施保护。
--- ### 精选评论 ##### **奎: > 热加载不用单独的Bean也可以的,直接以@Value的方式加上@RefreshScope注解 ###### 讲师回复: > 是的,利用@Value+@RefreshScope也是可以的 ##### **胜: > redis应该是可以作为nacos的mysql的替代品。替代的是数据存储功能。我认为配置中心要做的还是热加载,配置推送,配置分组管理等功能。这些功能是需要开发的,至于存储使用mysql。redis,mongo或者文件都是可以的吧 ###### 讲师回复: > 是的,REDIS、Mongo都是很好的配置中心产品,但推送功能还是需要自己开发的。而Nacos简单粗暴的为你集成了进来,何乐不为呀 ##### **7601: > 把链接hold住30秒,如果实例很多的话会不会有问题。会hold住很多链接,nacos服务器会不会扛不住。 ###### 讲师回复: > 链路调用情况下可能会产生雪崩,Nacos压力反而不大 ##### **伟: > 项目里配置文件采用bootstrap-环境名.yml形式,然后启动的时候指定相应的【环境名】就能做到采用不同的nacos配置了。做到一处打包不同环境都能运行。 ###### 讲师回复: > 是的。 ##### **1236: > 按照上面的写法,启动就报错Could not resolve placeholder,用@NacosValue注解 获取为参数为null ###### 讲师回复: > 代码我都是测试过的没问问题,请确认下Nacos配置中心的通信是否正常.活着看微服务启动时是否出现Warning/Exception,不妨再反馈一下 ##### Null: > 按照你的配置是无法启动项目的,首先多环境的时候 ,name你没有指定,项目是找不到要加载的配置文件的。 ###### 讲师回复: > bootstrap.yml spring: application: name: order-service #微服务id profiles: active: dev #环境名 这两句话不就是在制定微服务id和环境吗 ##### **华: > 正常情况下,生产环境和测试环境应该不会共用一个nacos吧?应该各个环境部署对应的注册中心才对吧,那这个nacos配置中心的dev、pro切换意义何在?? ###### 讲师回复: > 对于完全隔离的Dev、Prd环境,这样做当然没有意义。这里只是个方便理解的例子。 但在现实中,生产环境也会包含不同环境,例如:正式环境、生产的全链路压测环境,我们同样需要在Nacos提供环境切换。 ##### **鑫: > 在 "管理基础配置数据" 这一小节里面,把应用名称配置到 nacos可以启动成功吗, dataId默认格式需要服务名才可以找到对应的配置文件吧 ###### 讲师回复: > 是的,必须是服务名,这是默认规则.但大多数场景下应用名和注册的服务名是相同的,应该说影响不大 ##### **国: > 为啥我创建的项目配置文件是properties,而不是yml呢 ###### 讲师回复: > 默认就是properties,不过现在使用yml更多,只需要删掉properties重新创建yml即可。 ##### **伟: > 很多配置都已经在项目里的bootstrap.yml 配置了,为什么还要在nacos中配置,这不是多此一举吗? ###### 讲师回复: > bootstrap.yml只提供了应用启动与环境相关的基本配置,更多的应用配置要在Nacos中进行,在Nacos中就是为了在大集群下减少重复工作。 ##### **0034: > 开发,测试,正式环境配置格式相同,只是不同环境连接地址和值不同,应该用环境变量绑定到容器镜像吧,数据库缓存那些配置放配置中心,程序只要启动时连接到对应的nacos即可。第一层是环境变量切换nacos,第二层是nacos配置中心切换命名空间或组,这样不同机房,不同集群都可以兼容了,一次打包到处运行。 ##### *华: > redis有内存淘汰机制,不适合做配置中心存储 ##### *西: > redis的话,感觉还需要自己进一步做很多开发。 ##### **4019: > 不同环境需要改jar里的yml文件,没法做到一个jar或者一个镜像能够部署不同环境吗? ###### 讲师回复: > 可以做到,但那就要求你将所有与环境相关的配置剥离到进程外存储,比如读取新环境的环境变量就是一个好办法。 ##### *纯: > 看完我想问一下,如果修改了nacos 配置文件的内容后,如何保证立即生效? ###### 讲师回复: > 后面会讲到ContextRefresh即时生效了。