微网官方网站泉州哪里建设网站
目录
1. 背景:微服务架构的演进与核心挑战
🔍 详解架构演进与服务发现
1️⃣ 单体架构(Monolithic Architecture)
2️⃣ 垂直架构(Vertical Architecture)
3️⃣ 分布式架构(Distributed Architecture)
4️⃣ SOA架构(Service-Oriented Architecture)
5️⃣ 微服务架构(Microservices Architecture)
⚖️ Nacos 与 Eureka 的简单对比
💎 总结与服务发现演进规律
微服务带来的问题
2. Nacos 是什么?其核心特点
3. Nacos的核心组件与架构
3.1.架构组件解析
3.2 核心工作流程分析
3.3 总结
4.Nacos 与 Spring Boot 集成实战
环境准备
第一步:创建父工程(可选)
第二步:创建服务提供者 (Service Provider)
1. 添加依赖
2. 配置应用
3. 创建启动类
4. 创建服务接口
第三步:创建服务消费者 (Service Consumer)
1. 添加依赖
2. 配置应用
3. 创建启动类
4. 使用RestTemplate调用服务
5. 配置LoadBalanced的RestTemplate
6. 使用OpenFeign调用服务(推荐方式)
第四步:测试整个流程
1. 启动Nacos Server
2. 启动服务提供者
3. 启动服务消费者
4. 验证服务注册
5. 测试服务调用
6. 测试负载均衡(可选)
总结
5. 健康检查机制
6.深入解析Nacos源码
一、 服务注册的时机:何时触发注册?
二、 服务注册的原理:如何完成注册?
三、 服务地址动态感知原理:如何实时更新?
四、 服务提供者地址查询:如何获取地址列表?
核心原理总结
7. 注意事项与最佳实践
8. 总结
1. 背景:微服务架构的演进与核心挑战
🔍 详解架构演进与服务发现
1️⃣ 单体架构(Monolithic Architecture)
在企业发展初期,所有功能模块(如用户管理、商品管理、订单管理、支付管理等)都集中在一个应用中,部署到一个Web服务器中。
- 特点:结构简单,开发部署成本低;所有模块耦合在一起。
- 服务发现:基本无概念。组件间通过内部方法调用(Local Method Invocation)进行通信,不存在网络层面的发现问题。
- 缺点:可扩展性差,一个模块出问题可能导致整个应用不可用,技术栈迭代困难。
2️⃣ 垂直架构(Vertical Architecture)
随着访问量增大,将单体应用按业务拆分成多个互不相关的应用(如电商交易系统、后台管理系统、CMS系统)并独立部署。
- 特点:根据访问情况针对性优化和水平扩展;系统间相对独立,无法互相调用,存在重复业务代码。
- 服务发现:初期形态。通常使用 DNS 或 Nginx反向代理 进行简单的静态配置(手动维护服务的IP和端口列表)。服务变动需人工修改配置并重启,难以应对动态变化。
3️⃣ 分布式架构(Distributed Architecture)
为了复用重复的业务代码,将系统整体拆分为服务层(封装业务逻辑)和表现层(处理页面交互)。
- 特点:提高了代码复用性;但系统间耦合度变高,调用关系复杂。
- 服务发现:萌芽阶段。开始出现服务注册表(Service Registry)的早期概念,但通常集成在分布式框架中,功能可能比较简单。
4️⃣ SOA架构(Service-Oriented Architecture)
随着服务增多,为解决容量评估、小服务资源浪费等问题,增加了统一的调度中心(如ESB企业服务总线或注册中心)来管理服务依赖和调用关系。
- 特点:使用注册中心解决服务依赖和调用关系的自动注册与发现;但各服务间存在依赖关系,可能引发雪崩,测试部署困难。
- 服务发现:中心化注册中心。服务提供者向注册中心注册自身地址,消费者通过查询注册中心来发现服务地址。引入了心跳机制等初步的健康检查。但ESB可能较重,存在单点瓶颈。
5️⃣ 微服务架构(Microservices Architecture)
将应用彻底拆分为一个个小的、可独立部署的微服务,每个服务有自己的数据库。服务间通过API(如REST, RPC)协作。
- 特点:服务彻底拆分,独立部署升级,利于扩展维护;但带来了开发成本高、服务容错、数据一致性和分布式事务等新的复杂性。
- 服务发现:成熟的注册中心模式。这是服务发现机制真正成熟和全面应用的阶段。
- 核心组件:注册中心(如 Nacos, Eureka, Consul)成为基础设施。
- 工作流程:
- 服务注销:实例正常关闭时向注册中心注销;异常下线时,注册中心通过心跳超时自动剔除。
- 服务调用:消费者根据负载均衡策略(如轮询、随机、权重)直接调用提供者。
- 服务发现:消费者向注册中心查询健康的服务实例列表。
- 服务续期:定期向注册中心发送心跳以证明健康。
- 服务注册:服务实例启动时向注册中心注册元数据(服务名、IP、端口、健康状态)。
-
- 高级特性:现代注册中心(如Nacos)还集成了配置管理、权重规则、流量管理、集群容灾等功能,成为一个综合的服务治理平台。
演化过程
⚖️ Nacos 与 Eureka 的简单对比
在Spring Cloud生态中,Nacos和Eureka是两大知名注册中心。Nacos凭借其更全面的能力,已成为许多新项目的首选。
特性 | Netflix Eureka | Alibaba Nacos |
服务发现 | 支持 | 支持 |
配置管理 | 不支持,需配合Spring Cloud Config等 | 原生支持,集服务发现与配置管理于一体 |
一致性协议 | AP (保证可用性和分区容错性) | AP + CP 模式切换 (根据场景选择一致性模型,灵活性更高) |
健康检查 | 客户端心跳 | 多种方式 (心跳、TCP、HTTP、MySQL等) |
易用性 | 部署简单 | 提供友好的管理控制台,支持命名空间、分组管理、服务元数据、权重调节等 |
社区生态 | Netflix已停止维护,进入维护模式 | 阿里巴巴开源,活跃度高,与Spring Cloud Alibaba生态整合紧密 |
Nacos的核心优势在于其一体化的服务与配置管理能力 ,以及更丰富的功能和更活跃的社区。
💎 总结与服务发现演进规律
从架构演进图中可以看出,服务发现的演进遵循着清晰的规律:
- 服务粒度:从粗犷的单体 → 垂直拆分 → 分布式服务 → SOA服务 → 细粒度的微服务。
- 发现机制:从无到有,从静态配置(DNS/Nginx) → 中心化注册(注册中心)。
- 动态性:从手动操作 → 自动注册与发现,实时性和准确性极大提升。
- 健康机制:从无 → 简单心跳 → 全面的健康检查(如Nacos的多模式检查)。
- 功能集成:从单一服务发现 → 集配置管理、流量治理于一身的服务治理平台(如Nacos)。
Nacos 这类现代注册中心,通过其动态服务发现、配置管理和服务健康监测等核心功能,有效地解决了微服务架构中服务实例的动态性和服务消费的依赖性问题,成为了微服务架构的“中枢神经系统”。
微服务带来的问题
随着互联网业务的飞速发展,单体应用(Monolithic Application)在可维护性、扩展性和部署敏捷性上已无法满足需求。微服务架构(Microservices Architecture)应运而生,它将一个复杂的应用拆分成一系列小而自治的服务。
然而,微服务在带来巨大灵活性的同时,也引入了新的复杂性,其中首要解决的就是 服务发现(Service Discovery)问题:
- 服务实例的动态性:微服务实例的网络地址(IP和端口)是动态分配的,尤其是在云原生和容器化(如K8s)环境中,实例可能随时被创建或销毁。
- 服务消费的依赖性:服务消费者(Consumer)如何准确地找到并调用健康的服务提供者(Provider)?
2. Nacos 是什么?其核心特点
为了解决这一问题,服务注册中心 成为了微服务架构的核心基础设施。
它扮演着“电话簿”的角色,所有服务实例在启动时向注册中心注册自己的元数据,下线时注销,消费者通过查询注册中心来发现可用的服务实例。
在 Spring Cloud 生态的早期,Netflix Eureka 是事实标准的注册中心。然而,阿里巴巴开源的 Nacos(Naming and Configuration Service)以其更强大的动态服务发现与配置管理能力,迅速成为了新一代的明星组件。
Nacos 致力于帮助您发现、配置和管理微服务。它提供了一组简单易用的特性集,快速实现动态服务发现、服务配置、服务元数据及流量管理。
其核心特点包括:
- 服务发现与服务健康检查:支持基于 DNS 和 RPC 的服务发现,内置多种健康检查策略(如TCP、HTTP、MySQL等)。
- 动态配置管理:以中心化、外部化和动态化的方式管理所有环境的配置,消除了配置变更时重新部署应用和服务的需要。
- 动态 DNS 服务:支持加权路由,让您更容易地实现中间层负载均衡、更灵活的路由策略。
- 服务和元数据管理:支持从微服务平台建设的视角管理数据中心的所有服务及元数据。
- AP与CP模式切换:基于 Raft 协议保证集群间数据强一致性(CP),同时也支持可用性更高的分布式协议(AP),可根据场景在服务注册(AP)和配置管理(CP)模式间灵活切换。
与 Eureka 相比,Nacos 的优势在于其**“服务注册发现”与“配置中心”** 功能的合一,减少了运维复杂度,并提供了更丰富的功能和完善的控制台UI。
3. Nacos的核心组件与架构
Nacos 作为一个平台,其服务端(Server)、客户端(Client) 以及控制台(Console) 之间的关系和内部核心模块。
3.1.架构组件解析
我们将架构分为四个主要部分:客户端层、接入层、服务端核心层 和 控制台层。
1. 客户端层 (Client Side)
- Provider APP / Consumer APP: 服务提供者与服务消费者应用程序。它们通常是基于 Spring Boot、Dubbo 等框架构建的微服务。
- Nacos client/sidecar: 嵌入在应用程序中的 SDK 或以 Sidecar 模式(如 Nacos Mesh)部署的代理。它的核心职责是:
- 与服务端通信,完成服务注册(Provider)和服务发现(Consumer)。
- 拉取和管理动态配置。
- 维护服务的本地缓存,提高性能并在服务器宕机时提供一定的容错能力。
2. 接入层 (Access Layer - Name Server)
- Name Server (dns/vip/address-server): 这不是一个独立的服务,而是一个抽象的概念,指代客户端如何找到 Nacos 服务器集群的接入点。
- 作用:实现 Nacos Server 集群的负载均衡和高可用。客户端不需要知道所有 Server 的地址。
- 实现方式:
- Address Server: Nacos 官方提供的一个简单 HTTP 查询服务,客户端通过访问一个固定的地址获取可用的 Nacos Server 列表。
- VIP (Virtual IP): 使用一个虚拟 IP,背后是一个 Nacos Server 集群。
- DNS: 通过一个域名解析到多个 Nacos Server 的 IP 地址。
3. 服务端核心层 (Server Core - Nacos Core)
这是 Nacos 的“大脑”,运行在 nacos-server
中,包含三大核心服务和一个一致性协议。
- Naming Service (命名服务):
- 实现服务注册与发现功能的核心模块。负责处理服务的注册、注销、健康检查以及服务实例列表的维护与推送。
- Config Service (配置服务):
- 实现动态配置管理功能的核心模块。负责配置的发布、删除、变更历史、灰度发布以及配置变更的推送。
- Consistency Protocol (一致性协议):
- 这是 Nacos 最核心的底层机制,它决定了数据如何在不同 Server 节点间同步,直接影响了 CAP 特性。
- sync renew/rdbms based: 图示也提到了基于数据库(如MySQL)的同步方式。Nacos 支持将数据持久化到外部数据库,以实现集群节点间的数据共享和持久化。
- Raft (CP): 用于配置数据和领导者选举的同步。这是一个强一致性的协议,保证了配置信息的准确性和一致性(CP),确保在集群环境下配置不会错乱。
- Distro (私有协议, AP): 用于服务数据的同步。这是一个异步最终一致性的协议,保证了高可用和分区容错性(AP),确保服务注册和发现的性能与可用性。
- 这是 Nacos 最核心的底层机制,它决定了数据如何在不同 Server 节点间同步,直接影响了 CAP 特性。
- OpenAPI: Nacos Server 对外暴露的统一的 HTTP RESTful 接口。无论是 Console 还是 Client,最终所有操作都通过调用这些 API 完成。这也意味着你可以绕过客户端,直接用
curl
等工具管理服务和配置。
4. 控制台层 (Console Layer)
- Nacos Console: 一个基于 Web 的可视化管理界面,它通过调用 OpenAPI 来工作。
- User Console / Admin Console / Customized Console: 这表示控制台可以服务于不同角色的用户(普通用户、管理员),并且支持自定义和扩展,方便企业集成到自己的运维体系中。
5. 集群模式 (Multi-Datacenter)
- Multi-Datacenter Nacos Cluster: Nacos 支持多数据中心(机房)的集群部署模式。这意味着你可以在一地部署一个 Nacos 集群,在另一地部署另一个集群,它们之间可以通过同步工具进行数据同步,从而实现异地容灾和单元化路由。
3.2 核心工作流程分析
1. 服务注册流程 (Provider → Server)
- 1. Provider APP 启动。
- 2. 其内嵌的 Nacos Client 通过Name Server(DNS/VIP)查询到一个可用的 Nacos Server 地址。
- 3. Client 通过 OpenAPI(HTTP)向 Server 的 Naming Service 发送注册请求,包含自身 IP、端口、服务名等元数据。
- 4. Naming Service 将实例信息写入底层存储(内存或数据库),并通过 Distro 协议 将数据异步同步到集群内的其他节点。
- 5. Provider 会定期向 Server 发送心跳(也是通过 OpenAPI),以维持注册状态。
2. 服务发现与订阅流程 (Consumer → Server)
- 1. Consumer APP 启动,准备调用 user-service。
- 2. 其内嵌的 Nacos Client 通过Name Server 查询到一个可用的 Nacos Server。
- 3. Client 通过 OpenAPI 向 Naming Service 查询 user-service 的实例列表,并订阅该服务的变更。
- 4. Server 返回当前的实例列表,Client 将其缓存在本地。
- 5. 当 user-service 的实例发生变更(如上线、下线)时,Naming Service 会通过 “长轮询 (Long-Polling)” 或 UDP 推送的方式,主动将最新的列表推送给订阅了的 Consumer Client,实现实时动态更新。
3. 配置管理流程 (APP ↔ Server)
- 1. 运维人员通过 Nacos Console 发布一条新配置(dataId: my-app.yml)。
- 2. Console 通过 OpenAPI 调用 Config Service。
- 3. Config Service 将配置信息持久化到存储中,并通过 Raft 协议 在集群内进行强一致同步。
- 4. Client 在启动时,或监听配置变更时,会通过 OpenAPI 从 Config Service 拉取配置。
- 5. 当配置发生变更时,Config Service 会通知监听该配置的 Client,Client 会主动拉取最新配置,从而应用生效。
3.3 总结
Nacos 的架构设计精巧且层次分明:
- 分离设计:将服务 (Naming) 和配置 (Config) 两大核心功能在架构上分离,但在部署上合一,简化了运维。
- 灵活的一致性:底层根据场景(服务 vs 配置)采用不同的一致性协议(Distro-AP / Raft-CP),在 CAP 之间做出了最佳权衡。
- 开放生态:提供标准的 OpenAPI 和 Console,易于集成、扩展和二次开发。
- 高可用保障:通过 Name Server 解耦客户端与服务器集群,支持多数据中心部署,确保了平台自身的高可用和容灾能力。
这张图完美地体现了 Nacos 如何作为一个中心化的信息枢纽,高效、可靠地管理着微服务架构中最核心的服务元数据与配置数据。
4.Nacos 与 Spring Boot 集成实战
创建一个完整的服务提供者和服务消费者示例,演示它们如何通过 Nacos 实现服务注册与发现。
环境准备
- 确保已安装 JDK 1.8+
- 安装 Maven 3.2+
- 运行 Nacos Server(可从官网下载或使用 Docker)
第一步:创建父工程(可选)
创建一个 Maven 父工程来管理两个子模块。
<?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"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>nacos-demo</artifactId><version>1.0.0</version><packaging>pom</packaging><modules><module>service-provider</module><module>service-consumer</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.0</version><relativePath/></parent><properties><java.version>1.8</java.version><spring-cloud.version>2021.0.3</spring-cloud.version><spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.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-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
</project>
第二步:创建服务提供者 (Service Provider)
1. 添加依赖
<!-- service-provider/pom.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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>nacos-demo</artifactId><groupId>com.example</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>service-provider</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency></dependencies>
</project>
2. 配置应用
# service-provider/src/main/resources/application.yml
server:port: 8081spring:application:name: service-provider # 服务名称cloud:nacos:discovery:server-addr: localhost:8848 # Nacos服务器地址namespace: public # 命名空间,默认publicgroup: DEFAULT_GROUP # 分组,默认DEFAULT_GROUP# 自定义元数据,可用于灰度发布等场景
metadata:version: v1region: hangzhou
3. 创建启动类
// service-provider/src/main/java/com/example/ProviderApplication.java
package com.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication
@EnableDiscoveryClient // 开启服务注册与发现功能
public class ProviderApplication {public static void main(String[] args) {SpringApplication.run(ProviderApplication.class, args);}
}
4. 创建服务接口
// service-provider/src/main/java/com/example/controller/HelloController.java
package com.example.controller;import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@RestController
public class HelloController {@Value("${server.port}")private String port;@Value("${spring.application.name}")private String appName;@GetMapping("/hello/{name}")public Map<String, Object> hello(@PathVariable String name) {Map<String, Object> result = new HashMap<>();result.put("code", 200);result.put("msg", "success");result.put("data", String.format("Hello, %s! from %s on port %s", name, appName, port));return result;}@GetMapping("/info")public Map<String, Object> info() {Map<String, Object> result = new HashMap<>();result.put("appName", appName);result.put("port", port);return result;}
}
第三步:创建服务消费者 (Service Consumer)
1. 添加依赖
<!-- service-consumer/pom.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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>nacos-demo</artifactId><groupId>com.example</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>service-consumer</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies>
</project>
2. 配置应用
# service-consumer/src/main/resources/application.yml
server:port: 8082spring:application:name: service-consumercloud:nacos:discovery:server-addr: localhost:8848namespace: publicgroup: DEFAULT_GROUP# 开启Feign客户端日志
feign:client:config:default:loggerLevel: full# 设置Ribbon超时时间(如果使用Ribbon)
ribbon:ReadTimeout: 5000ConnectTimeout: 5000
3. 创建启动类
// service-consumer/src/main/java/com/example/ConsumerApplication.java
package com.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 开启Feign客户端
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}
}
4. 使用RestTemplate调用服务
// service-consumer/src/main/java/com/example/controller/RestTemplateController.java
package com.example.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;import java.util.List;@RestController
public class RestTemplateController {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;@Autowiredprivate LoadBalancerClient loadBalancerClient;// 方式1:使用LoadBalancerClient手动选择实例@GetMapping("/call/hello/{name}")public String callHello(@PathVariable String name) {// 使用LoadBalancerClient选择服务实例ServiceInstance instance = loadBalancerClient.choose("service-provider");if (instance == null) {return "No available service instance";}String url = String.format("http://%s:%s/hello/%s", instance.getHost(), instance.getPort(), name);System.out.println("Request URL: " + url);return restTemplate.getForObject(url, String.class);}// 方式2:使用@LoadBalanced注解的RestTemplate(推荐)@GetMapping("/call/hello2/{name}")public String callHello2(@PathVariable String name) {// 直接使用服务名进行调用return restTemplate.getForObject("http://service-provider/hello/" + name, String.class);}// 查看服务实例信息@GetMapping("/instances")public List<ServiceInstance> instances() {return discoveryClient.getInstances("service-provider");}
}
5. 配置LoadBalanced的RestTemplate
// service-consumer/src/main/java/com/example/config/RestTemplateConfig.java
package com.example.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class RestTemplateConfig {@Bean@LoadBalanced // 开启负载均衡public RestTemplate restTemplate() {return new RestTemplate();}
}
6. 使用OpenFeign调用服务(推荐方式)
// service-consumer/src/main/java/com/example/feign/ProviderFeignClient.java
package com.example.feign;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;import java.util.Map;@FeignClient(name = "service-provider") // 指定要调用的服务名
public interface ProviderFeignClient {@GetMapping("/hello/{name}")Map<String, Object> hello(@PathVariable("name") String name);@GetMapping("/info")Map<String, Object> info();
}
// service-consumer/src/main/java/com/example/controller/FeignController.java
package com.example.controller;import com.example.feign.ProviderFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;@RestController
public class FeignController {@Autowiredprivate ProviderFeignClient providerFeignClient;@GetMapping("/feign/hello/{name}")public Map<String, Object> feignHello(@PathVariable String name) {return providerFeignClient.hello(name);}@GetMapping("/feign/info")public Map<String, Object> feignInfo() {return providerFeignClient.info();}
}
第四步:测试整个流程
1. 启动Nacos Server
# 进入Nacos的bin目录
startup.cmd -m standalone # Windows
sh startup.sh -m standalone # Linux/Mac
2. 启动服务提供者
cd service-provider
mvn spring-boot:run
3. 启动服务消费者
cd service-consumer
mvn spring-boot:run
4. 验证服务注册
访问Nacos控制台:http://localhost:8848/nacos (默认账号/密码:nacos/nacos)
在"服务管理" -> "服务列表"中应该能看到两个服务:
- service-provider
- service-consumer
5. 测试服务调用
- 测试RestTemplate调用:
http://localhost:8082/call/hello/World
http://localhost:8082/call/hello2/World
- 测试Feign调用:
http://localhost:8082/feign/hello/World
http://localhost:8082/feign/info
查看服务实例:
- http://localhost:8082/instances
6. 测试负载均衡(可选)
要测试负载均衡,可以启动多个服务提供者实例:
# 第一个实例(端口8081)
java -jar target/service-provider-1.0.0.jar --server.port=8081# 第二个实例(端口8083,需要修改application.yml或使用命令行参数)
java -jar target/service-provider-1.0.0.jar --server.port=8083
然后多次调用消费者接口,观察请求被分发到不同的提供者实例。
总结
通过这个实战示例,我们完成了:
- 服务注册:服务提供者启动后自动注册到Nacos Server
- 服务发现:服务消费者通过Nacos发现可用的服务提供者
- 服务调用:
- 使用
RestTemplate
+@LoadBalanced
实现负载均衡调用 - 使用
OpenFeign
声明式HTTP客户端实现更简洁的调用
- 使用
- 负载均衡:通过Spring Cloud LoadBalancer实现了客户端负载均衡
这种架构的优势:
- 服务解耦:服务之间不直接依赖IP和端口,通过服务名进行调用
- 动态扩展:可以轻松增加或减少服务实例,消费者会自动感知
- 故障恢复:当某个服务实例不可用时,会自动从服务列表中移除
- 负载均衡:请求会自动分发到多个服务实例,提高系统吞吐量
您可以根据实际需求,在此基础上添加配置中心、熔断器、网关等更多功能。
5. 健康检查机制
Nacos 客户端与服务器之间通过心跳机制来维持健康状态,这是保证服务发现实时性的关键。
- 客户端行为:微服务实例启动注册后,会定期(默认为5秒)向 Nacos Server 发送一次心跳,心跳中包涵服务名、IP、端口等元数据。
- 服务器行为:Nacos Server 收到心跳后,会更新该实例的最后心跳时间。
- 健康判断:Nacos Server 会检查每个实例的最后心跳时间。如果超过一定时间(默认为15秒)没有收到心跳,则将该实例标记为不健康。如果超过更长时间(默认为30秒)仍未收到心跳,则直接删除该实例,并从服务列表中剔除。
这种机制保证了服务列表的“最终一致性”。即使某个 Provider 实例宕机,Consumer 最多会在30秒内感知到,后续的请求就不会再被发往这个故障实例。
Nacos 还支持扩展的健康检查方式,如 HTTP
检查(向服务的一个特定端点,如 /actuator/health
发送请求)和 MySQL
检查等。
6.深入解析Nacos源码
从服务注册时机、注册原理、动态感知和地址查询四个方面,深入 Nacos Client 与 Server 的交互细节。
一、 服务注册的时机:何时触发注册?
服务注册的触发并非在应用启动的最后,而是在应用生命周期的早期。其核心时机是:应用上下文准备就绪,但尚未对外提供服务之前。
源码追踪路径:
- 起点:
@EnableDiscoveryClient
这个注解是入口,它最终导入了EnableDiscoveryClientImportSelector
,但其核心作用是激活 Spring Cloud 的通用服务发现机制。 - 自动装配:
NacosServiceRegistryAutoConfiguration
Spring Cloud Alibaba 通过自动配置类来装配所需的 Bean。在这个类中,会创建NacosAutoServiceRegistration
实例。 - 关键类:
NacosAutoServiceRegistration
它继承自 Spring Cloud 通用的AbstractAutoServiceRegistration
。这个类是理解注册时机的关键。 - 核心方法:
onApplicationEvent(WebServerInitializedEvent event)
// 位于 AbstractAutoServiceRegistration 类中
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
// 1. 确保端口等信息就绪
bind(event);
}protected void bind(WebServerInitializedEvent event) {// ... 省略其他逻辑 ...// 2. 核心:调用 start() 方法start();
}protected void start() {// ... 省略其他逻辑 ...// 3. 最终调用 register() 方法完成注册register();
}
原理分析:
-
- 当内嵌的 Web 服务器(如 Tomcat、Netty)完成初始化并成功监听端口后,Spring 会发布一个
WebServerInitializedEvent
事件。 NacosAutoServiceRegistration
作为一个监听器,会捕获到这个事件。- 这表明应用已经准备好了自己的服务端口等关键信息,此时触发注册,才能将完整的服务元数据(包括IP和端口)发送给 Nacos Server。
- 当内嵌的 Web 服务器(如 Tomcat、Netty)完成初始化并成功监听端口后,Spring 会发布一个
结论:服务注册发生在 Web 服务器初始化完毕之后,确保 Nacos Server 收到的注册信息是准确和可用的。
二、 服务注册的原理:如何完成注册?
注册动作由 NacosAutoServiceRegistration
委托给 NacosServiceRegistry
完成。
源码追踪路径:
- 执行注册:
NacosServiceRegistry.register()
public void register(Registration registration) {
// ... 参数校验 ...
// 获取 Nacos 的 NamingService
NamingService namingService = namingService();
// 将 Spring Cloud 的 Registration 对象属性转换为 Nacos 的 Instance 对象
Instance instance = getNacosInstanceFromRegistration(registration);
try {// 2. 核心:调用 NamingService 进行注册namingService.registerInstance(serviceId, instance);
} catch (Exception e) {// ... 异常处理 ...
}
}
- Nacos Client API:
NamingService.registerInstance()
这是nacos-client
SDK 中的接口。我们通常使用的实现类是NacosNamingService
。 - 核心实现:
NacosNamingService.registerInstance()
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {// ... 参数处理,默认分组为 "DEFAULT_GROUP" ...// 3. 核心:通过 BeatReactor 发送心跳?不,这里是直接注册serverProxy.registerService(serviceName, groupName, instance);
}
- 与服务器交互:
NamingProxy.registerService()
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {// ... 日志和参数准备 ...// 4. 最终发起 HTTP 请求到 Nacos Server// 请求路径:/nacos/v1/ns/instance// 请求方法:POST// 参数:serviceName, ip, port, clusterName, weight, metadata 等final Map<String, String> params = new HashMap<String, String>(8);// ... 构建参数字典 ...reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}
原理分析:
-
- 客户端将服务信息(服务名、分组、IP、端口、集群名、元数据等)封装成一个
Instance
对象。 - 通过 HTTP POST 请求调用 Nacos Server 提供的 RESTful API:
/nacos/v1/ns/instance
。 - Nacos Server 接收到请求后,会将这个实例信息存储到其内置的注册表(一个双层的 ConcurrentMap)中。
- 客户端将服务信息(服务名、分组、IP、端口、集群名、元数据等)封装成一个
结论:服务注册本质是客户端通过一次 HTTP API 调用,将自身实例信息上报到服务器端进行存储。
三、 服务地址动态感知原理:如何实时更新?
这是 Nacos 最核心的特性之一,其原理是 客户端定时轮询 + 服务器端阻塞推送(Long-Polling) 的结合。
源码追踪路径:
- 起点:服务订阅当消费者启动时,会通过
NamingService.subscribe()
方法订阅它关心的服务。例如,OpenFeign 在第一次调用时会触发订阅。 - 核心类:
HostReactor
在nacos-client
中,这个类负责维护服务实例信息,是动态感知的核心。 - 更新服务列表:
HostReactor.getServiceInfo()
public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {// ... 参数检查 ...// 1. 首先从本地缓存 serviceInfoMap 中获取ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);if (null == serviceObj) {// 缓存不存在,则立即从服务器获取一次serviceObj = new ServiceInfo(serviceName, clusters);// 2. 将空 ServiceInfo 放入缓存,避免重复请求serviceInfoMap.put(serviceObj.getKey(), serviceObj);// 3. 立即更新updatingMap.put(serviceObj.getKey(), new Object());updateServiceNow(serviceName, clusters);updatingMap.remove(serviceObj.getKey());}// 4. 调度定时更新任务(核心:长轮询)scheduleUpdateIfAbsent(serviceName, clusters);return serviceInfoMap.get(serviceObj.getKey());
}
- 长轮询任务:
UpdateTask
scheduleUpdateIfAbsent
方法会为每个服务创建一个UpdateTask
并提交到线程池中执行。
// UpdateTask 的 run 方法
public void run() {try {// 获取最新的服务信息ServiceInfo serviceObj = hostReactor.getServiceInfo(serviceName, clusters);// ... 计算延迟时间 ...// 核心:再次调度自己,实现定时循环hostReactor.scheduleUpdate(serviceName, clusters, delay);} catch (Exception e) {// ... 异常处理 ...}
}
- 获取更新:
hostReactor.updateServiceNow()
->serverProxy.queryList()
最终,获取服务列表的请求会调用到NamingProxy.queryList()
方法。
public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly) throws NacosException {// ... 构建参数 ...// 关键参数:udpPort(客户端UDP端口,用于服务器推送) 和 healthyOnlyfinal Map<String, String> params = new HashMap<String, String>(8);params.put("namespaceId", namespaceId);params.put("serviceName", serviceName);params.put("clusters", clusters);params.put("udpPort", String.valueOf(udpPort)); // 告诉Server我的UDP端口params.put("clientIP", NetUtils.localIP());params.put("healthyOnly", String.valueOf(healthyOnly));// 发起 HTTP GET 请求,并设置超时时间(默认为30s)return reqApi(UtilAndComs.nacosUrlBase + "/instance/list", params, HttpMethod.GET);
}
原理分析(长轮询):
-
- 客户端发起一个 HTTP 查询请求到 Nacos Server,询问某服务列表是否有变化。
- Nacos Server 接收到请求后,会检查该服务的实例信息是否发生过变更。
- 如果一直没有变更:服务器会持有这个请求连接不立即返回,而是将其加入一个队列中“挂起”一段时间(比如29.5秒,小于客户端的30秒超时)。
- 如果立即有变更:服务器立即返回最新的服务列表数据。
- 在“挂起”期间,只要服务实例发生任何变更(注册、下线、元数据更改),服务器会立即找到所有相关的挂起请求,并响应变更数据。
- 如果挂起期间一直无变更,服务器会在超时前返回一个没有任何变化的响应。
- 客户端收到响应(无论有无变化)后,处理数据更新本地缓存,并立即再次发起下一个查询请求,如此循环往复。
UDP 辅助推送(增强实时性):
-
- 为了进一步提升感知的实时性,Nacos 还使用了 UDP 作为辅助。
- 客户端在查询请求中会带上自己的 UDP 端口号(
udpPort
)。 - 当 Nacos Server 感知到服务变更时,在通知完所有挂起的 HTTP 请求的同时,还会向所有订阅该服务的客户端发送一个 UDP 数据包。
- 客户端收到 UDP 包后,会立即触发一次
updateServiceNow
,主动拉取最新服务列表,从而在毫秒级内感知到变化。
结论:动态感知通过 “客户端长轮询(Pull) + 服务器端UDP推送(Push)” 的混合模式实现,既保证了实时性,又保证了可靠性,避免了客户端无限的无效轮询。
四、 服务提供者地址查询:如何获取地址列表?
地址查询是动态感知的前提,消费者需要先获取到地址列表。
- 缓存优先:所有从服务器获取到的服务列表信息,都会缓存在客户端的
HostReactor.serviceInfoMap
中。任何查询操作都首先访问本地缓存,这是保证高性能和容错(即使Nacos Server短暂不可用)的关键。 - 首次查询与强制刷新:
-
- 首次查询:当消费者需要调用某个服务(如
user-service
)时,会调用getServiceInfo
。由于本地缓存没有,会立即执行updateServiceNow
,同步阻塞地从服务器拉取一次最新数据。 - 强制刷新:在某些情况下(如收到Server的UDP推送),会调用
refreshOnly
方法,异步地触发updateServiceNow
来强制刷新缓存。
- 首次查询:当消费者需要调用某个服务(如
- 负载均衡:获取到健康的
List<Instance>
后,负载均衡器(如 Ribbon 或 Spring Cloud LoadBalancer)会介入,根据配置的策略(如轮询、随机、权重)从中选择一个实例实例进行调用。
总结流程:OpenFeign调用
-> Ribbon/LB获取服务名
-> 调用NamingService.getInstances()
-> HostReactor.getServiceInfo()
-> 【先读缓存】 -> 【无则同步拉取】 -> 【有则启动长轮询任务】 ->返回实例列表 -> LB选择实例
-> HttpClient发起网络调用
。
核心原理总结
机制 | 实现方式 | 核心类/组件 | 优点 |
服务注册 | 应用启动后(WebServer就绪事件),通过HTTP POST将实例信息发送到Server |
, | 时机准确,简单可靠 |
服务心跳 | 客户端定时(5s)通过HTTP PUT发送心跳,维持Server端实例的健康状态 |
, | 维持租约,判断健康 |
服务发现 | 客户端长轮询(HTTP GET挂起≤30s) + 服务器端UDP推送 |
, , | 兼顾实时性与效率,避免频繁轮询 |
本地缓存 | 客户端内存 缓存所有订阅的服务列表 |
| 高性能、高可用(容错) |
通过这份源码级的剖析,我们可以看到 Nacos 在服务注册与发现的设计上非常精巧,尤其是其长轮询+推送的混合模式,有效地解决了服务动态感知的实时性和服务端压力之间的平衡问题,这正是其强大功能和高性能的基石。
7. 注意事项与最佳实践
- 版本兼容性:Spring Cloud Alibaba、Spring Cloud 和 Spring Boot 三者版本有严格的对应关系。错误搭配会导致各种意想不到的问题。务必参照官方发布的**版本说明 Wiki** 进行选型。
- 生产环境集群部署:绝对不要在生产环境使用单机模式的 Nacos Server。务必遵循官方建议,至少部署一个3节点的集群,并使用 MySQL 作为持久化存储(默认内嵌Derby数据库无法满足集群需求)。
- 命名空间(Namespace)与分组(Group):善用这两个概念来实现环境的隔离(如dev、test、prod)和服务的逻辑分组,避免服务错乱调用。
- 保护 Nacos Server:Nacos Server 是系统的核心,其本身的高可用和安全性至关重要。建议将其部署在内部网络,并通过防火墙策略限制访问。同时,启用 Nacos 的认证授权功能,防止未授权的服务随意注册或拉取配置。
- 客户端容错与缓存:Nacos Client 会在本地缓存服务列表。即使短暂的 Nacos Server 网络抖动或不可用,客户端通常也能基于本地缓存继续工作,具备一定的容错能力。
- 优雅下线:在应用停机时,应通过
@PreDestroy
注解或监听ContextClosedEvent
事件,主动调用nacosDiscoveryManager.shutdown()
向 Nacos Server 执行注销操作,实现服务的优雅下线,避免存在短暂的无效服务调用。
8. 总结
Nacos 作为一个功能强大且易于使用的动态服务发现和配置管理平台,已经成为了 Spring Cloud 微服务架构中注册中心的优选方案。它以其二合一的功能、友好控制台、灵活的扩展性和强大的性能,极大地简化了微服务体系的治理复杂度。
通过与 Spring Boot 的无缝集成,开发者可以以极低的成本享受到企业级的服务发现能力。理解其核心原理、健康检查机制并遵循生产环境的最佳实践,将帮助您构建出更加稳定、健壮和高效的微服务系统。