Nacos 入门教程 - 服务发现基础篇

服务发现介绍

微服务架构概述

为适应企业的业务发展,提高软件研发的生产力,降低软件研发的成本,软件架构也作了升级和优化,将一个独立的系统拆分成若干小的服务,每个小服务运行在不同的进程中,服务与服务之间采用 RESTful、RPC 等协议传输数据,每个服务所拥有的功能具有独立性强的特点,这样的设计就实现了单个服务的高内聚,服务与服务之间的低耦合效果,这些小服务就是微服务,基于这种方法设计的系统架构即微服务架构。微服务架构的优点如下:

  • 易于开发和维护:一个微服务只会关注一个特定的业务功能,所以它业务清晰,代码量较少
  • 单个微服务启动较快:单个微服务代码量较少,所以启动会比较快
  • 业务之间松耦合,无论是在开发阶段或者部署阶段,不同的服务都是互相独立的
  • 局部修改容易部署:单体应用只要有修改,就得重新部署整个应用,微服务解决了这样的问题
  • 技术栈不受限:在微服务架构中,可以结合项目业务及团队的特点,合理地选择技术栈
  • 按需伸缩:可根据需求,实现细粒度的扩展
  • 只有业务逻辑的代码,不会和 HTML、CSS 或者其他前端页面耦合,目前有两种开发模式:前后端分离、全栈开发

什么是服务发现

在微服务架构中,整个系统会按职责能力划分为多个服务,通过服务之间协作来实现业务目标。这样在代码中免不了要进行服务间的远程调用,服务的消费方要调用服务的生产方,为了完成一次请求,消费方需要知道服务生产方的网络位置(IP 地址和端口号)。一般情况下,代码可以通过读取配置文件的方式读取服务生产方网络位置,如下图所示:

nacos-discovery-service-call

看上去很完美,但是仔细考虑以下,此方案对于微服务应用而言行不通。首先,微服务可能是部署在云环境的,服务实例的网络位置或许是动态分配的。另外,每一个服务一般会有多个实例来做负载均衡,由于宕机或升级,服务实例网络地址会经常动态改变。再者,每一个服务也可能应对临时访问压力增加新的服务节点,如下图所示:

nacos-discovery-service-call-2

基于以上的问题,服务之间如何相互发现?服务如何管理?这就是服务发现的问题了。服务发现就是服务消费方通过服务发现中心智能发现服务提供方,从而进行远程调用的过程,如下图所示:

nacos-discovery-service-call-3

上图中服务实例本身并不记录服务生产方的网络地址,所有服务实例内部都会包含服务发现客户端。

  • 在每个服务启动时会向服务发现中心上报自己的网络位置,这样在服务发现中心内部会形成一个服务注册表,服务注册表是服务发现的核心部分,是包含所有服务实例的网络地址的数据库
  • 服务发现客户端会定期从服务发现中心同步服务注册表,并缓存在客户端
  • 当需要对某服务进行请求时,服务实例通过该注册表,定位目标服务网络地址。若目标服务存在多个网络地址,则使用负载均衡算法从多个服务实例中选择出一个,然后发出请求。

总结:

在微服务环境中,由于服务运行实例的网络地址是不断动态变化的,服务实例数量的动态变化 ,因此无法使用固定的配置文件来记录服务提供方的网络地址,必须使用动态的服务发现机制用于实现微服务间的相互感知。各服务实例会上报自己的网络地址,这样服务中心就形成了一个完整的服务注册表,各服务实例会通过服务发现中心来获取访问目标服务的网络地址,从而实现服务发现的机制。

服务发现协作流程

nacos-discovery-process

服务发现产品对比

register-center-vs

Nacos 作为服务发现中心,具备更多的功能支持项,且从长远来看 Nacos 在以后的版本会支持 Spring Cloud + Kubernetes 的组合,填补两者者的鸿沟,在两套体系下可以采用同一套服务发现和配置管理的解决方案,这将大大的简化使用和维护的成本。另外,Nacos 计划实现 Service Mesh,也是未来微服务发展的趋势,更多关于 Nacos 的介绍可以看这里

Nacos 服务发现管理

服务发现数据模型

Nacos 在经过阿里内部多年生产经验后提炼出的数据模型,是一种服务 - 集群 - 实例的三层模型,这样基本可以满足服务在所有场景下的数据存储和管理。

nacos-discovery-model

命名空间(Namespace)

用于进行租户粒度的配置隔离,命名空间不仅适用于 Nacos 的配置管理,同样适用于服务发现。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

服务

提供给客户端的软件功能,通过预定义接口进行网络访问。

服务名

服务提供方的标识,通过该标识可以唯一确定其指代的服务。

实例

提供一个或多个服务的具有可访问网络地址(IP:Port)的进程,启动一个服务,就产生了一个服务实例。

元信息

Nacos 数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签 (label),从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息

集群

服务实例的集合,服务实例组成一个默认集群,集群可以被进一步按需求划分,划分的单位可以是虚拟集群,相同集群下的实例才能相互感知。

服务发现配置示例

应用通过 Namespace、Cluster、Service 的配置,描述了该服务向哪个环境(如开发环境)的哪个集群注册实例

1
2
3
4
5
6
7
8
9
spring:
application:
name: transaction‐service
cloud:
nacos:
discovery:
server‐addr: 127.0.0.1:8848
namespace: a1f8e863‐3117‐48c4‐9dd3‐e9ddc2af90a8
cluster-name: DEFAULT
  • 集群作为实例的隔离,相同集群的实例才能相互感知
  • namespace、cluster-name 若不填写都将采用默认值,namespace 的默认是 public 命名空间,cluster-name 的默认值为 DEFAULT 集群

服务发现管理功能

服务管理

开发者或者运维人员往往需要在服务注册后,通过友好的界面来查看服务的注册情况,包括当前系统注册的所有服务和每个服务的详情。并在有权限控制的情况下,进行服务的一些配置的编辑操作。Nacos 在目前最新版本开放的控制台的服务发现部分,主要就是提供用户一个基本的运维页面,能够查看、编辑当前注册的服务,这些功能集中在 Nacos 控制台的服务管理一级菜单内。

服务列表管理

服务列表帮助用户以统一的视图管理其所有的微服务以及服务健康状态。整体界面布局是左上角有服务的搜索框和搜索按钮,页面中央是服务列表的展示。服务列表主要展示服务名、集群数目、实例数目、健康实例数目和详情按钮五个栏目。

nacos-discovery-service-list

服务流量权重支持及流量保护

Nacos 为用户提供了流量权重控制的能力,同时开放了服务流量的阈值保护,以帮助用户更好的保护服务服务提供者集群不被意外打垮。如下图所示,可以点击实例的 编辑 按钮,修改实例的权重。如果想增加实例的流量,可以将权重调大;如果不想实例接收流量,则可以将权重设为 0。

nacos-discovery-weight

服务元数据管理

Nacos 提供多个维度的服务元数据的暴露,帮助用户存储自定义的信息。这些信息都是以 K-V 的数据结构存储,在控制台上,会以 JSON 数据格式来展示。类似的,编辑元数据可以通过相同的格式进行。例如服务的元数据编辑,首先点击服务详情页里的 编辑 按钮,然后在元数据输入框输入:{"version": 1.0}

nacos-discovery-metadata

服务优雅上下线

Nacos 还提供服务实例的上下线操作,在服务详情页面,可以点击实例的 上线 或者 下线 按钮,被下线的实例,将不会包含在健康的实例列表里。

nacos-discovery-online

Nacos Discovery Spring 入门案例

1.0、版本说明

在本案例中,Spring 的版本为 5.2.x,Nacos Server 的版本为 1.4.0,点击下载完整的案例代码。

1.1、添加 Maven 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-spring-context</artifactId>
<version>1.0.0</version>
</dependency>

1.2、创建 Nacos 配置类

通过添加 @EnableNacosDiscovery 注解开启 Nacos Spring 的服务发现功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.nacos.study.configuration;

import com.alibaba.nacos.api.annotation.NacosProperties;
import com.alibaba.nacos.spring.context.annotation.discovery.EnableNacosDiscovery;
import org.springframework.context.annotation.Configuration;

/**
* @author clay
*/
@Configuration
@EnableNacosDiscovery(globalProperties = @NacosProperties(serverAddr = "127.0.0.1:8848"))
public class NacosConfiguration {

}

1.3、创建 Controller 测试类

使用 @NacosInjected 注入 Nacos 的 NamingService 实例,通过该实例获取 Nacos Server 的服务列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.nacos.study.controller;

import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

/**
* @author clay
*/
@Controller
@RequestMapping("/discovery")
public class DiscoveryController {

@NacosInjected
private NamingService namingService;

@RequestMapping(value = "/get", method = RequestMethod.GET)
@ResponseBody
public List<Instance> get(@RequestParam(defaultValue = "") String serviceName) throws NacosException {
return namingService.getAllInstances(serviceName);
}
}

1.4、配置 web.xml

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

1.5、配置 dispatcherServlet-servlet.xml

1
2
3
4
5
6
7
<!-- Spring MVC Annotation-Driven -->
<mvc:annotation-driven/>

<!-- Spring Context Annotation-Driven -->
<context:annotation-config/>

<context:component-scan base-package="com.nacos.study"/>

1.6、调用 Nacos Open API 注册服务

调用 Nacos Open API 注册一个名称为 example 的服务,这里模拟了服务生产者自动注册服务到 Nacos Server。由于注册的服务不是真实存在的,因此服务注册一段时间后,会因 Nacos Server 的健康检查机制而被剔除出服务列表

1
$ curl -X PUT 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=example&ip=127.0.0.1&port=8080'

nacos-discovery-register-service

1.7、测试应用程序

  • 将 Spring Web 应用部署到 Tomcat 服务器
  • 浏览器访问 http://127.0.0.1:8080/discovery/get?serviceName=example,若响应结果如下,则说明程序运行正常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"instanceId": "127.0.0.1#8080#DEFAULT#DEFAULT_GROUP@@example",
"ip": "127.0.0.1",
"port": 8080,
"weight": 1.0,
"healthy": true,
"enabled": true,
"ephemeral": true,
"clusterName": "DEFAULT",
"serviceName": "DEFAULT_GROUP@@example",
"metadata": {

},
"instanceHeartBeatInterval": 5000,
"instanceHeartBeatTimeOut": 15000,
"ipDeleteTimeout": 30000,
"instanceIdGenerator": "simple"
}
]

Nacos Discovery Spring Boot 入门案例

2.0、版本说明

在本案例中,Spring Boot 的版本为 2.0.3.RELEASE,对应的 Nacos Discovery Spring Boot 的版本为 0.2.7,Nacos Server 的版本为 1.4.0,点击下载完整的案例代码。

2.1、添加 Maven 依赖

特别注意,Nacos Spring Boot Starter 版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<nacos-discovery-spring-boot.version>0.2.7</nacos-discovery-spring-boot.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-discovery-spring-boot-starter</artifactId>
<version>${nacos-discovery-spring-boot.version}</version>
</dependency>
</dependencies>

2.2、创建启动主类

1
2
3
4
5
6
7
@SpringBootApplication
public class NacosDiscoveryApplication {

public static void main(String[] args) {
SpringApplication.run(NacosDiscoveryApplication.class, args);
}
}

2.3、创建 Controller 测试类

使用 @NacosInjected 注入 Nacos 的 NamingService 实例,通过该实例获取 Nacos Server 的服务列表

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
@RequestMapping("/discovery")
public class DiscoveryController {

@NacosInjected
private NamingService namingService;

@RequestMapping(value = "/get", method = RequestMethod.GET)
@ResponseBody
public List<Instance> get(@RequestParam(defaultValue = "") String serviceName) throws NacosException {
return namingService.getAllInstances(serviceName);
}
}

2.4、配置 application.properties

application.properties 中配置 Nacos Server 的地址

1
nacos.discovery.server-addr=127.0.0.1:8848

2.5、调用 Nacos Open API 注册服务

调用 Nacos Open API 注册一个名称为 example 的服务,这里模拟了服务生产者自动注册服务到 Nacos Server。由于注册的服务不是真实存在的,因此服务注册一段时间后,会因 Nacos Server 的健康检查机制而被剔除出服务列表

1
$ curl -X PUT 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=example&ip=127.0.0.1&port=8080'

nacos-discovery-register-service

2.6、测试应用程序

  • 启动 Spring Boot 应用
  • 浏览器访问 http://127.0.0.1:8080/discovery/get?serviceName=example,若响应结果如下,则说明程序运行正常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"instanceId": "127.0.0.1#8080#DEFAULT#DEFAULT_GROUP@@example",
"ip": "127.0.0.1",
"port": 8080,
"weight": 1.0,
"healthy": true,
"enabled": true,
"ephemeral": true,
"clusterName": "DEFAULT",
"serviceName": "DEFAULT_GROUP@@example",
"metadata": {

},
"instanceIdGenerator": "simple",
"instanceHeartBeatInterval": 5000,
"instanceHeartBeatTimeOut": 15000,
"ipDeleteTimeout": 30000
}
]

Nacos Discovery Spring Cloud 入门案例

3.0、版本说明

在本案例中,Spring Cloud 的版本是 Greenwich.SR6,对应的 Spring Boot 版本是 2.1.18.RELEASE,对应的 Nacos Discovery Spring Cloud 版本为 2.1.3.RELEASE,Nacos 官方版本说明可以看这里点击下载完整的案例代码。

3.1、创建 Maven 父工程

在 Maven 父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体配置如下。特别注意,Nacos Spring Cloud Starter 版本 2.1.x.RELEASE 对应的是 Spring Boot 2.1.x 版本,版本 2.0.x.RELEASE 对应的是 Spring Boot 2.0.x 版本,版本 1.5.x.RELEASE 对应的是 Spring Boot 1.5.x 版本,Nacos 官方版本说明可以看这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.18.RELEASE</version>
</parent>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
<nacos-discovery-spring-cloud.version>2.1.3.RELEASE</nacos-discovery-spring-cloud.version>
</properties>

<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<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-starter-alibaba-nacos-discovery</artifactId>
<version>${nacos-discovery-spring-cloud.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<!-- 利用传递依赖,公共部分 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

3.2、创建 Provider Service 工程

创建 Provider Service 的 Maven 工程,配置工程里的 pom.xml 文件,需要引入 spring-cloud-starter-alibaba-nacos-discovery 依赖

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

创建主启动类,添加 @EnableDiscoveryClient 注解,将服务注册到 Nacos Server

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {

public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}

创建 Controller 测试类

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/provider")
public class ProviderController {

@GetMapping("/call")
public String call() {
return "provider invoke";
}
}

application.properties 中配置 Nacos Server 的地址

1
2
3
4
5
6
7
8
9
10
server:
port: 56011

spring:
application:
name: provider-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848

3.3、创建 Consumer Service 工程

创建 Consumer Service 的 Maven 工程,配置工程里的 pom.xml 文件,引入 spring-cloud-starter-alibaba-nacos-discovery 依赖,由于需要通过 Feign Client 调用远程服务,因此还需要引入 spring-cloud-starter-openfeign 依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

创建主启动类,添加 @EnableDiscoveryClient 注解,将服务注册到 Nacos Server,同时添加 @EnableFeignClients 注解来启用 Feign Client

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {

public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}

创建服务接口类,用于调用 Provider Service 暴露的 API

1
2
3
4
5
6
7
@FeignClient("provider-service")
public interface ProviderClient {

@GetMapping("/provider/call")
public String call();

}

创建 Controller 测试类,因为需要创建一个 API 来供第三方调用 Provider Service 的那个自定义 API

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/consumer")
public class ConsumerController {

@Autowired
private ProviderClient providerClient;

@GetMapping("/call")
public String call() {
return "consumer invoke | " + providerClient.call();
}

}

application.properties 中配置 Nacos Server 的地址

1
2
3
4
5
6
7
8
9
10
server:
port: 56010

spring:
application:
name: consumer-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848

3.4、测试应用程序

  • 1)分别启动 nacos-provider-service、nacos-consumer-service 应用
  • 2)浏览器访问 http://127.0.0.1:56011/provider/call,若响应结果为 provider invoke,则说明 nacos-provider-service 应用运行正常
  • 3)浏览器访问 http://127.0.0.1:56010/consumer/call,若响应结果为 consumer invoke | provider invoke,则说明 nacos-consumer-service 应用运行正常
  • 4)在 Nascos Server 的控制台,可以看到已经有两个服务成功注册了,如下图:

nacos-discovery-springcloud-services

  • 5)若希望测试多实例(Provider)的负载均衡调用情况,可以修改 Provider Service 工程下的 application.properties 配置文件里的 server.port 参数(如下),然后通过 -Dport=xxxxx VM 参数指定不同的端口来启动多个 Provider Service 应用即可
1
2
server:
port: ${port:56011}

补充内容

Endpoint 信息查看

Spring Boot 支持这一点,Nacos Discovery 也可以使用 Endpoint 来暴露信息,先决条件是将依赖 spring-boot-starter-actuator 添加到 pom.xml 文件中,并配置端点安全策略。

  • Spring Boot 1.x:添加配置 management.security.enabled = false
  • Spring Boot 2.x:添加配置 management.endpoints.web.exposure.include = *
  • Spring Boot 1.x:Nacos Discovery 端点查看的 URL 是 http://127.0.0.1:18083/nacos_discovery
  • Spring Boot 2.x:Nacos Discovery 端点查看的 URL 是 http://127.0.0.1:18083/actuator/nacos-discovery

Nacos 服务发现原理浅析

服务注册

在 Spring 应用程序的启动阶段,将监视 WebServerInitializedEvent 事件。在初始化 Web 容器后收到 WebServerInitializedEvent 事件时,将触发注册操作,并调用 ServiceRegistry 注册方法以将服务注册到 Nacos Server。

服务发现

NacosServerList 实现 com.netflix.loadbalancer.ServerList 接口,并在 @ConditionOnMissingBean 下自动注入它。

Nacos 服务发现常用配置说明

nacos-discovery-configurations