2025-10-19
微服务与分布式
0

目录

Nacos介绍
为什么要使用注册中心?
Nacos 的下载和安装
下载
安装
Nacos 持久化配置
注册中心
创建父项目
服务提供者
服务消费者
服务调用
OpenFeign 服务调用
注册中心对比
CAP 模型
CP 原则(一致性 + 分区容错性)
AP 原则(可用性 + 分区容错性)
Nacos 的 AP/CP
Nacos 何时选择切换模式
关于 Nacos Discovery Starter 更多的配置项信息

在学习 Nacos 以前,先保证了解基本的使用与实现,带入问题去思考,在 分布式系统中为什么需要使用注册中心?Nacos 到底是 AP模型 还是 CP模型? 你将会有不一样的收获!!!

Nacos介绍

Nacos(Naming Configuration Service) 是一个易于使用的动态服务发现、配置和服务管理平台,用于构建云原生应用程序。服务发现是微服务架构中的关键组件之一,Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

官方文档:https://nacos.io/docs/next/overview/

为什么要使用注册中心?

在开始学习之前,我们来思考一个问题,那就是为什么要使用注册中心呢?

以入住酒店的前台为例子,先设想一个没有服务前台的酒店,客人入住需要自己寻找合适居住的房间,客人不知道每个房间的情况,无法确定那个房间是打扫干净可以入住,客人只能逐个房间寻找,如果遇到已经居住房客的房间一定很尴尬,显然这是不正常的情况。正常的情况是酒店会安排服务台,酒店打扫干净可以入住的房间会登记注册到服务台,这样客人来住店,只需要在前台就可以查找到可以入住的房间,这样就无需等待快速的入住。显然,服务器提供发注册和发现机制可以让房客快速找到合适的房间,快速解决入住问题。

image.png

采用微服务以后,软件微服务组件各自独立,但最终还要组合为一个整体作为一个软件系统服务于最终客户,这时软件组件之间也需要彼此通讯,彼此调用方法。

微服务架构内部发起通讯调用方法的一方成为 “服务消费者” ,提供远程方法调用的服务器称为 “服务提供者” ,往往为了提高系统性能,会提供多个服务器作为 服务提供者,此时 服务消费者 找到 服务提供者 的过程,就类似于用户在找房间的过程。为了帮助 服务消费者 快速的发现 服务提供者,在微服务框架中都会引入 注册中心

注册中心 类似于酒店的前台,提供在软件服务的注册和发现功能,服务提供者 会先在注册中心进行 注册,声明可以对外提供服务,而 服务消费者 只需要在注册中心就可以快速 发现 找到可以使用的服务,快速使用服务。注册中心实现了服务提供和服务消费的快速撮合功能。

image.png

Nacos 的下载和安装

下载

打开官网下载对应的服务版本:https://nacos.io/zh-cn/index.html

历史版本下载地址:https://github.com/alibaba/nacos/releases

image.png

生产使用官网推荐的稳定版本

官网文档推荐的网址:https://nacos.io/zh-cn/docs/quick-start.html

image.png

安装

下载之后,进行安装,流程如下:

  1. 解压以后找到bin目录

  2. 执行指令:

    Linux/Unix/Mac

    启动命令(standalone代表着单机模式运行,非集群模式):

    sh startup.sh -m standalone

    Windows

    启动命令(standalone代表着单机模式运行,非集群模式):

    startup.cmd -m standalone
  3. 运行后

image.png

  1. 访问:http://localhost:8848/nacos 进行验证,出现此界面表示已经成功启动 Nacos

image.png

  1. 默认的账号密码是:nacos/nacos,进行登录,进入到控制台,到这里就是成功开启了Nacos服务了

image.png

Nacos 持久化配置

Nacos 默认自带嵌入式数据库 derby,所以我们每次创建一个 Nacos 实例就会有一个 derby,当有多个Nacos 节点的时候,就会出现一致性问题,所以 Nacos 支持了外部数据库统一数据管理 MySQL

官网部署地址:https://nacos.io/zh-cn/docs/deployment.html

修改 application.properties 文件,取消 MySQL 的注释

image.png

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

image.png

注册中心

Nacos 可以直接提供注册中心(Eureka)+ 配置中心(Config),所以它的好处显而易见,我们在前面成功安装和启动了 Nacos 以后就可以发现 Nacos 本身就是一个小平台,它要比之前的 Eureka 更加方便,不需要我们在自己做配置。

服务发现是微服务架构中的关键组件之一。在这样的架构中,手动为每个客户端配置服务列表可能是一项艰巨的任务,并且使得动态扩展极其困难。Nacos Discovery 帮助您自动将您的服务注册到 Nacos 服务器,Nacos 服务器会跟踪服务并动态刷新服务列表。此外,Nacos Discovery 将服务实例的一些元数据,如主机、端口、健康检查 URL、主页等注册到 Nacos。

学习任何知识我们都需要从它的官方文档入手,所以我们直接来看官网给我们提供的文档:https://spring.io/projects/spring-cloud-alibaba#learn

image.png

创建父项目

首先创建一个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 的配置信息

yml
server: 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中服务列表中看到注册的服务信息

image.png

服务消费者

同样的我们再创建一个服务消费者的 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>

然后在属性文件中添加对应的配置信息

yml
server: 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服务列表中看到注册的服务信息

image.png

服务调用

上面我们创建了服务的提供者和服务的消费者,但是都只是完成了服务的 Nacos 注册,并没有串联起来。

在 Spring Cloud Alibaba 2021 版本中移除了 RibbonRibbon已经停止更新维护),开始使用 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接口位置

  1. url的第一部分是在Nacos中注册的服务提供者名称,如果多个服务提供者注册相同名称,Ribbon会自动寻找其中一个服务提供者,并且调用接口方法。这个就是负载均衡功能。
  2. url后半部是控制器的请求路径。

第二个参数是返回值类型

  1. JavaBean类型或者JavaBean数组类型,如果控制器返回的是List集合,需要使用数组类型接收。

第三个参数是可变参数

  1. 是传递给url的动态参数,使用参数时候需要在url上需要使用{1}、{2}、{3}进行参数占位,这样传递的参数就会自动替换占位符。

启动服务后访问测试:http://localhost:9101/consumer/nacos , 看到返回信息说明访问成功了。

OpenFeign 服务调用

OpenFegin 是一个声明式的服务调用组件,其实现与 Ribbon 相似

image.png

然后添加相关的依赖

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模型控制台管理社区活跃度
EurekaAP支持低(2.x版本闭源)
ZookeeperCP不支持
ConsulCP支持
NacosAP/CP支持

CAP 模型

分布式系统设计要考虑的三个核心要素:

  • 一致性(Consistency):同一时刻的同一请求的实例返回的结果相同,所有的数据要求具有强一致性(Strong Consistency)
  • 可用性(Availability):所有实例的读写请求在一定时间内可以得到正确的响应
  • 分区容错性(Partition tolerance):在网络异常(光缆断裂、设备故障、宕机)的情况下,系统仍能提供正常的服务

以上三个特性就是 CAP原理,但是三个特性不可能同时满足,所以分布式系统设计要考虑的是在满足P(分区容错性)的前提下选择C(一致性)还是A(可用性),即:CP或AP

image.png

CP 原则(一致性 + 分区容错性)

CP 原则属于强一致性原则,要求所有节点可以查询的数据随时都要保持一致(同步中的数据不可查询),即:若干个节点形成一个逻辑的共享区域,某一个节点更新的数据都会立即同步到其他数据节点之中,当数据同步完成后才能返回成功的结果,但是在实际的运行过程中网络故障在所难免,如果此时若干个服务节点之间无法通讯时就会出现错误,从而牺牲了以可用性原则(A),例如关系型数据库中的事务。

AP 原则(可用性 + 分区容错性)

AP 原则属于弱一致性原则,在集群中只要有存活的节点那么所发送来的所有请求都可以得到正确的响应,在进行数据同步处理操作中即便某些节点没有成功的实现数据同步也返回成功,这样就牺牲一致性原则(C 原则)。

使用场景: 对于数据的同步一定会发出指令,但是最终的节点是否真的实现了同步,并不保证,可是却可以及时的得到数据更新成功的响应,可以应用在网络环境不是很好的场景中。

Nacos 的 AP/CP

Nacos 无缝支持一些主流的开源生态,同时再阿里进行Nacos设计的时候重复的考虑到了市场化的运作(市面上大多都是以单一的实现形式为主,例如:Zookeeper使用的是 CP、而 Eureka 采用的是AP),在 Nacos 中提供了两种模式的动态切换。

image.png

Nacos 何时选择切换模式

  1. 一般来说,如果不需要储存服务界别的信息且服务实例通过 nacos-client 注册,并能够保持心跳上报,那么就可以选择 AP 模式。如 Spring Cloud 和 Dubbo,都适用于 AP 模式,AP 模式为了服务的可用性减弱了一致性,因此 AP 模式下只支持注册 临时实例
  1. 如果需要在服务级别编辑或者储存配置信息,那么 CP 是必须的,K8S服务和DNS服务则是用于 CP 模式。CP 模式下则支持注册 持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。

  2. 切换命令(默认是AP):

    shell
    curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

注意

临时和持久化的区别主要在健康检查失败后的表现,持久化实例健康检查失败后会被标记成不健康,而临时实例会直接从列表中被删除。

关于 Nacos Discovery Starter 更多的配置项信息

配置项Key默认值说明
服务端地址spring.cloud.nacos.discovery.server-addrNacos Server 启动监听的ip地址和端口
服务名spring.cloud.nacos.discovery.service${spring.application.name}注册的服务名
权重spring.cloud.nacos.discovery.weight1取值范围 1 到 100,数值越大,权重越大
网卡名spring.cloud.nacos.discovery.network-interface当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址
注册的IP地址spring.cloud.nacos.discovery.ip优先级最高
注册的IP地址类型spring.cloud.nacos.discovery.ip-typeIPv4可以配置IPv4和IPv6两种类型,如果网卡同类型IP地址存在多个,希望制定特定网段地址,可使用 spring.cloud.inetutils.preferred-networks配置筛选地址
注册的端口spring.cloud.nacos.discovery.port-1默认情况下不用配置,会自动探测
命名空间spring.cloud.nacos.discovery.namespace常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等
AccessKeyspring.cloud.nacos.discovery.access-key当要上阿里云时,阿里云上面的一个云账号名
SecretKeyspring.cloud.nacos.discovery.secret-key当要上阿里云时,阿里云上面的一个云账号密码
Metadataspring.cloud.nacos.discovery.metadata使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息
日志文件名spring.cloud.nacos.discovery.log-name
集群spring.cloud.nacos.discovery.cluster-nameDEFAULTNacos集群名称
接入点spring.cloud.nacos.discovery.endpoint地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址
是否集成LoadBalancerspring.cloud.loadbalancer.nacos.enabledfalse
是否开启Nacos Watchspring.cloud.nacos.discovery.watch.enabledfalse可以设置成 true 来开启 watch
是否开启Nacos Discovery HeartBeatspring.cloud.nacos.discovery.heart-beat.enabledfalse可以设置成 true 来开启 heart beat

本文作者:柳始恭

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!