本文旨在总结Alibaba常用组件及技术关键点,希望可以帮助到路过的朋友。Alibaba的cloud解决方案在github上是热度最高的一个cloud方案,也是时下最为流行的解决方案,所以他的原理和内幕还是值得我们一探的。这里不做微服务架构演进,微服务基础知识等的普及,如果需要可以进这里进行了解:Springcloud netfilx常用组件
这里通过maven的父子工程来管理本次展示的demo。使用父子观察管理的好处是:
使用IDEA。file–>new–>project–>maven 来进行创建父工程,父工程一定得是pom的,这样我们编译时父工程才可以实现管理子工程的目的,如果不是pom的我们是无法在父工程下正确创建子工程的。
本次demo采用集中的版本管理,我们只需要限定以下三个版本即可,只需要将他们放在dependencyManagement中进行指明版本,同时声明type和scope就可以做到全局的组件版本控制了,功能类似于parent标签,不过parent只能声明一个父工程来管理版本依赖,而放在dependencyManagement的dependency却可以支持多个。
若是对alibaba与cloud和boot的版本对应关系不清晰,可以看这里:SpringCloud组件版本依赖关系
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${Spring-cloud-alibaba-version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependencies>
dependencyManagement>
下面是父工程的完整pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.chenggroupId>
<artifactId>spring-cloud-alibaba-baseartifactId>
<packaging>pompackaging>
<version>1.0.0version>
<modules>
<module>order-servermodule>
<module>stock-servermodule>
modules>
<name>spring-cloud-alibaba-basename>
<description>alibaba父工程description>
<properties>
<java.version>8java.version>
<Spring-boot-version>2.6.11Spring-boot-version>
<Spirng-cloud-version>2021.0.4Spirng-cloud-version>
<Spring-cloud-alibaba-version>2021.0.4.0Spring-cloud-alibaba-version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>${Spring-boot-version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${Spring-cloud-alibaba-version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${Spirng-cloud-version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
子工程创建都是一样的,这里展示一个例子:父工程右键–>new–>module–>spring-initializr,这里不使用spring-initializr,使用maven也是可以的,只是使用spring-initializr会方便一些,无需我们自己创建启动类、配置文件等。若是你的IDEA比较新(据我所知IDEA2021以下好像用不了)还可以使用spring-initializr指定三方的脚手架,三方脚手架地址推荐使用阿里的。https://start.aliyun.com,这个脚手架对Springcloud组件支持的更友好,也包含更多的阿里系的组件
(注意:社区版的IDEA不支持Spring Initializr功能)
下面是子工程的pom文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.chenggroupId>
<artifactId>spring-cloud-alibaba-baseartifactId>
<version>1.0.0version>
parent>
<groupId>com.chenggroupId>
<artifactId>order-serverartifactId>
<version>1.0.0version>
<name>order-servername>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>17java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
到这里就简单搭建了一个架子出来,后面就是为每个服务进行引入不同的组件和依赖了。
若是感觉以上父子工程创建不够详细,推荐看这里:Maven创建父子工程详解
Nacos是使用SpringCloud Alibaba的必用组件了,他统一了Eureka和Config,同时支持优雅的服务下线,对服务进行管理可以很从容的实现服务替换。优点还有很多不一一列举了,反正是肯定比eureka和config好用。上面我们已经指定了Alibaba的版本,这个版本里限定的Nacos版本是:2.0.4 所以这里的服务端、客户端我们都是用2.0.4的Nacos来进行搭建。下图是主流的注册中心:
这里服务端的搭建使用docker来进行安装,这样会比较快和省事。这里采用的思路是:先下载一个基础的nacos镜像,不配置数据卷。下载完毕后将默认的配置文件cp出来进行更改,然后删除原容器。从新启动一个新的镜像。这里需要特别注意的是9848/9849两个端口的暴露,这俩是必须的端口,测试时感觉不到问题,但是部署线上肯定会有问题,加上就完事了。如果不是使用的Docker,那服务器也必须把这俩端口放开。这俩端口是在8848的基础上进行+1000,和+1001来的,如果你用的不是8848那就根据+1000、+1001来重新计算即可。
第一步:下载基础镜像
docker run \
--name my-nacos -d \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:8849 \
nacos/nacos-server:v2.0.4
新建文件夹,然后将镜像内的配置文件cp到我们的文件夹
mkdir -p /apps/nacos/logs/
mkdir -p /apps/nacos/conf/
cd /apps/nacos/conf/
docker cp my-nacos:/home/nacos/conf/application.properties ./
第二步:修改本地的配置文件
这个本地配置文件是指刚刚我们从容器内部cp出来的配置文件,更改下面几项即可,这里的数据库放在了第四步
注意:spring.datasource.platform 这个配置项在后面的版本进行了变更,变为了:spring.sql.init.platform(好像是2.2.3开始变得)
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.*.*:3306/nacos_config?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
db.user.0=root
db.password.0=root
第三步: 重启nacos
注意这里是以单机模式来运行的,重启完成后可以去看看日志是否启动正常了,如果正常直接访问即可:ip:8848/nacos
注意这里是无法直接访问的,登录会提示用户名和密码错误(nacos/nacos),f12会看到具体错误,请看后面的“登录报错”来进行解决。
docker run \
--name my-nacos -d \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:8849 \
--env PREFER_HOST_MODE=ip --env MODE=standalone \
-v /apps/nacos/logs:/home/nacos/logs \
-v /apps/nacos/conf/application.properties:/home/nacos/conf/application.properties \
nacos/nacos-server:v2.0.4
第四步:数据库创建
下面是数据库、表,不过还是推荐去官网下载包自己找,防止sql有变化:
官网下载地址:https://github.com/alibaba/nacos/releases?page=3
数据库脚本相对地址:./nacos-server-1.4.1/nacos/conf/nacos-mysql.sql
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info */
/******************************************/
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', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
第五步:集群配置
nacos的集群模式很简单,只需要两步操作即可:
// 声明集群模式
--env MODE=cluster
// 这个解决ip展示问题,和集群无关
--network=host
// 声明其他节点信息,无需写自己的,多节点逗号隔开
--env NACOS_SERVERS=10.3.8.135:8848,10.3.8.136:8848
更改后的完整命令如下:// 节点1启动命令
docker run \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:8849 \
--name my-nacos -d \
--network=host \
--env PREFER_HOST_MODE=ip --env MODE=cluster \
--env NACOS_SERVERS=192.168.150.185:8848 \
-v /apps/nacos/logs:/home/nacos/logs \
-v /apps/nacos/conf/application.properties:/home/nacos/conf/application.properties \
nacos/nacos-server:v2.0.4
// 节点2命令
docker run \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:8849 \
--name my-nacos -d \
--network=host \
--env PREFER_HOST_MODE=ip --env MODE=cluster \
--env NACOS_SERVERS=192.168.150.148:8848 \
-v /apps/nacos/logs:/home/nacos/logs \
-v /apps/nacos/conf/application.properties:/home/nacos/conf/application.properties \
nacos/nacos-server:v2.0.4
搭建完成后,进入nacos可以在集群管理中看到我们的集群:特别注意:登录报错:
注意nacos现在安装完毕以后是肯定登录不了的,登录时f12 会提示这个错误:caused: The specified key byte array is 0 bits which is not secure enough for any JWT HMAC-SHA algorithm.The JWT JWA Specification(RFC 7518, Section 3.2)states that keys used with HMAC-SHA algorithms MUST have a size>=256 bits(the key size must be greater
这需要我们为配置文件增加一个默认配置,现在nacos官方删除了这个默认配置(有安全隐患,万一把地址暴露到公网别人用这个秘钥访问你就会有隐患),所以需要我们自己添加到application.properties中,不添加是登录不了的,也无法使用。
# 秘钥可自行更改,我试了2.0.4和1.4.6版本都是无法直接登录的,demo测试日期:2023.09 ,后面配置参数可能会变化,如果不行建议官网查下参数
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
nacos官方文档:nacos官方文档
这是官方文档位置(我下载的是1.4.6和2.0.4也是没有这个参数的,这个描述有点坑爹):
第一步:添加依赖
增加依赖项,因为父工程已经声明了依赖版本,所以这里直接添加依赖即可
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
第二步:修改配置文件
当前版本已无需在启动类增加注解:@EnableDiscoveryClient了,当然加了也不会报错。我们只需要配置好配置文件就ok了。
server:
port: 8001
# nacos中的服务名默认读取的是spring.application.name
# 如果需要自定义,则需要通过spring.cloud.nacos.discovery.service-name来指定
spring:
application:
name: order-server
cloud:
nacos:
# 这里配置集群时,启动时默认只会连接其中一个,但是若是有节点挂掉,可以自动切换到另外的结点
server-addr: 192.168.150.185:8848,192.168.150.148:8848
user-name: nacos # 这是默认值
password: nacos # 这是默认值
namespace: public # 这是默认值
特别注意:
spring:
application:
name: order-server
cloud:
nacos:
# 这里配置集群时,启动时默认只会连接其中一个,但是若是有节点挂掉,可以自动切换到另外的结点
server-addr: 192.168.150.185:8848,192.168.150.148:8848
discovery:
ephemeral: false # 是否是临时实例,若是false,则即使服务端超过30s没有收到心跳,也不会剔除,而是永久存在
下面是和nacos相关的一些常用配置了。大部分其实使用默认值即可无需更改
# nacos中的服务名默认读取的是spring.application.name
# 如果需要自定义,则需要通过spring.cloud.nacos.discovery.service来指定
spring:
application:
name: order-server
cloud:
nacos:
# 这里配置集群时,启动时默认只会连接其中一个,但是若是有节点挂掉,可以自动切换到另外的结点
server-addr: 192.168.150.185:8848,192.168.150.148:8848
user-name: nacos # 这是默认值
password: nacos # 这是默认值
namespace: public # 这是默认值
discovery:
ephemeral: true # 是否是临时实例,若是false,则即使服务端超过30s没有收到心跳,也不会剔除,而是永久存在
group: DEFAULT_GROUP # 默认值
service: ${spring.application.name} # 默认值
weight: 1 # 权重,默认值1
namespace: public # 命名空间,默认值public
access-key: "" # 访问秘钥,默认值空,只有部署在阿里云时才需要,上云就会用到
secret-key: "" # 访问秘钥,默认值空,只有部署在阿里云时才需要
watch:
enabled: true # 是否开启心跳监听,默认值true
heart-beat-interval: 5000 # 心跳间隔,默认值5000
在nacos2.0.3之前,他的客户端默认是集成了Ribbon的,且Ribbon的配置也是打开的,但是2.0.3之后就给关了。原因也是因为Ribbon已经闭源,作为Ribbon的开发者网非已经不对他进行更新。
# 这是nacos2.0.3之后的默认配置,在这之前nacos是默认集成ribbon且使用ribbon的
spring:
cloud:
loadbalancer:
ribbon:
enabled: false # 关闭ribbon
但是作为负载均衡组件,其实就是那几种负载策略,所以新的东西还是那几种(轮询、随机、权重、hash、最少连接),并且Ribbon的负载做的也挺好,为什么不接着更新呢,再写一个类似的感觉不是更消耗吗,而且现在Springcloud官方推荐的Load Balancer到现在为止也就只支持两种负载,还是最简单的轮询和随机。但是没得选啊,我们只能用Load Balancer了。
<!-- 引入loadbalancer负载均衡,父工程引入了cloud的包管理,这里无需声明具体包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author pcc
*/
@Configuration
public class SysConfig {
/**
* 创建RestTemplate
* @param builder 构造器,这个restTemplate主要是测试使用
* 加入feign后,将不在使用restTemplate进行测试
* @return
*/
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
这样其实基础使用就ok了,就可以实现通过nacos中的服务名来调用服务提供者了,同时还有负载均衡策略在里面。为什么加入LoadBalancer或者Ribbon就可以直接使用RestTemplate了呢?因为无论是LoadBalancer还是Ribbon都是属于客户端负载均衡的策略,而这类负载均衡都是通过将注册中心的实例拉取到本地以后才能进行计算负载的策略,所以我们增加了负载均衡时,他就会自动拉取我们的注册中心的实例,从而就认识了我们写的RestTemplate中的服务名了,如下:
@RequestMapping("/addOrder")
public void addOrder(){
log.info("add order start ");
// 只有增加了负载均衡,才能使用RestTemplate识别到注册中心的服务名
restTemplate.getForObject("http://stock-server/stock/addStock",String.class);
}
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* @author pcc
* 注意类上别加configuration注解
*/
public class RandomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory){
// 获取注册中心所有实例
String property = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
// 对实例进行负载
return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(property, ServiceInstanceListSupplier.class),property);
}
}
然后启动类上增加LoadBalancer的注解即可了import com.cheng.orderserver.config.RandomLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
@LoadBalancerClients(defaultConfiguration= RandomLoadBalancerConfig.class)
@SpringBootApplication
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
}
以上就是更换负载的全部操作了,更改完成重启就会生效了。就使用上确实比Ribbon麻烦一些。不过无论是LoadBalancer还是Ribbon都是基于http的负载,这个通讯效率其实是不高的,在这个方面Dubbo是好很多的,这个不做过多研究(没必要,如果想研究可以去深研负载均衡算法)。注册中心、负载均衡、远程调用,这是实现微服务的最基础的三个组件,使用了他们三个才能算是搭建了一个微型的服务,其他组件都是为了让服务更好的运行来设计的,只有他们三个是缺一不可,最基础的三个也是最没有看头的三个,nacos需要很少的配置就可以使用,至于LoadBalancer可以直接引入包就会默认使用。也就OpenFeign场景会多些。先说下基础使用
<!--引入OpenFign,父工程引入了cloud的包管理,这里无需声明具体包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author pcc
* name:用以声明feign调用的服务,这个必须与注册中心服务名保持一致
* path:控制器上的类路径,如果不写path在方法上写全路径也是一样的
*
*/
@FeignClient(name = "stock-server",path = "/stock")
public interface StockFeignController {
@RequestMapping("/addStock")
void addStock();
}
import com.cheng.orderserver.config.RandomLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
// 不加自定义负载,这个配置就可以不要
@LoadBalancerClients(defaultConfiguration= RandomLoadBalancerConfig.class)
@SpringBootApplication
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
}
import com.cheng.orderserver.feign.StockFeignController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author pcc
*/
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
StockFeignController stockFeignController;
@RequestMapping("/addOrder")
public void addOrder(){
log.info("add order start ");
// 添加完feign以后就不需要restTemplate了
stockFeignController.addStock();
}
}
这样就实现了OpenFeign的简单使用,当然了这支持最基础的功能使用,下面来看一下略深一点的功能,也都是生产会用到的一些场景。在开发或者调试过程中,有时候排查过程还是需要对Feign的调用过程进行打印的,因此掌握他的日志配置还是很有必要的。这里可以分为全局和局部两种配置。不过无论是使用全局还是使用局部都有一个前提,那就是更改Spring的默认日志级别,我们知道Spring的默认日志级别是info级别的。但是OpenFeign的日志打印的都是debug,所以我们必须调整Spring的展示日志级别我们才可以看到想看的日志,不过调整的话我们也是可以局部调整的,而不用全局调整。如下所示:
# 日志级别配置
# 使用feign日志时,必须要设置下面的配置,注意下面配置是Spring的,默认全局info,我们这里可以
# 指定对应文件夹下的日志为debug,这样就不会全局debug了,这个配置指向哪个包,哪个包就是debug了
# 也可以指向一个类
logging:
level:
# com.cheng.orderserver.feign: debug
com.cheng: debug
加上这个配置后,指定包下面的日志界别就会变成debug了,注意是当前包下的所有日志都是以debug级别进行输出。下面看下OpenFeign如何设置全局和局部的日志配置。
1.全局日志配置
全局日志配置我们只需要在配置了Spring的日志(上面说的)的基础上增加一个配置类即可,如下:
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author pcc
*/
@Configuration
public class FeignConfig {
@Bean
public Logger.Level getLoggerLevel() {
return Logger.Level.FULL;
}
}
2.局部日志配置-配置类
若是感觉全局日志太多,我们可以只针对某一个feign调用类进行配置,直接使用上一步的配置文件去掉他的@Configuration注解(有这个注解就会全局生效),然后将其交给feign类的@FeignClient注解即可。如下:
import feign.Logger;
import org.springframework.context.annotation.Bean;
/**
* @author pcc
*/
public class FeignConfig {
@Bean
public Logger.Level getLoggerLevel() {
return Logger.Level.FULL;
}
}
下面是feign类配置
import com.cheng.orderserver.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author pcc
* name:用以声明feign调用的服务,这个必须与注册中心服务名保持一致
* path:控制器上的类路径,如果不写path在方法上写全路径也是一样的
* configuration:配置类,可以配置一些公共的参数,只对当前类下的接口生效,属于局部配置
*
*/
@FeignClient(name = "stock-server",path = "/stock",configuration = FeignConfig.class)
public interface StockFeignController {
@RequestMapping("/addStock")
void addStock();
}
2.局部日志配置-配置文件
使用配置文件的话看着更清晰明了一些。上面我们设置的都是 Logger.Level.FULL,这其实只是Feign日志的一种级别,Feign其实有四个级别
这里的局部配置的配置文件配置我们就用BASIC进行展示:
feign:
client:
config:
stock-server: # 这里是被调用的服务名(注册中心里的名称)
loggerLevel: BASIC # 这里使用BASIC,表示只打印请求方法名和URL,不打印请求头和响应头
如上所示,就是OpenFeign的日志配置的全部内容了,一般调试信息或者排查问题是有用的。如果想要展示Feign的日志。也要严格限定只开启Feign目录下的debug日志,不然Spring提供的debug日志可是很多的,很容易把磁盘打满。此外还需要注意:若是既配置了全局又配置了局部那以谁为准呢?注意是以局部为准(很多框架都是这种局部配置大于全局)
这里的配置也是分为全局和局部两种,其实和上面是类似的,全局的配置我们需要新增一个配置类(使用上面的全局配置类就行)就行了,如实局部配置有两种选择一种是和上面一样放到feign类的FeignClient注解里,一种是放到配置文件里,都差不多。超时时间配置支持两项一个是connetct,一个是read的。
import feign.Logger;
import feign.Request;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author pcc
*/
@Configuration
public class FeignConfig {
/**
* 设置feign的日志级别
*
* FULL: 所有请求和响应
* HEADERS: 请求和响应的头信息
* BASIC: 请求方法、url、响应状态码和执行时间
* NONE: 默认,啥也没有
* @return 日志级别
*/
@Bean
public Logger.Level getLoggerLevel() {
return Logger.Level.FULL;
}
/**
*
* 设置feign的请求和响应的超时时间
* @return 请求配置
*/
@Bean
public Request.Options options(){
return new Request.Options(5000, 5000);
}
}
此外服务提供者的接口增加一个线程睡眠6秒测试下,测试结果如下:import feign.Logger;
import feign.Request;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author pcc
*/
//@Configuration
public class FeignConfig {
/**
* 设置feign的日志级别
*
* FULL: 所有请求和响应
* HEADERS: 请求和响应的头信息
* BASIC: 请求方法、url、响应状态码和执行时间
* NONE: 默认,啥也没有
* @return 日志级别
*/
@Bean
public Logger.Level getLoggerLevel() {
return Logger.Level.FULL;
}
/**
*
* 设置feign的请求和响应的超时时间
* @return 请求配置
*/
@Bean
public Request.Options options(){
return new Request.Options(5000, 5000);
}
}
然后我们在feign类上指明局部配置类,注意局部配置优先级大于全局,若同时配置的话,局部配置生效。import com.cheng.orderserver.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author pcc
* name:用以声明feign调用的服务,这个必须与注册中心服务名保持一致
* path:控制器上的类路径,如果不写path在方法上写全路径也是一样的
* configuration:配置类,可以配置一些公共的参数,只对当前类下的接口生效,属于局部配置
*
*/
@FeignClient(name = "stock-server",path = "/stock",configuration = FeignConfig.class)
//@FeignClient(name = "stock-server",path = "/stock") // 取消feign的局部配置,使用配置文件
public interface StockFeignController {
@RequestMapping("/addStock")
void addStock();
}
feign:
client:
config:
stock-server: # 这里是被调用的服务名(注册中心里的名称)
# 注意日志配置,必须配合Spring的日志配置才可以生效
loggerLevel: BASIC # 这里使用BASIC,表示只打印请求方法名和URL,不打印请求头和响应头
connectTimeout: 5000 # 连接超时时间,默认10s
readTimeout: 5000 # 读取超时时间,默认60s
以上就是超时的配置了。拦截器在java生态里都差不多,都是对请求或者响应进行处理的,这里是对OpenFeign的请求进行处理,我们可以增加一些日志信息,可以改变请求参数,可以增加请求头信息等等。在实际应用中可能会存在使用该拦截器往请求头里进行信息设置,从而追踪日志的调用链路的场景。因为分布式服务的调用日志时跨服务的,怎么确定日志是哪一条就可以通过这种方式来实现,当然市面上有比较好的解决方案,有时候不需要我们自己做,但原理都是类似的。这里同样是支持全局和局部配置的,而且与上面的日志、超时配置其实都差不多。
1 全局配置
先要自己实现一个Interceptor,自己的Interceptor需要实现OpenFeign的RequestInterceptor,重写他的apply方法,从这里可以看出OpenFeign底层还是RestTemplate。都是http请求。
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import java.util.UUID;
/**
* @author pcc
*/
@Slf4j
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
log.info("进入到了OpenFeign自定义拦截器了");
requestTemplate.header("trace_id", UUID.randomUUID().toString());
}
}
声明好拦截器以后,我们把他注入到Spring中就行,这样机会全局生效了。
import com.cheng.orderserver.interceptor.FeignInterceptor;
import feign.Logger;
import feign.Request;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author pcc
*/
@Configuration
public class FeignConfig {
/**
* 设置feign的日志级别
*
* FULL: 所有请求和响应
* HEADERS: 请求和响应的头信息
* BASIC: 请求方法、url、响应状态码和执行时间
* NONE: 默认,啥也没有
* @return 日志级别
*/
@Bean
public Logger.Level getLoggerLevel() {
return Logger.Level.FULL;
}
/**
*
* 设置feign的请求和响应的超时时间
* @return 请求配置
*/
@Bean
public Request.Options options(){
return new Request.Options(5000, 5000);
}
/**
* 配置feign的拦截器
* 将其交给Spring即可
*/
@Bean
public FeignInterceptor feignInterceptor() {
return new FeignInterceptor();
}
}
2 局部配置-配置类
这里和上面两种说的局部配置没有区别,就不重复演示了:将配置类取消@Configuration注解,然后使用FeignClient指明配置类即可。
2 局部配置-配置文件
自己写拦截器这一步是无法省略的,然后我们可以将拦截器交给指定的服务,这样就只会拦截指定的服务了。
feign:
client:
config:
stock-server: # 这里是被调用的服务名(注册中心里的名称)
# 注意日志配置,必须配合Spring的日志配置才可以生效
loggerLevel: BASIC # 这里使用BASIC,表示只打印请求方法名和URL,不打印请求头和响应头
connectTimeout: 5000 # 连接超时时间,默认10s
readTimeout: 5000 # 读取超时时间,默认60s
requestInterceptors: # 请求拦截器,可以添加多个
com.cheng.orderserver.interceptor.FeignInterceptor # 这里指定的是拦截器的全限定名
像下面这样配置也是可以的:
feign:
client:
config:
stock-server: # 这里是被调用的服务名(注册中心里的名称)
# 注意日志配置,必须配合Spring的日志配置才可以生效
loggerLevel: BASIC # 这里使用BASIC,表示只打印请求方法名和URL,不打印请求头和响应头
connectTimeout: 5000 # 连接超时时间,默认10s
readTimeout: 5000 # 读取超时时间,默认60s
requestInterceptors[0]: # 请求拦截器,可以添加多个
com.cheng.orderserver.interceptor.FeignInterceptor # 这里指定的是拦截器的全限定名
以上都是openfeign的扩展功能了,在性能等上面其实没啥影响。都是写辅助功能。
Nacos的服务端都是共用的,这里就不重复进行展示了,只说下配置中心客户端的具体情况
客户端搭建差不多,都是三步走:导入依赖,配置文件,使用。基本都差不多,当然这里需要我们配置远端服务了。
1.导入依赖
这里依赖包注意是两个,一个是nacos-config的,一个是bootstrap的,nacos-config的配置必须配置在bootstrap中,否则不生效。所以我们需要导入两个包。注意要确认包导入完毕了,不然启动肯定会报错找不到要解析的注意的配置信息。比如使用@Value进行注入配置,会告诉你这个配置信息解析不了。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bootstrapartifactId>
dependency>
2.增加配置文件bootstrap.yml
添加如下配置,这个配置其实在配置nacos配置中心时其实已经写过了,与application.yml一样,没有任何区别,但是放到application.yml的话配置是不生效的。下面是直接从applicaion.yml复制过来的配置,无需任何改动就可以使用
spring:
application:
name: order-server # nacos配置中心和注册中心默认都是用这个作为默认名称
cloud:
nacos:
# 这里配置集群时,启动时默认只会连接其中一个,但是若是有节点挂掉,可以自动切换到另外的结点
# 这里只配置一个,不然虚拟机ip一变(没有配置静态ip),集群多个结点就相互注册不到对方了,所以用一个结点
# server-addr: 192.168.150.187:8848,192.168.150.188:8848
server-addr: 192.168.150.187:8848
user-name: nacos # 这是默认值
password: nacos # 这是默认值
namespace: public # 这是默认值
3.配置中心增加配置文件
这里需要注意:
${prefix}-${spring.profiles.active}.${file-extension}
所以这里我们在nacos配置中心的文件名应该叫:order-server。文件类型选择properties
编辑完发布即可。
4.使用nacos配置信息
这里就是使用了,使用的话还是很简单的,和application.yml使用没有任何区别。这里使用@Value进行注入验证
import com.cheng.orderserver.feign.StockFeignController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author pcc
*/
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
StockFeignController stockFeignController;
@Value("${name}")
String name;
@RequestMapping("/addOrder")
public void addOrder(){
log.info("add order start :{}",name);
stockFeignController.addStock();
}
}
下面是接口输出截图:
到这里客户端的基础搭建就ok了,我们已经可以读取到配置中心的文件了。然后就可以将本地的配置信息同步到nacos配置中心了。
其实都是写基础功能,需要说的也不是很多
1.Data Id
data id就是配置文件的名称,客户端默认是spring.application.name 也可以手动指定。不指定默认就是spring.application.name,一般都是使用默认,没人去更改这个。
2.namespace 和 group
这个和注册中心的概念是一样的,都是为了做区分的,怎么使用都是看自己,一般场景使用namespace作为环境隔离,使用group作为应用隔离,dataid 作为服务隔离。不过实际场景中很少有人将生产和测试使用同一套nacos。所以命名空间一般都是不使用,或者就是建造一个自己用的命名空间。group就要看情况了,若是一个中台有很多应用。是可以使用group进行区分应用的,再用dataid来区分同应用下的不同服务。
3.编辑、克隆、导出、导入
这些都是基本配置,可以更方便的进行配置信息的管理,没啥好说的
5.监听查询
这里需要注意,并不是说有服务在使用这个配置文件,监听查询中就可以看到对应服务的,测试服务启动和配置变更时都会服务均未正常出现在监听列表内,可能是bug。
nacos有两个默认配置文件,也就是说我们不添加任何配置的话,nacos也会去服务端寻找这两个配置文件进行加载(在指定的命名空间和分组下)。注意这里指的都是服务端的文件名,和文件是yml配置文件还是properties配置文件没有任何关系。那是哪两个呢?
若是我们服务名是order-server,则nacos客户端默认会去服务端加载这两个配置文件:order-server、order-server.properties,注意这里是找的是文件名,这里的properties是指文件名的后缀,与文件的配置格式是不是properties没有关系,也就是说你可以在nacos服务端起个order-server.properties的名字(dataid)然后里面是yml配置文件,这也是ok的。虽然可以但是别这么干,很容易让接收项目的兄弟吐槽你不专业,一般保持一致方便区分。特别需要注意的是若是在order-server、order-server.properties都添加了相同的配置,则使用的是order-server.properties的配置。order-server.properties的优先级更高。
下面是不添加任何配置时服务启动的日志:
可以看到nacos为我们默认加载这两个文件,如果加载不到也不会报错的,只是会在之前的日志里提示空文件。那这个默认后缀properties是如何来的呢,请看下面10.1
服务端默认使用properties作为服务扩展名,若是想要变更则客户端必须进行指定,指定的话使用该配置进行指定,下面是不增加这个配置时的客户端启动的日志
可以发现,nacos客户端会扫描两个服务端的配置文件,一个是order-server,另一个是order-server.properties。下面新增file-extension的配置,注意位置,此时切记不可以在config下重新声明namespace,这会导致无法正常寻找到配置文件。namespace和user-name、password都应该声明在nacos下,而不是config下。此外还需要注意,file-extension修改的是dataid的名字与文件内部是yml和properties都没有关系,内部文件的类型我们可以随便指定,但是一般为了可维护性更高会保持一致。
# 已省略与此处关系不大的配置
spring:
cloud:
nacos:
config:
# namespace: public # 这里不可以重复这么些,写了就无法正常加载配置文件
file-extension: yaml # 默认是properties,若是yaml,需要配置这个
这里我虽然增加了file-extension的配置,但是我在服务端并没有增加这个文件,所以提示加载不到。当我们提供了这个文件后就不会有这个提示了 这个日志文件告诉我们nacos客户端会从哪些服务端配置文件加载配置:
注意这个配置是针对默认配置文件${spring.application.name}.${file-extension}和${spring.application.name}-${spring.profiles.active}.${file-extension}有用,若是使用指定dataid方式进行加载时则无需遵循这个规则(这个后面10.5细说)。
特別注意:查阅资料说config下支持namesapce的配置,但是亲测这里配置命名空间以后服务是无法读取到对应的配置信息的,所以若是需要更改namespace还是在nacos配置属性中更改,不要在config中更改,这里nacos客户端:2.0.4
这是何种场景下使用的呢?若是对所有配置文件不区分环境都放在了一起,就像写SpringBoot项目时不使用配置中心一样,此时我们需要声明
多个环境的不同文件,然后使用
spring:
profiles:
active: dev # 这个配置声明在application和bootstrap都是可以的,bootstrap优先级高
这个配置来指明当前使用的到底是哪个文件,但是使用nacos完全没有必要这么写的。一般不同环境的配置文件在nacos服务端是不会放一起的,都是使用不同的nacos集群。所以这个场景一般不这么用,不过nacos是支持这种配置的,以防止有些人就是想要放一起。前面说过nacos加载的配置文件默认是:
${prefix}-${spring.profiles.active}.${file-extension}
当我们增加了如上的配置以后:nacos默认会读取以下三个配置文件,假设file-extension是yaml:
重启服务,验证下是不是读的这些配置文件:
可以看到正常去加载这些配置文件了,配置文件的使用都是一样的,就不具体说了,唯一需要特殊说明的是他们的配置优先级。在Springcloud中,bootstrap>application-profile>application,这里基本是一样的。他们三个是
order-server-dev.yaml > order-server.yaml > order-server
建议:不要这么使用,因为nacos一般根据集群区分环境,而不是都放到一个nacos服务端,这样不是好的配置文件管理方式。那我们该怎么管理不同的环境下不同的地址呢?我们一般是通过本地的profile配置文件来管理,而不是远端放到一起。假如我们存在三个环境dev、uat、prd。则我们需要本地提供这么三个文件:
这里需要注意三点:
# 使用-- 来覆盖配置信息,使用-- 必须参数放在jar后面
java -jar ./order-server.jar --spring.profiles.active=dev
# 使用-D 来覆盖配置信息,这种写法优先级更高,且位置没有要求
java -jar -Dspring.profiles.active=dev ./order-server.jar
nacos客户端默认每10ms去服务端查看下配置有无变更,通过比对MD5的值来进行校验。变更的话就会进行重新拉取。这个操作默认是开启的,使用nacos的原因一部分也是因为他的动态感知能力,所以一般不会有人去禁用这个的,除非是一些服务配置不希望随便被更改的,这些都是些特殊场景了。
# 已省略关系不大配置
spring:
cloud:
nacos:
config:
# namespace: public # 这里不可以重复这么些,写了就无法正常加载配置文件
file-extension: yaml # 默认是properties,若是yaml,需要配置这个
refresh-enabled: true # 默认是true,nacos每10ms去服务端比对信息有误变化
这个场景想要验证需要配合@RefreshScope注解来实现,这个注解下面会详细说下,他的作用就是将使用了配置中心的属性或者对象放到一个context中,一旦配置中心改变就会热加载这个配置。当上面的配置未配置时,我们使用这个@RefreshScope是可以是实现热加载的,但是加了refresh-enabled:false以后就不会有热加载了。原因就是因为nacos客户端不会再每10ms去服务端比对信息变化了。
查阅资料说config下支持namesapce的配置,但是亲测这里配置命名空间以后服务是无法读取到对应的配置信息的,所以若是需要更改namespace还是在nacos配置属性中更改,不要在config中更改,这里nacos客户端:2.0.4
spring:
cloud:
nacos:
namespace: dev # 这是默认值
config:
# namespace: dev # 这里不可以重复这么些,写了就无法正常加载配置文件
若是在config中进行更改namespace则无法正常加载配置信息。不过在config中声明group却是可以正常加载的。nacos服务端允许同namespace下存在不同group的同名文件。
这种是可以正常加载到配置信息的。
项目中肯定会碰到有一些公共配置信息是需要我们进行共享的,对于这部分信息若是在每个配置文件都配置一遍则是很low的一个动作,而且一旦发生变更我们需要每个配置文件都去改一下,一旦漏了一个就是事故了。所以主流的配置中心都有这个功能就是共享配置文件,用以配置一些所有服务或者多个服务共享的信息。nacos里可以通过shared-configs来进行配置共享信息。注意:
假如有一个公共的配置文件叫:common.yaml ,应该像如下进行配置:
# 已省略关系不大的配置
spring:
cloud:
nacos:
config:
shared-configs:
- data-id: common.yaml # 这里配置多个,可以配置多个配置文件,但是要保证文件名不能重复
refresh: true # 默认是fase,也就是不会动态去刷新配置文件
group: DEFAULT_GROUP # 默认值
shared-configs是一个数组,Springboot中数组的配置支持两种,一种是- 来进行配置元素,一种是使用下标来配置来配置,使用下标应该这么写:
spring:
cloud:
nacos:
config:
shared-configs[0]:
data-id: common.yaml # 这里配置多个,可以配置多个配置文件,但是要保证文件名不能重复
refresh: true # 默认是fase,也就是不会动态去刷新配置文件
group: DEFAULT_GROUP # 默认值
使用验证与之前都没有区别,不重复举例了。
extension-configs和shared-configs基本一样,包括上面说的shared-configs的内容都适用于extension-configs。他们区别主要是加载优先级不一样。加载优先级 extension-configs > shared-configs 。而 extension-configs 与其他文件相比同样是最低的。
使用和shared-configs没有任何区别,这里简单举例:
# 已省略关系不大的配置
spring:
cloud:
nacos:
config:
shared-configs[0]:
data-id: common.yaml # 这里配置多个,可以配置多个配置文件,但是要保证文件名不能重复
refresh: true # 默认是fase,也就是不会动态去刷新配置文件
group: DEFAULT_GROUP # 默认值
extension-configs:
- data-id: common2.yaml # 同样也可以配置多个,但是要保证文件名不能重复
refresh: true # 默认是fase,也就是不会动态去刷新配置文件
group: DEFAULT_GROUP # 默认值
配置文件配置的信息,他的变化是可以被服务实时感知到的,前面也说了nacos客户端每10ms会去服务端做一次配置文件MD5的比对,若是发现变更了,则会从新拉取配置信息到本地。但是我们虽然拉到本地了,但是Spring加载信息的动作只会执行一次,所以我们一般使用@Value获取的信息并无法直接随着我们的变更而变更。此时我们就需要@RefreshScope注解了
@Slf4j
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
}
如此,当nacos服务端配置变动时信息就会实时更新到@Value上了。RefreshScope是config提供的注解,而不是nacos的,nacos是按照这一标准进行实现了。
Nacos作为当下最流行的注册中心和配置中心的解决方案,是必须掌握的一项技能,本文旨在总结基础的使用,从Nacos注册中心开始,继承RestTemplate实现负载,然后使用OpenFeign代替RestTemplate等。后面对配置中心的使用进行了进一步总结,比较需要关注的是多文件配置以及共享文件配置的shared-configs、extension-configs以及支持动态刷新配置到已加载配置的变量或者对象中的配置@RefreshScope。