Nacos 起源于阿里巴巴 2008 年的五彩石项目(完成微服务拆分和业务中台建设),经历了阿里十年双十?的洪峰流量的考验,沉淀了简单易用、稳定可靠、性能卓越等核心特性。随着云计算的兴起和受到开源软件行业的影响,2018年阿里决定将Nacos(阿里内部Configserver/Diamond/Vipserver 内核) 开源,输出阿里十年的沉淀,推动微服务行业发展,加速企业数字化转型!
Nacos/nɑ:k??s/
是Dynamic Naming and Configuration Service 的首字母简称;?个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
官网:https://nacos.io/ 仓库:https://github.com/alibaba/nacos
1. 服务发现和服务运行状况检查: Nacos使服务可以通过DNS或HTTP接口简单地注册自己并发现其他服务。Nacos还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求。
2. 动态配置管理: 动态配置服务允许您以集中和动态的方式跨所有环境管理所有服务的配置。Nacos消除了在更新配置时重新部署应用程序和服务的需要,这使得配置更改更加高效和敏捷。
3. 动态DNS服务: Nacos支持加权路由,使您更容易在数据中心的生产环境中实现中间层负载平衡、灵活的路由策略、流量控制和简单的DNS解析服务。它可以帮助您轻松实现基于dns的服务发现,并防止应用程序耦合到特定于供应商的服务发现api。
4. 服务和元数据管理: Nacos提供了一个易于使用的服务仪表板,帮助您管理服务元数据、配置、kubernetes DNS、服务运行状况和度量统计数据。
Nacos下载地址:https://github.com/alibaba/nacos/releases ;目前最新版本为2.3.0的beta公测版本,我们可以下载合适的版本使用,建议使用2.x版本。Windows
版本下载zip
包,Linux
版本下载tar.gz
包。
在linux的nacos安装目录执行tar -zxvf nacos-server-2.1.1.tar.gz解压安装包,解压后的目录为。
目录结构介绍:
bin:启动脚本目录,
startup.sh
用于启动服务,shutdown.sh
用于停止服务
conf:配置文件目录,application.properties
核心配置文件,nacos-mysql.sql
用mysql做数据源所需的sql文件。
注意:nacos服务本身就是一个Java应用,所以需要JDK环境才能运行,官方建议最好是JDK8以及更高的版本。
nacos服务启动需要在bin目录下执行./startup.sh -m standalone命令和参数,不加参数默认会以集群模式启动。通过查看nacos目录下的logs/start.out文件可以了解项目启动情况和端口号。
在浏览器输入主机ip:8848/nacos访问Nacos控制台,默认用户名和密码都是nacos/nacos
。
Nacos提供了一系列的Open API接口来帮助我们快速的操作Nacos,所以我们可以通过调用Open API的http接口来简单地进行的服务注册。
请求方式:POST
注册实例的接口:http://127.0.0.1:8848/nacos/v2/ns/instance
必填参数: serviceName, ip, port, ephemeral, metadata
当ephemeral=true
时调用接口注册的是临时实例,临时实例需要自己上报心跳给Nacos Server,由于是通过接口注册的,客户端无法进行心跳上报,所以等待15秒实例会变为不健康,等待30秒后实例会被删除。
我们可以使用Nacos提供的Java SDK原生API,来实现手动注册服务到Nacos的功能,这种方式比较直接,也便于我们了解Nacos注册的底层原理。
引入Nacos的Java SDK
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.1.1</version>
</dependency>
Nacos需保证有权限才可以注册,应该在application.properties
配置文件中开启认证功能。
nacos.core.auth.enabled = true
Java代码实现简单的服务注册功能
public static void main(String[] args) throws NacosException, InterruptedException {
Properties properties = new Properties();
properties.setProperty("server-addr","127.0.0.1:8848");
properties.setProperty("serverAddr","127.0.0.1:8848");
properties.setProperty("username","nacos");
properties.setProperty("password","nacos");
NamingService namingService = NamingFactory.createNamingService(properties);
namingService.registerInstance("app","192.168.8.130",9000);
TimeUnit.SECONDS.sleep(60);
}
程序运行后查看Nacos控制台的服务管理/服务列表
,可以看到服务被成功注册到了Nacos上,默认是注册到public
命名空间的default_group
默认分组下。
当程序运行结束后,服务列表上的服务实例信息也随之删除,因为Nacos默认注册的实例都是临时实例。注册时也可以按照需求设置服务是临时实例还是持久化实例,持久化实例会保留不健康的服务实例信息。
注册持久化实例
public static void main(String[] args) throws NacosException, InterruptedException {
Properties properties = new Properties();
properties.setProperty("server-addr","127.0.0.1:8848");
properties.setProperty("serverAddr","127.0.0.1:8848");
properties.setProperty("username","nacos");
properties.setProperty("password","nacos");
NamingService namingService = NamingFactory.createNamingService(properties);
Instance instance = new Instance();
instance.setIp("192.168.8.129");
instance.setPort(9001);
instance.setEphemeral(false); // 设置实例为持久化实例
namingService.registerInstance("app1",instance);
TimeUnit.SECONDS.sleep(60);
}
运行程序查看持久化实例注册成功,实例是健康的;当60s
过后程序结束,健康实例数变为0
,而实例数还是1
,此时服务实例信息已经被持久化了。实例是否健康是通过Nacos的健康检查机制检测出来的,临时实例会采用客户端服务定时心跳上报的方式检查,而持久化实例采用的是注册中心主动探测机制。
Nacos集群是同时支持AP
和CP
模式。当注册的实例是临时实例时,使用的是AP
模式,在AP
模式下,Nacos会使用Distro
协议来维护集群结点的数据同步问题,会优先保证集群的高可用。当注册的实例是持久化实例时,会使用CP
模式的Raft
协议来保证集群结点数据的强一致性,对数据的一致性比较看重。
Nacos2.x版本使用了http
和grpc
两种方式来进行服务的通信和注册。当注册的服务是临时实例时,会使用grpc
框架进行通信注册,通过建立长连接来和服务端维持心跳;如果注册的服务是持久化实例时,则会调用Open API的http
接口对服务进行注册。Springboot项目在启动时会自动地注册到Nacos上,底层是通过Spring的事件监听机制完成的。
在父工程引入项目依赖
<dependencyManagement>
<dependencies>
<!-- springboot 2.3.12.RELEASE -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.12.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springcloud Hoxton.SR12 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springcloud alibaba 2.2.8.RELEASE -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.8.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在父工程下新建子模块 nacos-client-provider,引入核心依赖。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.1.1</version>
</dependency>
在application.yml
文件配置服务注册信息。
server:
port: 9998
spring:
application:
name: nacos-client-provider # 注册的服务名
cloud:
nacos: # 注册中心和配置中心是同一个时的写法
username: nacos
password: nacos
server-addr: localhost:8848
启动 nacos-client-provider 服务,发现服务被自动注册到Nacos上了。
SpringBoot实现自动注册的原理是使用了Spring中的事件监听机制。SpringBoot的微服务项目在启动时会发布一个ServletWebServerInitializedEvent
事件,该事件会被AbstractAutoServiceRegistration
类的onApplicationEvent(WebServerInitializedEvent event)
方法监听到,监听方法在执行过程中会调用Nacos的NacosAutoServiceRegistration
类重写过的register()
方法,最后调用NacosServiceRegistry
真正执行注册的register(Registration registration)
方法完成注册。
http
接口,在该版本中的SDK方式注册底层也是直接调用Open API的http
接口进行注册。http
接口注册,还新增加了grpc
的方式进行注册,在新版本中使用SDK方式注册时,对于持久实例的注册还是使用的http
接口方式,而对于临时实例的注册则会采用RPC
与注册中心保持长连接,客户端会定时的通过RPC
连接向Nacos 注册中心发送心跳,保持连接的存活。如果客户端和注册中心的连接断开,那么注册中心会主动剔除该 client 所注册的服务,以达到下线的效果。在单体架构的时候我们可以将配置写在配置文件中,但有?个缺点就是每次修改配置都需要重启服务才能生效。
当应用程序实例比较少的时候还可以维护。如果转向微服务架构有成百上千个实例,每修改?次配置要将全部实例重启,不仅增加了系统的不稳定性,也提高了维护的成本。
对于以上的问题,Nacos为我们提供了一个功能强大的配置中心。Nacos的配置中心具有:便于快速动态修改配置的界面、服务配置的热更新(无需重启服务)、服务配置的历史版本追踪(30天内)、服务配置的多级权限隔离等特性。
进入权限控制/用户列表
控制台界面,添加mj_dev/123
,mj_tes/123
,mj_prod/123
这三个用户账号和密码。
进入权限控制/角色管理
控制台界面,为这三个用户绑定三个角色ROLE_DEV
,ROLE_TEST
,ROLE_PROD
。
进入 命名空间
控制台界面,创建三个命名空间用于环境隔离 dev
,test
, prod
。
最后在权限控制/权限管理
控制台界面,为这三个角色分别绑定命名空间 dev
,test
, prod
以及读写 (rw)
权限,这样就做到了账号和命名空间的访问权限控制。
新建nacos-client-goods工程,引入核心依赖。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
新建bootstrap.yml
配置文件,引入配置中心后需要将配置中心信息写在此文件中,否则配置中心无法连接。
spring:
application:
name: goods-service # 服务名称
profiles:
active: dev # 激活的配置文件
cloud:
nacos:
server-addr: localhost:8848 # 注册中心地址
username: mj_dev # 开发环境账号
password: 123 # 开发环境密码
discovery:
namespace: b8aac48d-d92a-4234-ab42-754bcf37fd5e # 命名空间为开发环境
group: DEV_GROUP # 服务所在的分组
cluster-name: HZ # 服务所在的集群
config:
namespace: b8aac48d-d92a-4234-ab42-754bcf37fd5e # 命名空间,用于环境隔离
group: DEV_GROUP # 配置文件的分组,用于配置隔离
file-extension: yaml # 配置文件后缀格式
在 配置管理/配置列表
控制台界面,新建 goods-service.yaml
和goods-service-dev.yaml
配置文件。Nacos对Springboot的配置文件也只支持 yaml
和 properties
这两种格式。
配置文件的命名规则有:
{spring.application.name}.yaml
{spring.application.name}.properties
{spring.application.name}-{spring.profiles.active}.yaml
{spring.application.name}-{spring.profiles.active}.properties
。测试使用@Value()
注解读取配置的值,使用@RefreshScope
注解开启配置变更的自动刷新机制,输出的日期格式为:yyyy/MM/dd HH:mm:ss
,证明 {spring.application.name}-{spring.profiles.active}.yaml
配置的优先级高于{spring.application.name}.yaml
。
@RestController
@RefreshScope
public class ConfigCenterController {
@Value("${global.date.format}")
private String dateFormat;
@GetMapping("/now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateFormat));
}
}
在日常开发中我们可能会有很多公用的配置,比如多个微服务使用的是同一个数据库或者Redis的情况,这时就可以用到共享配置(shared-configs)
。共享配置通过对可重用配置的抽取,在多个应用中引入来实现配置的共享。
在Nacos上新建common-env.properties
的共享配置文件,用来设置在各个应用中都通用的共享变量。
在bootstrap.yml
文件中配置共享配置的dataId
,group
和refresh
属性值,多个共享配置时通过shared-configs[n]
配置优先级,n 越大优先级越高。
spring:
cloud:
nacos:
config:
server-addr: localhost:8848 # 配置中心地址
username: mj_dev # 开发环境账号
password: 123 # 开发环境密码
namespace: b8aac48d-d92a-4234-ab42-754bcf37fd5e # 命名空间,用于环境隔离
group: DEV_GROUP # 配置文件的分组,用于配置隔离
file-extension: yaml # 配置文件后缀格式
shared-configs[0]: # shared-configs[n],n 越大优先级越高
data-id: common-env.properties # 设置dataId,即文件名
group: DEFAULT_GROUP # 设置文件所在的分组,不设置也是默认分组
refresh: true # 配置变更会自动刷新给服务
使用@ConfigurationProperties()
注解自动装配共享配置的属性值,这些属性在所有应用中都是通用的,所以标识为共享配置,其实shared-configs
也只是一种标识,并不是只有这样配置后才能共享。
@Setter
@Getter
@Configuration
@ConfigurationProperties(prefix = "money.unit")
public class MoneyUnitConfig {
private String china;
private String america;
private String britain;
}
另外一个扩展配置(extension-configs)
的用法是,对共享配置进行修改和扩展,因为扩展配置的优先级高于共享配置,所以可以按照应用的需求对公共配置的属性进行修改和扩充。
在Nacos上新建extension-env.properties
的配置文件,用来修改和扩展共享配置的属性值。
在bootstrap.yml
文件中设置扩展配置的dataId
,group
和refresh
属性值。
spring:
cloud:
nacos:
config:
server-addr: localhost:8848 # 配置中心地址
username: mj_dev # 开发环境账号
password: 123 # 开发环境密码
namespace: b8aac48d-d92a-4234-ab42-754bcf37fd5e # 命名空间,用于环境隔离
group: DEV_GROUP # 配置文件的分组,用于配置隔离
file-extension: yaml # 配置文件后缀格式
shared-configs: # 共享配置
- data-id: common-env.properties # 设置dataId,即文件名
group: DEFAULT_GROUP # 设置文件所在的分组,不设置也是默认分组
refresh: true # 配置变更会自动刷新给服务
extension-configs: # 扩展配置
- data-id: extension-env.properties
refresh: true
测试扩展配置(extension-configs)
的优先级是否真的大于共享配置(shared-configs)
。修改MoneyUnitConfig
类,输出对象属性自动装配的值,通过输出结果证明:扩展配置优先级大于共享配置,而且可以对共享配置做修改和补充。
@Setter
@Getter
@ToString
@Configuration
@ConfigurationProperties(prefix = "money.unit")
public class MoneyUnitConfig implements InitializingBean {
private String china;
private String chinaStandard;
private String america;
private String britain;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("输出结果:china = "+china+" ,chinaStandard = "
+chinaStandard+" ,america = "+america+" ,britain = "+britain);
}
}
输出结果:china = 元 ,chinaStandard = 人民币 ,america = 美元 ,britain = 英镑
Nacos的注册中心和配置中心是可以分开部署的,所以我们也可以把Nacos的配置中心和注册中心的配置分开,万一以后有了更优秀的注册中心产品,我们也可以灵活切换。
对bootstrap.yml
配置文件进行修改,增加多环境支持。
spring:
application:
name: goods-service
profiles:
active: dev # 激活不同环境的配置
---
spring:
profiles: dev # 开发环境的nacos配置中心信息
cloud:
nacos:
config:
server-addr: localhost:8848 # 注册中心地址
username: mj_dev # 开发环境账号
password: 123 # 开发环境密码
namespace: b8aac48d-d92a-4234-ab42-754bcf37fd5e # 命名空间,用于环境隔离
group: DEV_GROUP # 配置文件的分组,用于配置隔离
file-extension: yaml # 配置文件后缀格式
---
spring:
profiles: test # 测试环境的nacos配置中心信息
cloud:
nacos:
config:
server-addr: localhost:8848 # 注册中心地址
username: mj_test # 测试环境账号
password: 123 # 测试环境密码
namespace: 11c2dc6c-f1c9-4b44-927f-d1cb6f7e3202 # 命名空间,用于环境隔离
group: TEST_GROUP # 配置文件的分组,用于配置隔离
file-extension: yaml # 配置文件后缀格式
---
spring:
profiles: prod # 生产环境的nacos配置中心信息
cloud:
nacos:
config:
server-addr: localhost:8848 # 注册中心地址
username: mj_prod # 生产环境账号
password: 123 # 生产环境密码
namespace: 90ec3dd0-ee97-4a5b-a048-8054fad3b62b # 命名空间,用于环境隔离
group: PROD_GROUP # 配置文件的分组,用于配置隔离
file-extension: yaml # 配置文件后缀格式
开发环境的配置,修改Nacos上的goods-service-dev.yaml
配置文件,添加注册中心的自动配置属性。为了防止网络故障读取不到配置,在应用本地的application-dev.yml
配置文件也需要配置注册中心的自动配置属性。
测试环境的配置,修改Nacos上的配置文件goods-service-test.yaml
,添加注册中心的自动配置属性。同样在应用本地的application-test.yml
配置文件也需要配置注册中心的自动配置属性,但是由于Nacos上的配置优先级更高,所以会以goods-service-test.yaml
为主。
生产环境的配置,相同的操作,在goods-service-test.yaml
配置文件中添加Nacos注册中心的自动配置属性,在应用本地的application-test.yml
配置文件也添加同样的配置。
到这里项目已经完成了多环境配置的搭建,就只需要通过简单的配置spring.profiles.active
属性的值就可以切换到不同的环境上。当切换在开发环境启动时,服务会被注册到开发环境,也会只读取开发环境的配置,这样就做到了不同开发人员之间的环境和配置的隔离。
健康检查是注册中心的基本功能之一,这是因为注册中心不应该仅仅提供服务注册和发现功能,还应该保证对服务的可用性进行监测,对不健康或不可用的服务应该进行标识或剔除,维护实例的生命周期,以保证客户端尽可能的查询到可用的服务列表。
注册中心的产品有很多,比如Nacos、Eureka、Zookeeper等,他们都实现了自己的健康检查机制。健康检查机制主要有两种:第?种方式是客户端定时主动上报心跳,告诉服务端自己的健康状态,如果在规定的时间没有上报,那么服务端就判定服务实例已经不健康;第二种则是服务端主动向客户端进行探测,检查客户端是否还健康。
目前主流的注册中心,对于健康检查机制主要都采用了TTL(Time To Live)
机制,即客户端在?定时间没有向注册中心发送心跳,那么注册中心会认为此服务不健康,进而触发后续的剔除逻辑。Nacos和Eureka使用的就是客户端定时主动上报心跳这个方式,不过Nacos还支持服务端主动向客户端进行探测的方式。对于主动探测的方式,根据不同的场景,需要采用的方式可能会有不同。
我们知道Nacos 的服务实例可以分为临时实例和持久化实例,这两种实例采用的健康检查机制是不同的。
临时实例健康检查机制:
临时实例采用的是客户端定时主动上报心跳的方式,默认客户端服务会以5秒为周期向Nacos服务端发送心跳包,如果心跳包的间隔时间超过了15秒,那么服务端会将此服务实例标记为不健康实例,如果心跳包的间隔时间超过了30秒,那么服务实例将会被删除。
可以修改默认的客户端心跳配置时间
# 心跳上报的周期时间(ms)
spring.cloud.nacos.discovery.metadata.preserved.heart.beat.interval = 5000
# 心跳超时被标记为不健康实例的时间
spring.cloud.nacos.discovery.metadata.preserved.heart.beat.timeout= 10000
# 实例心跳超时被删除的时间
spring.cloud.nacos.discovery.metadata.ip.delete.timeout= 15000
配置生效后可以在服务实例的元数据中看到,也可以直接编辑修改心跳配置。
持久化实例健康检查机制:
对于持久化实例的健康检查,Nacos 采用的是注册中心探测机制,注册中心会在持久化服务初始化时根据客户端选择的协议类型注册探活的定时任务。Nacos现在内置提供了三种探测的协议,即Http、TCP 以及MySQL。?般而言Http 和TCP 已经可以涵盖绝大多数的健康检查场景,而MySQL 主要用于特殊的业务场景。
持久化实例为什么会采用Nacos服务端主动探测机制呢?
首先我们要知道,持久化实例是Nacos1.0.0增加的新特性,他对应的是一些通过Open API注册的持久化服务,Nacos 中使用SDK 对于持久化实例的注册实际也是使用Open API 的方式进行注册。这些服务在第一次注册成功就会被持久化存储下来,他们可能并不知道注册中心存在,因为他们可以被任何人调用接口注册,比如我调用接口注册了一个Redis服务,这时Redis服务是无法给Nacos发送心跳的,因为Redis服务压根不知道注册中心的存在,也没有实现给Nacos主动上报心跳的功能,所以持久化实例需要Nacos主动去探测服务的健康状态。