Nacos 简介

Nacos 可以帮助我们发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助我们快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 的关键特性包括:

  • 服务发现和服务健康监测

    Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDKOpenAPI 或一个独立的 Agent 注册 Service 后,服务消费者可以使用 DNS 或 HTTP&API 查找和发现服务。

    Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP) 和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测 2 种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。

    基于 DNS 的服务发现和查找在 V2.0.0 中使用 CoreDNS,但是暂未成熟、稳定;

    独立的 Agent 是以 Sidecar 边车模式通过 spring-cloud-starter-alibaba-sidecar 来提供的。

  • 动态配置服务

    动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。

    动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。

    配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

    Nacos 提供了一个简洁易用的UI 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。

  • 服务及其元数据管理

    Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

Nacos 术语

Nacos 引入了一些基本的概念,系统性的了解一下这些概念可以帮助您更好的理解和正确的使用 Nacos 产品。

命名空间 - Namespace

用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

bootstrap 中的 namespace 属性为命名空间的 ID,而非名称,默认 public 的 namespace 属性为空。

配置集 - Data

一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。

配置集 ID - Data ID

Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。

配置分组 - Group

Nacos 中的一组配置集,是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和 MQ_topic 配置。

它们的关系如下图所示:

nacos_data_model

Nacos 架构

nacos_arch

Provider 和 Consumer 通过客户端 Client 或者边车 Sidecar 使用 HTTP/RPC/DNS 等方式与 Server 进行交互,Server 包含可选的配置服务(Config Service)和命名服务(Naming Service),Nacos 本身支持多数据中心,天然分布式,使用一致性协议 Raft 来保证一致性,同时提供了 Console 和 UI 进行监控和管理交互。

客户端类试图

nacos_sdk_class_relation

Factory 工厂用来创建相应的配置服务或命名服务。

配置服务类使用 ClientWorker 与服务端进行沟通,在传输层使用 RPC 代理进行通讯,Java 客户端使用的是 gRPC 实现。

命名服务类同样适用 RPC 与服务端进行沟通,命名服务不需要时常监听变更,所以少了 Worker 作业类的监听、同步和缓存等。

Spring-Cloud 集成

Nacos 提供了开箱即用的 spring boot autoconfig starter 帮助我们快速集成动态配置或服务发现及健康检查功能。

动态配置

Spring Cloud Nacos Config 是通过 Spring Cloud Context 的引导上下文 (Bootstrap Context) 来集成动态配置功能。

引导上下文从外部配置源加载配置属性并解密本地依赖的外部配置文件中的属性,它是整个应用程序的父上下文,在启动和 Environment 后处理期间可用,直到准备好应用上下文 (Application Context)。

这两个上下文共享环境 Environment ,它是任何 Spring 应用程序的外部属性的来源。默认情况下,引导加载到的外部属性有更高的优先级,它们不能被本地配置覆盖。

可以参考官方 DEMO 及文档 快速了解集成及使用

应用上下文层级

如果我们是从 SpringApplicationSpringApplicationBuilder 来启动构建应用程序上下文,则 Bootstrap 上下文会作为父级添加到该上下文中。Spring 的一个特性是子上下文从其父上下文继承属性源和配置文件,因此与在没有 Spring Cloud Config 的情况下构建相同的上下文相比,“main”应用程序上下文包含额外的属性源。这些额外的属性包含:

  • “引导 - bootstrap”:如果在 Bootstrap 上下文中找到任何 PropertySourceLocator 并且它们具有非空属性,则以高优先级显示可选的 CompositePropertySourceCompositePropertySource 以链表形式组合多个属性源,头部的元素会优先被使用。

    Nacos 正是实现了 PropertySourceLocator 并从 Server 加载并构建组合的多个属性源 CompositePropertySource,Nacos 会按如下顺序及规则加载属性源:

    • 加载共享配置:通过 spring.cloud.nacos.config.shared-configs[0]=xxx 配置项加载多个共享配置
    • 加载扩展配置:通过 spring.cloud.nacos.config.extension-configs[0]=xxx 配置项加载多个扩展配置
    • 加载应用程序配置

    在 Nacos 中,应用程序配置的 Data ID 的完整格式如下:

    ${prefix}-${spring.profile.active}.${file-extension}

    1. prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。
    2. spring.profile.active 即为当前激活的一个或多个环境 profile。
    3. file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。spring 配置使用 properties 或 yml 类型。

    Nacos 按如下顺序加载应用程序配置:

    • 只加载以 ${prefix} 为 Data ID 的配置文件,file-extension 配置项还是要和服务端的配置格式一致
    • 包含后缀的 ${prefix}.${file-extension}
    • 当前激活环境的多个 profile 的完整格式 Data ID

    后加载的配置会被置于链表的头部,所以配置的优先顺序和加载顺序相反

  • ”应用程序配置:[classpath:bootstrap.yml]”(以及相关文件,如果 Spring 配置文件处于激活状态):如果有 bootstrap.yml(或 .properties),则这些属性用于配置 Bootstrap 上下文。然后在设置其父级时将它们添加到子级上下文中。它们的优先级低于 application.yml(或 .properties)以及作为创建 Spring Boot 应用程序过程的正常部分添加到子级的任何其他属性源。

覆盖远程属性值

通过引导上下文添加到应用程序的属性源通常是“远程的”(例如,来自 Nacos Config Server)。默认情况下,它们不能在本地被覆盖。如果你想让应用程序使用自己的系统属性或配置文件覆盖远程属性,远程属性源必须通过设置 spring.cloud.config.allowOverride=true 来授予它权限(在本地设置它不起作用)。一旦设置了该标志,两个更细粒度的设置将控制远程属性相对于系统属性和应用程序本地配置是否可被覆盖:

  • spring.cloud.config.overrideNone=true: 任何本地属性源可以覆盖远程设置。
  • spring.cloud.config.overrideSystemProperties=false: 只有系统属性、命令行参数和环境变量(而不是本地配置文件)可以覆盖远程设置。

如果要完全禁用远程配置服务而使用本地配置,可以通过 spring.cloud.nacos.config.enabled=false 来设置。

Environment 变更

nacos-config-starter-sequence

Nacos 通过 ClientWorker 的 Schedule 执行器线程轮询 Server 来监测配置变更,并将变更通知到 ConfiService 中添加的监听器;Nacos Config Starter 的 NacosContextRefresher 该类默认为所有获取数据成功的 Nacos 的配置项添加监听器,在监听到服务端配置发生变化时会发送 RefreshEvent 事件给 Spring 并实时触发 ContextRefresher 的 refresh 方法 。

Spring 的 refresh 方法主要做两件事:

  • 刷新环境 Environment,将环境中属性源 PropertySource 相对应的属性 bean 从旧的换成新的,并发布 EnvironmentChangeEvent 事件告知变更的属性源的键列表。
  • 触发 RefreshScoperefreshAll 操作,针对 RefreshScope 来说就是清空了他所代理管理的缓存 bean,待再次调用时重新创建,创建过程就会注入新的属性源。

EnvironmentChangeEvent

应用程序可以监听 EnvironmentChangeEvent 并以几种标准方式对更改做出反应(例如用户可以以普通方式注册 ApplicationListener@Beans 来实现监听)。当观察到 EnvironmentChangeEvent 时,该事件具有已更改的键列表,应用程序可以使用这些:

  • 在上下文中重新绑定任何 @ConfigurationProperties bean 的值
  • logging.level.* 中的任何属性设置日志级别

EnvironmentChangeEvent 涵盖了一部分刷新用例,只要您可以实际对 Environment 进行更改并发布事件即可。请注意,这些 API 是公共的,并且是核心 Spring 的一部分)。您可以通过访问 /configprops Endpoint(一个普通的 Spring Boot Actuator 功能)来验证更改是否绑定到 @ConfigurationProperties bean。例如,DataSource 可以在运行时更改其 maxPoolSize(Spring Boot 创建的默认 DataSource@ConfigurationProperties bean)并动态增加容量。

Nacos 也同样支持 Actuator 的 Endpoint 并提供了专门的 /nacosconfig Endpoint 来暴露配置信息。

重新绑定 @ConfigurationProperties 不涵盖另一部分用例,在这些用例中,您需要对刷新进行更多控制,并且需要对整个 ApplicationContext 进行原子更改。为了解决这些问题,我们可以使用 @RefreshScope

Refresh Scope

当配置发生更改时,标记为 @RefreshScope 的 Spring @Bean 会被特殊处理。此功能解决了仅在初始化时才注入配置的有状态 bean 的问题。例如,如果在通过 Environment 更改数据库 URL 时 DataSource 具有打开的连接,您可能希望这些连接的持有者能够完成他们正在做的事情。然后,下一次从池中获取连接时,它会获得一个使用新 URL 的连接 。

有时,甚至可能强制在某些只能初始化一次的 bean 上应用 @RefreshScope 注释。如果 bean 是“不可变的”,则必须使用 @RefreshScope 注解 bean 或在属性键 spring.cloud.refresh.extra-refreshable 下指定类名。

刷新作用域的 bean 是在使用时(即调用方法时)初始化的惰性代理,作用域充当初始化值的缓存。要强制 bean 在下一个方法调用时重新初始化,您必须使其缓存条目无效。

RefreshScope 是上下文中的一个 bean,并且有一个公共的 refreshAll() 方法通过清除目标缓存来刷新范围内的所有 bean。/refresh 端点公开了这个功能(通过 HTTP 或 JMX)。要按名称刷新单个 bean,还有一个 refresh(String) 方法。

配置管理

Nacos 不但提供了 Actuator Endpoint 的 Rest 接口来暴露和管理配置项,还提供了可视化的 Web UI 来进行编辑、存储、分发、变更管理、历史版本管理、变更审计等。

服务发现与健康检查

可以参考官方 DEMO 及文档快速了解集成及使用

服务发现

@EnableDiscoveryClient 注解

Spring Cloud Commons 提供了 @EnableDiscoveryClient 注解。这会寻找带有 META-INF/spring.factoriesDiscoveryClientReactiveDiscoveryClient 接口的实现。服务发现的客户端实现在 spring.factories 的键 org.springframework.cloud.client.discovery.EnableDiscoveryClient 下中添加一个自己的配置类。DiscoveryClient 实现的示例包括 Spring Cloud Netflix Eureka, Spring Cloud Consul DiscoverySpring Cloud Zookeeper Discovery

Nacos 并没有使用该种 SPI 的形式直接提供服务发现客户端实现,而是使用在 spring.factories 下提供 Spring Boot 的自动配置实现来自动注入 DiscoveryClient 的实现 NacosDiscoveryClient

Spring Cloud 默认提供阻塞和响应式服务发现客户端。您可以通过设置 spring.cloud.discovery.blocking.enabled=falsespring.cloud.discovery.reactive.enabled=false 轻松禁用阻塞和/或响应式客户端。要完全禁用服务发现,您只需设置 spring.cloud.discovery.enabled=false

Nacos 同样支持这两种类型的客户端配置,也可以直接使用 Nacos 的 spring.cloud.nacos.discovery.enabled=false 来禁用服务发现。

默认情况下,DiscoveryClient 的实现会自动将本地 Spring Boot 服务器注册到远程发现服务器。可以通过在 @EnableDiscoveryClient 中设置 autoRegister=false 来禁用此行为。

@EnableDiscoveryClient 不再需要。如果类路径上发现 DiscoveryClient 的实现,Spring Boot 应用程序就会自动向服务发现服务器注册。

负载均衡

NacosServerList 实现了 com.netflix.loadbalancer.ServerList 接口,并在 @ConditionOnMissingBean 的条件下进行自动注入,

Nacos Discovery Starter 默认集成了 Ribbon ,所以对于使用了 Ribbon 做负载均衡的组件,可以直接使用 Nacos 的服务发现。

除了 Ribbon,Spring Cloud 也提供了自己的 Load-balancer 实现,我们可以轻松的将 RestTemplateFeignClientWebClient 配置为自动使用负载均衡器的客户端。

服务注册

Spring Cloud Nacos Discovery 遵循了 Spring Cloud Common 标准,实现了 AutoServiceRegistrationServiceRegistryRegistration 这三个接口。

Commons 提供了一个 ServiceRegistry 接口,该接口提供了 register(Registration)deregister(Registration) 等方法,可以让你提供自定义的注册服务。Registration 是一个标记接口。

Nacos 同样也使用该接口来注册服务,通过 Spring Boot 自动配置类提供 ServiceRegistry 的实现 NacosServiceRegistry

每个 ServiceRegistry 实现都有自己的 Registry 实现。

  • ZookeeperRegistrationZookeeperServiceRegistry 一起使用
  • EurekaRegistrationEurekaServiceRegistry 一起使用
  • ConsulRegistrationConsulServiceRegistry 一起使用
  • NacosRegistrationNacosServiceRegistry 一起使用

在 Spring Cloud 应用的启动阶段,监听了 WebServerInitializedEvent 事件,当 Web 容器初始化完成后,即收到 WebServerInitializedEvent 事件后,会触发注册的动作,调用 ServiceRegistryregister 方法,将服务注册到 Nacos Server。

如果需要有更加自定义的获取服务信息或管理服务,可以使用 @Autowired 注入一个 NacosRegistration 实例,通过其 NacosDiscoveryProperties 持有的 NamingService 字段内容直接调用 Nacos API。

服务注册 Actuator Endpoint

Spring Cloud Commons 提供了一个 /service-registry 执行期端点。这个端点依赖于 Spring Application Context 中的 Registration bean。使用 GET 调用 /service-registry 会返回 Registration 服务实例的注册状态。使用带有 JSON 正文的同一端点的 POST 会将当前注册的状态更改为新值。 JSON 正文必须包含具有合法值的 status 字段。请参阅您在更新状态时使用的 ServiceRegistry 实现的文档以及为状态返回的值。例如,Eureka 支持的状态是 UP、DOWN、OUT_OF_SERVICEUNKNOWN

Nacos 也同样支持 Actuator 的 Endpoint 并提供了专门的 /nacos_discovery Endpoint 来获取本机已订阅的服务信息及配置信息。