在微服务架构中,配置管理是一个至关重要的问题。随着系统规模的扩大,配置的管理和更新变得更加繁琐。Nacos 作为一个全能的服务发现和配置管理平台,为解决这一问题提供了全方位的支持。在本文中,我们将深入理解 Nacos 的配置管理,包括配置的统一管理、热更新、多环境配置共享以及Nacos集群的搭建。
Nacos 提供了直观的 Web 界面,使得我们可以轻松地添加和管理配置文件。通过 Web 界面,我们可以为不同的微服务添加对应的配置信息,包括数据库连接、端口号等。
下面将演示如何在 Nacos 的控制台上添加新的配置文件:
首先在 Nacos 的控制台中点击配置管理,然后选择右侧的加号添加新的配置:
然后设置以下配置信息,最后点击发布:
在这个配置信息中,指定了 Data ID 为 userservice-dev.yml,并在配置内容中设置了一个日期的格式。
关于 Data ID 命名格式的说明:
关于配置内容的说明:
配置内容通常包含了应用程序的参数、设置、以及其他与应用程序运行有关的信息。这些配置信息可能包括但不限于:
数据库连接信息: 包括数据库的URL、用户名、密码等。
服务端口号: 应用程序运行的端口,用于处理外部请求。
第三方服务的地址和密钥: 与其他服务进行交互时所需的信息。
日志级别和格式: 控制应用程序日志输出的详细程度和格式。
缓存策略: 配置缓存的大小、过期时间等相关参数。
安全配置: 包括认证和授权相关的配置信息。
任务调度配置: 配置定时任务的执行策略和参数。
其他运行时参数: 如线程池大小、连接超时等。
配置内容的特点在于它们是经常需要变化的,而不同环境下的配置可能也有所不同。因此,合理管理和更新配置信息对于应用程序的灵活性和可维护性至关重要。通过使用Nacos等配置中心,可以实现配置信息的集中管理、动态更新,从而更好地适应应用程序的不断变化。
在微服务应用中,获取配置信息是一个关键的任务。配置信息包括数据库连接、服务端口、第三方服务的地址、日志设置等等,这些信息对于应用程序的正确运行至关重要。在没有 Nacos 配置时和有 Nacos 配置时,微服务获取配置信息的过程有所不同。
在没有Nacos配置中心的情况下,微服务获取配置信息的步骤如下图所示:
说明:
在有 Nacos 配置中心的情况下,微服务获取配置信息的步骤如下图所示:
说明:
在有 Nacos 配置中心的情况下,Nacos 充当了集中式的配置管理中心,使得微服务可以动态获取配置信息并实现配置的集中管理,从而提高了灵活性和可维护性。
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config
spring: application: name: userservice # 服务名称 profiles: active: dev #开发环境,这里是 dev cloud: nacos: server-addr: localhost:8848 # Nacos 地址 config: file-extension: yaml # 文件后缀名
当新增了 bootstrap.yml 配置文件之后,就可以将application.yml 重复的配置项删除掉。
当所有的配置都修改完成之后,如何证明配置成功了呢?此时就可以使用代码来读取,看看能否读取到 Nacos 中添加的配置信息。读取配置信息可以使用 @Value注解读取。
例如,修改 UserController 中的代码如下:
@Value("${pattern.dateformat}") private String dateformat; @GetMapping("now") public String now() { return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat)); }
然后重启服务器,使用浏览器访问 now 这个接口:
成功输出了时间,说明 Nacos 中新增的配置也成功生效了。
此时,如果我们想要修改 Nacos 中的配置内容,比如修改日期的格式如下:
当修改完成之后,再次通过浏览器访问,发现并没有生效:
如果重启 user-service服务,再次通过浏览器访问才生效了:
但是如果想要修改了配置文件之后立马就生效,并且在实际生产环境中,也不能因为修改一点配置信息就重启服务器吧,那么此时又该然如何操作呢?
要达到这个目的,就需要使用到配置文件的热更新,即 Nacos 中的配置文件变更后,微服务无需重启就可以感知。在代码中,有两种实现配置文件热更新的方法:
方式一: 在 @Value 注入的变量所在类上添加注解 @RefreshScope 注解
添加完注解之后,重启微服务,当前访问输出的时间格式如下:
然后,再次修改 Nacos 的配置文件中的时间格式:
此时并没有重启微服务,直接通过浏览器访问,其输出的时间格式如下,和刚修改的格式相匹配,则说明热更新配置成功了:
并且此时,user-service的控制台也会输出相关配置文件相关的信息:
方式二:使用 @ConfigurationProperties注解
使用@ConfigurationProperties注解时,会把从配置文件中读取到的内容封装到一个对象中,因此首先需要创建一个类:
@Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }
然后修改 UserController 中的代码,并注释掉方式一的相关代码:
@Autowired private PatternProperties properties; @GetMapping("now") public String now() { return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformat())); }
重启微服务,此时访问浏览器输出的时间格式如下:
然后再次修改Nacos配置文件中的日期格式:
此时并没有重启微服务,直接通过浏览器访问,其输出的时间格式如下,和刚修改的格式相匹配,则说明热更新配置成功了:
在微服务应用中,通常需要在不同的环境(开发、测试、生产等)下使用不同的配置信息。但如果在多个环境中的配置内容是相同的,为了避免重复配置,可以实现多环境配置文件的共享。Nacos提供了一种简单而有效的方式来实现这一目标。
在微服务启动时,从 Nacos 中读取多个配置文件时,采用以下命名规范:
不论 spring.profiles.active 如何变化,[spring.application.name].yaml 这个文件都会被加载。因此,多环境共享的配置可以写入这个文件中。
示例:
假设 spring.application.name 为 userservice,则对应的配置文件命名如下:
server: port: 8080 database: url: jdbc:mysql://localhost:3306/userservice username: admin password: admin123
spring: profiles: active: dev database: url: jdbc:mysql://localhost:3306/userservice_dev
spring: profiles: active: test database: url: jdbc:mysql://testdb:3306/userservice_test
这样的命名规范使得在不同环境下维护配置变得更加清晰和方便,同时确保了在多环境中可以共享相同的配置信息。
在 Nacos 的环境列表中新增一个配置文件userservice.yaml:
当添加完共享配置文件之后,再次修改 PatternProperties 类和UserController来读取共享配置信息:
PatternProperties 类:
@Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; private String envSharedValue; }
UserController类:
@GetMapping("prop") public PatternProperties properties(){ return properties; }
然后重启这个user-service服务,注意这个服务当前的环境是dev环境,对应的端口号是 8081。
另外,再开启一个user-service服务,端口号为 8082,使其处于test 环境,但并不用在创建一个配置文件,直接通过IDEA进行设置即可:
由于并没有创建test环境的配置文件,因此可以推测 8082 端口对应的服务除了共享环境变量外的其他字段都应该为null。
分别访问这两个服务的prop接口,结果如下:
至此,便成功实现了配置文件的共享。
如果当不同的配置文件拥有不同的属性时,配置文件的优先级有是怎么样的呢?其实这个问题可以很容易的找出答案。那就是中不同的配置文件中设置相同的属性,看看最终的结果以哪个为准就能够得出优先级的关系了。
并修改 PatternProperties代码:
@Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; private String envSharedValue; private String name; }
此时毫无疑问读取到的就是本地配置,因为 Nacos 中并没有配置这个属性。
刷新浏览器,发现是以共享配置文件为准,因此得出结论是Nacos的共享配置文件的优先级大于本地配置文件。
刷新浏览器,发现是以Nacos的开发配置文件为准,因此得出结论是 Nacos 开发环境配置文件的优先级大于共享配置文件。
综上所述,不同的配置文件的优先级关系如下:
更详细地,在 Nacos 配置管理中,不同来源的配置文件有不同的优先级。优先级从高到低排列如下:
环境配置(Profile): 环境配置具有最高优先级。当在Nacos中使用 [spring.application.name]-[spring.profiles.active].yaml 格式命名的配置文件时,其中的 spring.profiles.active 将指定当前环境,优先级最高。
服务名.yaml: 使用 [spring.application.name].yaml 格式命名的配置文件,其中的 spring.application.name 即服务名。这是服务级别的通用配置,具有较高优先级。
extension-config: 通过Nacos配置中心的 Data ID 中的 extension-config 配置项获取的配置。这个配置项通常用于扩展配置,例如定制化的业务配置。
extension-configs: 通过Nacos配置中心的 Data ID 中的 extension-configs 配置项获取的配置。这个配置项也用于扩展配置,但具有比单独的 extension-config 更低的优先级。
shared-configs: 通过Nacos配置中心的 Data ID 中的 shared-configs 配置项获取的配置。这个配置项通常用于共享配置,例如团队内部约定的通用配置。
本地配置: 本地配置是在应用程序中直接指定的配置项,具有最低的优先级。这包括在应用程序的配置文件(如 application.yml 或 application.properties)中定义的配置项。
优先级示例:
假设有一个微服务名为 userservice,当前环境为 dev,那么优先级顺序为:
这个优先级规则确保了在不同层次和来源的配置中,能够灵活地管理和覆盖配置项,从而实现更精细化的配置控制。
搭建 Nacos 集群是确保系统可用性和容错性的关键步骤。通过集群部署,可以分摊负载、提高系统的并发处理能力,并在某一节点发生故障时保持系统的正常运行。
以下是 Nacos 集群搭建的基本步骤:
下面是详解的搭建过程。
官方给出的 Nacos 集群图:
其中包含3个 nacos 节点,然后一个负载均衡器代理 3 个 Nacos。这里负载均衡器可以使用 nginx。
计划搭建的集群结构:
三个 nacos 节点的地址:
节点 | IP | PORT |
---|---|---|
nacos1 | 192.168.150.1 | 8845 |
nacos2 | 192.168.150.1 | 8846 |
nacos3 | 192.168.150.1 | 8847 |
Nacos 默认数据存储在内嵌数据库 Derby 中,不属于生产可用的数据库。
官方推荐的最佳实践是使用带有主从的高可用数据库集群,但是这里以单点的数据库为例。
首先新建一个数据库,命名为 nacos,而后导入下面的 SQL:
CREATE TABLE `config_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(255) DEFAULT NULL, `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `c_desc` varchar(256) DEFAULT NULL, `c_use` varchar(64) DEFAULT NULL, `effect` varchar(64) DEFAULT NULL, `type` varchar(64) DEFAULT NULL, `c_schema` text, PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_aggr */ /******************************************/ CREATE TABLE `config_info_aggr` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(255) NOT NULL COMMENT 'group_id', `datum_id` varchar(255) NOT NULL COMMENT 'datum_id', `content` longtext NOT NULL COMMENT '内容', `gmt_modified` datetime NOT NULL COMMENT '修改时间', `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_beta */ /******************************************/ CREATE TABLE `config_info_beta` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL COMMENT 'content', `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_tag */ /******************************************/ CREATE TABLE `config_info_tag` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `tag_id` varchar(128) NOT NULL COMMENT 'tag_id', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_tags_relation */ /******************************************/ CREATE TABLE `config_tags_relation` ( `id` bigint(20) NOT NULL COMMENT 'id', `tag_name` varchar(128) NOT NULL COMMENT 'tag_name', `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `nid` bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`nid`), UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = group_capacity */ /******************************************/ CREATE TABLE `group_capacity` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群', `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = his_config_info */ /******************************************/ CREATE TABLE `his_config_info` ( `id` bigint(64) unsigned NOT NULL, `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `data_id` varchar(255) NOT NULL, `group_id` varchar(128) NOT NULL, `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL, `md5` varchar(32) DEFAULT NULL, `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `src_user` text, `src_ip` varchar(50) DEFAULT NULL, `op_type` char(10) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', PRIMARY KEY (`nid`), KEY `idx_gmt_create` (`gmt_create`), KEY `idx_gmt_modified` (`gmt_modified`), KEY `idx_did` (`data_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = tenant_capacity */ /******************************************/ CREATE TABLE `tenant_capacity` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID', `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表'; CREATE TABLE `tenant_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `kp` varchar(128) NOT NULL COMMENT 'kp', `tenant_id` varchar(128) default '' COMMENT 'tenant_id', `tenant_name` varchar(128) default '' COMMENT 'tenant_name', `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc', `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source', `gmt_create` bigint(20) NOT NULL COMMENT '创建时间', `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info'; CREATE TABLE `users` ( `username` varchar(50) NOT NULL PRIMARY KEY, `password` varchar(500) NOT NULL, `enabled` boolean NOT NULL ); CREATE TABLE `roles` ( `username` varchar(50) NOT NULL, `role` varchar(50) NOT NULL, UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE ); CREATE TABLE `permissions` ( `role` varchar(50) NOT NULL, `resource` varchar(255) NOT NULL, `action` varchar(8) NOT NULL, UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE ); INSERT INTO users (username, password, enabled) VALUES ('nacos', 'a$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
进入 nacos 的 conf 目录,修改配置文件 cluster.conf.example,重命名为cluster.conf:
然后添加地址:
127.0.0.1:8845 127.0.0.1.8846 127.0.0.1.8847
然后修改 application.properties 文件,添加数据库配置
spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=root db.password.0=123
将nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3
然后分别修改三个文件夹中的application.properties,
nacos1:
server.port=8845
nacos2:
server.port=8846
nacos3:
server.port=8847
然后分别启动三个nacos节点:
startup.cmd
将 nginx 安装到非中文目录下:
修改conf/nginx.conf文件,配置如下:
upstream nacos-cluster { server 127.0.0.1:8845; server 127.0.0.1:8846; server 127.0.0.1:8847; } server { listen 80; server_name localhost; location /nacos { proxy_pass http://nacos-cluster; } }
而后在浏览器访问:http://localhost/nacos即可。
代码中application.yml文件配置如下:
spring: cloud: nacos: server-addr: localhost:80 # Nacos地址
实际部署时,需要给做反向代理的nginx服务器设置一个域名,这样后续如果有服务器迁移nacos的客户端也无需更改配置.
Nacos的各个节点应该部署到多个不同服务器,做好容灾和隔离