在学习 Nacos 以前,先保证了解基本的使用与实现,带入问题去思考,在 分布式系统中为什么需要使用注册中心? 、 Nacos 到底是 AP模型 还是 CP模型? 你将会有不一样的收获!!!
Nacos(Naming Configuration Service) 是一个易于使用的动态服务发现、配置和服务管理平台,用于构建云原生应用程序。服务发现是微服务架构中的关键组件之一,Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
官方文档:https://nacos.io/docs/next/overview/
在开始学习之前,我们来思考一个问题,那就是为什么要使用注册中心呢?
以入住酒店的前台为例子,先设想一个没有服务前台的酒店,客人入住需要自己寻找合适居住的房间,客人不知道每个房间的情况,无法确定那个房间是打扫干净可以入住,客人只能逐个房间寻找,如果遇到已经居住房客的房间一定很尴尬,显然这是不正常的情况。正常的情况是酒店会安排服务台,酒店打扫干净可以入住的房间会登记注册到服务台,这样客人来住店,只需要在前台就可以查找到可以入住的房间,这样就无需等待快速的入住。显然,服务器提供发注册和发现机制可以让房客快速找到合适的房间,快速解决入住问题。

采用微服务以后,软件微服务组件各自独立,但最终还要组合为一个整体作为一个软件系统服务于最终客户,这时软件组件之间也需要彼此通讯,彼此调用方法。
微服务架构内部发起通讯调用方法的一方成为 “服务消费者” ,提供远程方法调用的服务器称为 “服务提供者” ,往往为了提高系统性能,会提供多个服务器作为 服务提供者,此时 服务消费者 找到 服务提供者 的过程,就类似于用户在找房间的过程。为了帮助 服务消费者 快速的发现 服务提供者,在微服务框架中都会引入 注册中心。
注册中心 类似于酒店的前台,提供在软件服务的注册和发现功能,服务提供者 会先在注册中心进行 注册,声明可以对外提供服务,而 服务消费者 只需要在注册中心就可以快速 发现 找到可以使用的服务,快速使用服务。注册中心实现了服务提供和服务消费的快速撮合功能。

打开官网下载对应的服务版本:https://nacos.io/zh-cn/index.html
历史版本下载地址:https://github.com/alibaba/nacos/releases

下载之后,进行安装,流程如下:
解压以后找到bin目录
执行指令:
启动命令(standalone代表着单机模式运行,非集群模式):
sh startup.sh -m standalone
启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
运行后



Nacos 默认自带嵌入式数据库 derby,所以我们每次创建一个 Nacos 实例就会有一个 derby,当有多个Nacos 节点的时候,就会出现一致性问题,所以 Nacos 支持了外部数据库统一数据管理 MySQL。
官网部署地址:https://nacos.io/zh-cn/docs/deployment.html
修改 application.properties 文件,取消 MySQL 的注释

找到 Nacos 安装目录下的 conf 目录中的 Sql 脚本,然后在数据库中进行执行到命名为 nacos 的数据库中,然后修改上述配置,重启即可

Nacos 可以直接提供注册中心(Eureka)+ 配置中心(Config),所以它的好处显而易见,我们在前面成功安装和启动了 Nacos 以后就可以发现 Nacos 本身就是一个小平台,它要比之前的 Eureka 更加方便,不需要我们在自己做配置。
服务发现是微服务架构中的关键组件之一。在这样的架构中,手动为每个客户端配置服务列表可能是一项艰巨的任务,并且使得动态扩展极其困难。Nacos Discovery 帮助您自动将您的服务注册到 Nacos 服务器,Nacos 服务器会跟踪服务并动态刷新服务列表。此外,Nacos Discovery 将服务实例的一些元数据,如主机、端口、健康检查 URL、主页等注册到 Nacos。
学习任何知识我们都需要从它的官方文档入手,所以我们直接来看官网给我们提供的文档:https://spring.io/projects/spring-cloud-alibaba#learn

首先创建一个Maven的父项目。用来聚合管理。并声明了 SpringCloud 和 SpringCloudAlibaba 的版本,版本关系可查看: https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明
xml<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-cloud-alibaba-version>2021.1</spring-cloud-alibaba-version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
创建一个服务提供者的模块。父项目为上面创建的项目,同样是一个 SpringBoot 项目,pom.xml 中的完整依赖信息如下:
xml<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.liushigong</groupId>
<artifactId>NacosProviderExample9001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>NacosProviderExample9001</name>
<parent>
<groupId>com.liushigong</groupId>
<artifactId>SpringCloudExample</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.2</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.boge.provider.AppProviderStart</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
然后在 application.yml 中添加 Nacos 的配置信息
ymlserver:
port: 9001
spring:
application:
name: nacos-provider
cloud:
discovery:
server-addr: 127.0.0.1:8848
management:
endpoint:
web:
exposure:
include:'*'
然后在启动类中添加 @EnableDiscoveryClient 注解,默认可不加
java@EnableDiscoveryClient
@SpringBootApplication
public class AppProviderStart {
public static void main(String[] args) {
SpringApplication.run(AppProviderStart.class, args);
}
}
然后添加提供的服务接口
java@RestController
public class DemoController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/bobo")
public String getServerPort(){
return "Hello Nacos Discovery"+serverPort;
}
}
注意需要在父项目中标记:
<modules> <module>NacosProviderExample9001</module> </modules>
启动服务我们就可以在Nacos中服务列表中看到注册的服务信息

同样的我们再创建一个服务消费者的 SpringBoot 项目。并添加相关的依赖
xml<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.liushigong</groupId>
<artifactId>NacosConsumerExample9101</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>NacosConsumerExample9101</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>com.liushigong</groupId>
<artifactId>SpringCloudExample</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.2</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.boge.consumer.AppConsumerStart</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
然后在属性文件中添加对应的配置信息
ymlserver:
port: 9101
spring:
application:
name: nacos-consumer
cloud:
discovery:
server-addr: localhost:8848
service-url:
nacos-user-service: http://nacos-provider
同样在父项目中记得添加关系
xml<modules>
<module>NacosProviderExample9001</module>
<module>NacosConsumerExample9101</module>
</modules>
在主启动类中一样添加对应的注解
java@EnableDiscoveryClient
@SpringBootApplication
public class AppConsumerStart {
public static void main(String[] args) {
SpringApplication.run(AppConsumerStart.class, args);
}
}
启动消费服务后一样可以在Nacos服务列表中看到注册的服务信息

上面我们创建了服务的提供者和服务的消费者,但是都只是完成了服务的 Nacos 注册,并没有串联起来。
在 Spring Cloud Alibaba 2021 版本中移除了 Ribbon(Ribbon已经停止更新维护),开始使用 loadBalancer 作为新的负载均衡器,需要我们手动添加新的依赖
xml <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
然后需要配置 RestTemplate 来实现远程调用
java@EnableDiscoveryClient
@SpringBootApplication
public class AppConsumerStart {
public static void main(String[] args) {
SpringApplication.run(AppConsumerStart.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
然后在服务调用的接口中的获取 RestTemplate 对象。并完成相关的逻辑
java@RestController
public class DemoController {
@Autowired
private RestTemplate restTemplate;
/**
* 消费者去访问具体服务,这种写法可以实现
* 配置文件和代码的分离
*/
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping(value = "consumer/nacos")
public String getDiscovery(){
System.err.println(serverURL);
return restTemplate.getForObject(serverURL+"/bobo",String.class);
}
}
说明:getForObject 方法的参数的含义
第一个参数url表示被调用的目标Rest接口位置
第二个参数是返回值类型
第三个参数是可变参数
启动服务后访问测试:http://localhost:9101/consumer/nacos , 看到返回信息说明访问成功了。
OpenFegin 是一个声明式的服务调用组件,其实现与 Ribbon 相似

然后添加相关的依赖
xml <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
然后创建对应的 Feign 接口
java@FeignClient(name = "nacos-provider")
public interface ProviderService {
@GetMapping(value = "/bobo")
public String getServerPort();
}
然后在启动类中添加 @EnableFeignClients(basePackages = "com.boge.consumer.feign") 注解。指定我们存放 Feign 接口的位置
java@EnableFeignClients(basePackages = "com.boge.consumer.feign")
@EnableDiscoveryClient
@SpringBootApplication
public class AppConsumerStart {
public static void main(String[] args) {
SpringApplication.run(AppConsumerStart.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
然后在 controller 中通过 OpenFeign 实现服务的远程调用
java@RestController
public class DemoController {
@Autowired
private ProviderService providerService;
@GetMapping(value = "consumer/nacos2")
public String getDiscovery2(){
System.err.println("--->"+serverURL);
return providerService.getServerPort();
}
}
启动服务后访问测试:http://localhost:9101/consumer/nacos2
| 服务注册与发现框架 | CAP模型 | 控制台管理 | 社区活跃度 |
|---|---|---|---|
| Eureka | AP | 支持 | 低(2.x版本闭源) |
| Zookeeper | CP | 不支持 | 中 |
| Consul | CP | 支持 | 高 |
| Nacos | AP/CP | 支持 | 高 |
分布式系统设计要考虑的三个核心要素:
以上三个特性就是 CAP原理,但是三个特性不可能同时满足,所以分布式系统设计要考虑的是在满足P(分区容错性)的前提下选择C(一致性)还是A(可用性),即:CP或AP

CP 原则属于强一致性原则,要求所有节点可以查询的数据随时都要保持一致(同步中的数据不可查询),即:若干个节点形成一个逻辑的共享区域,某一个节点更新的数据都会立即同步到其他数据节点之中,当数据同步完成后才能返回成功的结果,但是在实际的运行过程中网络故障在所难免,如果此时若干个服务节点之间无法通讯时就会出现错误,从而牺牲了以可用性原则(A),例如关系型数据库中的事务。
AP 原则属于弱一致性原则,在集群中只要有存活的节点那么所发送来的所有请求都可以得到正确的响应,在进行数据同步处理操作中即便某些节点没有成功的实现数据同步也返回成功,这样就牺牲一致性原则(C 原则)。
使用场景: 对于数据的同步一定会发出指令,但是最终的节点是否真的实现了同步,并不保证,可是却可以及时的得到数据更新成功的响应,可以应用在网络环境不是很好的场景中。
Nacos 无缝支持一些主流的开源生态,同时再阿里进行Nacos设计的时候重复的考虑到了市场化的运作(市面上大多都是以单一的实现形式为主,例如:Zookeeper使用的是 CP、而 Eureka 采用的是AP),在 Nacos 中提供了两种模式的动态切换。

- 一般来说,如果不需要储存服务界别的信息且服务实例通过 nacos-client 注册,并能够保持心跳上报,那么就可以选择 AP 模式。如 Spring Cloud 和 Dubbo,都适用于 AP 模式,AP 模式为了服务的可用性减弱了一致性,因此 AP 模式下只支持注册
临时实例。
如果需要在服务级别编辑或者储存配置信息,那么 CP 是必须的,K8S服务和DNS服务则是用于 CP 模式。CP 模式下则支持注册 持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
切换命令(默认是AP):
shellcurl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
注意
临时和持久化的区别主要在健康检查失败后的表现,持久化实例健康检查失败后会被标记成不健康,而临时实例会直接从列表中被删除。
| 配置项 | Key | 默认值 | 说明 |
|---|---|---|---|
| 服务端地址 | spring.cloud.nacos.discovery.server-addr | Nacos Server 启动监听的ip地址和端口 | |
| 服务名 | spring.cloud.nacos.discovery.service | ${spring.application.name} | 注册的服务名 |
| 权重 | spring.cloud.nacos.discovery.weight | 1 | 取值范围 1 到 100,数值越大,权重越大 |
| 网卡名 | spring.cloud.nacos.discovery.network-interface | 当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址 | |
| 注册的IP地址 | spring.cloud.nacos.discovery.ip | 优先级最高 | |
| 注册的IP地址类型 | spring.cloud.nacos.discovery.ip-type | IPv4 | 可以配置IPv4和IPv6两种类型,如果网卡同类型IP地址存在多个,希望制定特定网段地址,可使用 spring.cloud.inetutils.preferred-networks配置筛选地址 |
| 注册的端口 | spring.cloud.nacos.discovery.port | -1 | 默认情况下不用配置,会自动探测 |
| 命名空间 | spring.cloud.nacos.discovery.namespace | 常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等 | |
| AccessKey | spring.cloud.nacos.discovery.access-key | 当要上阿里云时,阿里云上面的一个云账号名 | |
| SecretKey | spring.cloud.nacos.discovery.secret-key | 当要上阿里云时,阿里云上面的一个云账号密码 | |
| Metadata | spring.cloud.nacos.discovery.metadata | 使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息 | |
| 日志文件名 | spring.cloud.nacos.discovery.log-name | ||
| 集群 | spring.cloud.nacos.discovery.cluster-name | DEFAULT | Nacos集群名称 |
| 接入点 | spring.cloud.nacos.discovery.endpoint | 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 | |
| 是否集成LoadBalancer | spring.cloud.loadbalancer.nacos.enabled | false | |
| 是否开启Nacos Watch | spring.cloud.nacos.discovery.watch.enabled | false | 可以设置成 true 来开启 watch |
| 是否开启Nacos Discovery HeartBeat | spring.cloud.nacos.discovery.heart-beat.enabled | false | 可以设置成 true 来开启 heart beat |
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!