1、概述
服务发现是微服务架构体系中最关键的组件之一。如果尝试用手动的方式来给每一个客户端来配置所有服务提供者的服务列表是一件非常困难的事,而且也不利于服务的动态扩缩容。
Nacos Discovery 可以帮助您将服务自动注册到 Nacos 服务端并且能够动态感知和刷新某个服务实例的服务列表。
除此之外,Nacos Discovery 也将服务实例自身的一些元数据信息-例如 host,port, 健康检查URL,主页等内容注册到 Nacos。
1.1 为什么会出现Nacos
Nacos是阿里巴巴开源的一个对微服务架构中服务发现,配置管理和服务管理平台,由于第一代SpringCloud也就是SpringCloud Netflix很多组件已经进入停更维护模式,所以迫使我们必须要找到一个可以代替Netflix的第二代产品,这时候SpringCloud Alibaba出现了。
Nacos就是注册中心 + 配置中心的结合体
1.2 Nacos功能特性
- 服务发现与健康监测
- 动态配置管理
- 动态DNS服务
- 服务和元数据管理(管理平台的角度,nacos也有一个ui页面,可以看到注册的服务以及实例信息(元数据信息等),动态的服务权重调整,动态服务优雅下线,都可以去做)
2、注册中心原理
在使用注册中心时,一共有三种角色:服务提供者(Service Provider)、服务消费者(Service Consumer)、注册中心(Registry)。
一般服务提供者被称为Server,服务消费者被称为Client。
三个角色交互图:
Nacos注册中心架构:
服务注册表结构:
1)服务提供者Provider:
- 启动时,向 Registry 注册自己为一个服务(Service)的实例(Instance)
- 同时,定期向 Registry 发送心跳,告诉自己还存活。
- 关闭时,向 Registry 取消注册。
2)服务消费者Consumer:
- 启动时,向Registry订阅使用到的服务,并缓存服务的实例列表在内存中。
- 后续,Consumer 向对应服务的 Provider 发起调用时,从内存中的该服务的实例列表选择一个,进行远程调用。
- 关闭时,向 Registry 取消订阅。
3)注册中心Registry
- 服务提供者Provider超过一定时间未发送心跳时,将从注册中心的服务列表移除。
- 服务的实例列表发生变化(新增或者移除)时,通知订阅该服务的消费者Consumer,从而让 Consumer 能够刷新本地缓存。
另外,Provider 和 Consumer 是角色上的定义,**一个服务同时即可以是 Provider 也可以作为 Consumer。**例如说,优惠劵服务可以给订单服务提供接口,同时又调用用户服务提供的接口
3、快速入门
示例代码对应仓库:
* 服务提供者:labx-01-sca-nacos-discovery-demo01-provider
* 服务消费者:labx-01-sca-nacos-discovery-demo01-consumer
本小节,我们来搭建一个 Nacos Discovery 组件的快速入门示例。步骤如下:
- 首先,搭建一个服务提供者demo-provider,注册服务到Nacos中。
- 然后,搭建一个服务消费者demo-consumer,从 Nacos 获取到 demo-provider 服务的实例列表,选择其中一个示例,进行 HTTP 远程调用。
3.1 搭建服务提供者
创建 labx-01-sca-nacos-discovery-demo01-provider 项目,作为服务提供者 demo-provider。最终项目代码如下图所示:
3.1.1 引入依赖
在 pom.xml 文件中,主要引入 Spring Cloud Nacos Discovery 相关依赖。代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>labx-01</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>labx-01-sca-nacos-discovery-demo01-provider</artifactId>
<properties>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
<spring.cloud.alibaba.version>2.2.0.RELEASE</spring.cloud.alibaba.version>
</properties>
<!--
引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,进行依赖版本的管理,防止不兼容。
在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 开发团队推荐了三者的依赖关系
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 引入 SpringMVC 相关依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Spring Cloud Alibaba Nacos Discovery 相关依赖,将 Nacos 作为注册中心,并实现对其的自动配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
引入 spring-cloud-starter-alibaba-nacos-discovery 依赖,将 Nacos 作为注册中心,并实现对它的自动配置。
引入spring-cloud-starter-alibaba-nacos-discovery 依赖,它的内部同时也引入了ribbon
@LoadBalanced注解
3.1.2 配置文件
创建 application.yaml 配置文件,添加 Nacos Discovery 配置项。配置如下:
spring:
application:
name: demo-provider # Spring 应用名
cloud:
nacos:
# Nacos 作为注册中心的配置项,对应 NacosDiscoveryProperties 配置类
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
service: ${spring.application.name} # 注册到 Nacos 的服务名。默认值为 ${spring.application.name}。
server:
port: 18080 # 服务器端口。默认为 8080
重点看 spring.cloud.nacos.discovery 配置项,它是 Nacos Discovery 配置项的前缀,对应 NacosDiscoveryProperties 配置项.
3.1.3 DemoProviderApplication
创建 DemoProviderApplication 类,创建应用启动类,并提供 HTTP 接口。代码如下:
@SpringBootApplication
@EnableDiscoveryClient
public class DemoProviderApplication {
public static void main(String[] args) {
SpringApplication.run(DemoProviderApplication.class, args);
}
@RestController
static class TestController {
@GetMapping("/echo")
public String echo(String name) {
return "provider:" + name;
}
}
}
① @SpringBootApplication注解,被添加在类上,声明这是一个Spring Boot应用。SpringCloud是构建在SpringBoot之上的,所以需要添加。
② @EnableDiscoveryClient注解,开启Spring Cloud的注册发现功能,不过从 Spring Cloud Edgware 版本开始,实际上已经不需要添加 @EnableDiscoveryClient 注解,只需要引入 Spring Cloud 注册发现组件,就会自动开启注册发现的功能。例如说,我们这里已经引入了 spring-cloud-starter-alibaba-nacos-discovery 依赖,就不用再添加 @EnableDiscoveryClient 注解了。
③ TestController 类,提供了 /echo 接口,返回 provider:${name} 结果。
3.1.4 简单测试
① 通过 DemoProviderApplication 启动服务提供者。
- 服务demo-provider注册到Nacos上的日志。
② 打开Nacos控制台,可以在服务列表看到服务demo-provider
3.2 搭建服务消费者
创建 labx-01-sca-nacos-discovery-demo01-consumer 项目,作为服务提供者 demo-consumer。最终项目代码如下图所示:
整个项目的代码,和服务提供者是基本一致的,毕竟是示例代码。
3.2.1 引入依赖
和「3.1.1 引入依赖」一样。
3.2.2 配置文件
创建yaml配置文件,添加相应配置项。配置如下:
spring:
application:
name: demo-consumer # Spring 应用名
cloud:
nacos:
# Nacos 作为注册中心的配置项,对应 NacosDiscoveryProperties 配置类
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
server:
port: 28080 # 服务器端口。默认为 8080
和「3.1.2 配置文件」基本一致,主要是将配置项目 spring.application.name 修改为 demo-consumer。
3.2.3 DemoConsumerApplication
创建 DemoConsumerApplication 类,创建应用启动类,并提供一个调用服务提供者的 HTTP 接口。代码如下:
@SpringBootApplication
// @EnableDiscoveryClient
public class DemoConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(DemoConsumerApplication.class, args);
}
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
static class TestController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/hello")
public String hello(String name) {
// <1> 获得服务 `demo-provider` 的一个实例
ServiceInstance instance;
if (true) {
// 获取服务 `demo-provider` 对应的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("demo-provider");
// 选择第一个
instance = instances.size() > 0 ? instances.get(0) : null;
} else {
instance = loadBalancerClient.choose("demo-provider");
}
// <2> 发起调用
if (instance == null) {
throw new IllegalStateException("获取不到实例");
}
String targetUrl = instance.getUri() + "/echo?name=" + name;
String response = restTemplate.getForObject(targetUrl, String.class);
// 返回结果
return "consumer:" + response;
}
}
}
① @EnableDiscoveryClient 注解,因为已经无需添加,所以我们进行了注释,原因在上面已经解释过。
② RestTemplateConfiguration 配置类,创建 RestTemplate Bean。RestTemplate 是 Spring 提供的 HTTP 调用模板工具类,可以方便我们稍后调用服务提供者的 HTTP API。
③ TestController 提供了 /hello 接口,用于调用服务提供者的 /demo 接口。代码略微有几行,我们来稍微解释下哈。
discoveryClient 属性,DiscoveryClient 对象,服务发现客户端,上文我们已经介绍过。这里我们注入的不是 Nacos Discovery 提供的 NacosDiscoveryClient,保证通用性。未来如果我们不使用 Nacos 作为注册中心,而是使用 Eureka 或则 Zookeeper 时,则无需改动这里的代码。
loadBalancerClient 属性,LoadBalancerClient 对象,负载均衡客户端。稍后我们会使用它,从 Nacos 获取的服务 demo-provider 的实例列表中,选择一个进行 HTTP 调用。
/hello 接口,示例接口,对服务提供者发起一次 HTTP 调用。
- <1> 处,获得服务 demo-provider 的一个实例。这里我们提供了两种方式的代码,分别基于 DiscoveryClient 和 LoadBalancerClient。
- <2> 处,通过获取到的服务实例 ServiceInstance 对象,拼接请求的目标 URL,之后使用 RestTemplate 发起 HTTP 调用。
3.2.4 简单测试
① 通过 DemoConsumerApplication 启动服务消费者,IDEA 控制台输出日志如:
// ... 省略其它日志
2020-02-08 18:05:35.810 INFO 35047 --- [ main] c.a.c.n.registry.NacosServiceRegistry : nacos registry, DEFAULT_GROUP demo-consumer 10.171.1.115:28080 register finished
- 服务demo-consumer注册到Nacos上的日志。
注意,服务消费者和服务提供是一种角色的概念,本质都是一种服务,都是可以注册自己到注册中心的。
② 打开 Nacos 控制台,可以在服务列表看到服务 demo-consumer。如下图:
③ 访问服务消费者的 http://127.0.0.1:28080/hello?name=yudaoyuanma 接口,返回结果为 “consumer:provider:yudaoyuanma”。说明,调用远程的服务提供者成功。
④ 打开 Nacos 控制台,可以在订阅者列表看到订阅关系。如下图:
⑤ 关闭服务提供者后,再次访问 http://127.0.0.1:28080/hello?name=yudaoyuanma 接口,返回结果为报错提示 “获取不到实例”,说明我们本地缓存的服务 demo-provider 的实例列表已刷新,没有任何实例。
4.Nacos概念详解
4.1 数据模型
Nacos 数据模型 Key 由三元组唯一确认。如下图所示:
- 作为注册中心时,Namespace + Group + Service
- 作为配置中心时,Namespace + Group + DataId
4.1.1 Namespace命名空间
用于进行租户粒度的配置隔离。默认为 public(公共命名空间)。
不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
4.1.2 Group服务分组
不同的服务可以归类到统一分组。默认为DEFAULT_GROUP(默认分组).
Service服务
例如说,用户服务、订单服务、商品服务等等。
5、更多的配置项信息
-
Nacos服务器相关
-
服务相关
6、多环境配置
同一个服务,我们会部署到开发、测试、预发布、生产等环境中,那么我们需要在项目中,添加不同环境的 Nacos 配置。一般情况下,开发和测试使用同一个 Nacos,预发布和生产使用另一个 Nacos。那么针对相同的 Nacos,我们怎么实现不同环境的隔离呢?
实际上,Nacos 开发者已经告诉我们如何实现了,通过 Nacos Namespace 命名空间。文档说明如下:
下面,我们来搭建一个多环境配置的示例。步骤如下:
- 首先,我们会在 Nacos 中创建开发环境使用的 Namespace 为 dev,测试环境使用的 Namespace 为 uat。
- 然后,搭建一个服务提供者 demo-provider,使用开发环境配置,注册服务到 Nacos 的 dev Namespace 下。
- 之后,搭建一个服务消费者 demo-consumer,调用服务提供者 demo-provider 提供的 HTTP 接口。
- 先使用开发环境配置,因为服务 demo-provider 是在 Nacos dev Namespace 下注册,所以调用它成功。
- 后使用测试环境配置,因为服务 demo-provider 不在 Nacos uat Namespace 下注册,所以调用它失败。
6.1 创建Nacos命名空间
① 打开 Nacos UI 界面的「命名空间」菜单,进入「命名空间」功能。如下图所示:
② 点击列表右上角的「新建命名空间」按钮,弹出「新建命名空间」窗口,创建一个 dev 命名空间。输入如下内容,并点击「确定」按钮,完成创建。如下图所示:
③ 重复该操作,继续创建一个 uat 命名空间。最终 dev 和 uat 信息如下图:
6.2 搭建服务提供者
从「3.1 搭建服务提供者」小节的 labx-01-sca-nacos-discovery-demo01-provider 项目,复制出 labx-01-sca-nacos-discovery-demo02-provider 项目。然后在其上进行修改,方便搭建~
6.2.1 配置文件
修改 application.yaml 配置文件,将 Nacos Discovery 配置项删除,稍后添加在不同环境的配置文件中。配置如下:
spring:
application:
name: demo-provider # Spring 应用名
server:
port: 18080 # 服务器端口。默认为 8080
创建开发环境使用的 application-dev.yaml 配置文件,增加 Namespace 为 dev 的 Nacos Discovery 配置项。配置如下:
spring:
cloud:
nacos:
# Nacos 作为注册中心的配置项,对应 NacosDiscoveryProperties 配置类
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
namespace: 14226a0d-799f-424d-8905-162f6a8bf409 # Nacos 命名空间 dev 的编号
创建测试环境使用的 application-uat.yaml 配置文件,增加 Namespace 为 uat 的 Nacos Discovery 配置项。配置如下:
spring:
cloud:
nacos:
# Nacos 作为注册中心的配置项,对应 NacosDiscoveryProperties 配置类
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
namespace: bc8c8c2d-bd85-42bb-ada3-1a8f940ceb20 # Nacos 命名空间 uat 的编号
6.2.2 简单测试
下面,我们使用命令行参数进行 --spring.profiles.active 配置项,实现不同环境,读取不同配置文件。
① 先配置 --spring.profiles.active 为 dev,设置 DemoProviderApplication 读取 application-dev.yaml 配置文件。如下图所示:
之后通过 DemoProviderApplication 启动服务提供者。
② 打开 Nacos 控制台,可以在服务列表看到服务 demo-provider 注册在命名空间 dev 下。如下图:
6.3 搭建服务消费者
从「3.2 搭建服务消费者」小节的 labx-01-sca-nacos-discovery-demo01-consumer 项目,复制出 labx-01-sca-nacos-discovery-demo02-consumer 项目。然后在其上进行修改,方便搭建~
6.3.1 配置文件
修改 application.yaml 配置文件,将 Nacos Discovery 配置项删除,稍后添加在不同环境的配置文件中。配置如下:
spring:
application:
name: demo-consumer # Spring 应用名
server:
port: 28080 # 服务器端口。默认为 8080
创建开发环境使用的 application-dev.yaml 配置文件,增加 Namespace 为 dev 的 Nacos Discovery 配置项。配置如下:
spring:
cloud:
nacos:
# Nacos 作为注册中心的配置项,对应 NacosDiscoveryProperties 配置类
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
namespace: 14226a0d-799f-424d-8905-162f6a8bf409 # Nacos 命名空间 dev 的编号
创建测试环境使用的 application-uat.yaml 配置文件,增加 Namespace 为 uat 的 Nacos Discovery 配置项。配置如下:
spring:
cloud:
nacos:
# Nacos 作为注册中心的配置项,对应 NacosDiscoveryProperties 配置类
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
namespace: bc8c8c2d-bd85-42bb-ada3-1a8f940ceb20 # Nacos 命名空间 uat 的编号
6.2.3 测试
下面,我们使用命令行参数进行 –spring.profiles.active 配置项,实现不同环境,读取不同配置文件。
① 先配置 --spring.profiles.active 为 dev,设置 DemoConsumerApplication 读取 application-dev.yaml 配置文件。如下图所示:
之后通过 DemoConsumerApplication 启动服务消费者
访问服务消费者的 http://127.0.0.1:28080/hello?name=yudaoyuanma 接口,返回结果为 “consumer:provider:yudaoyuanma”。说明,调用远程的服务提供者【成功】。
② 再配置 --spring.profiles.active 为 uat,设置 DemoConsumerApplication 读取 application-uat.yaml 配置文件。如下图所示:
之后通过 DemoConsumerApplication 启动服务消费者。
访问服务消费者的 http://127.0.0.1:28080/hello?name=yudaoyuanma 接口,返回结果为 报错提示 “获取不到实例”。说明,调用远程的服务提供者【失败】。
原因是,虽然说服务 demo-provider 已经启动,因为其注册在 Nacos 的 Namespace 为 dev,这就导致第 ① 步启动的服务 demo-consumer 可以调用到该服务,而第② 步启动的服务 demo-consumer 无法调用到该服务。
即,我们可以通过 Nacos 的 Namespace 实现不同环境下的服务隔离。未来,在开源版本 Nacos 权限完善之后,每个 Namespace 提供不同的 AccessKey、SecretKey,保证只有知道账号密码的服务,才能连到对应的 Namespace,进一步提升安全性。