【Spring Boot 与 Spring Cloud 深度 Mape 之三】服务注册与发现:Nacos 核心实战与原理浅析

发布于:2025-04-01 ⋅ 阅读:(26) ⋅ 点赞:(0)

【Spring Boot 与 Spring Cloud 深度 Mape 之三】服务注册与发现:Nacos 核心实战与原理浅析

#SpringCloudAlibaba #Nacos #服务注册 #服务发现 #服务治理 #微服务 #SpringBoot #Java

系列衔接:在前两篇 [【深度 Mape 之一】 和 [【深度 Mape 之二】] 中,我们分别掌握了 Spring Boot 的核心应用构建能力,并理解了微服务架构的演进以及 Spring Cloud 解决分布式挑战的宏伟蓝图。现在,我们将踏入 Spring Cloud 核心组件的实战领域。本文作为系列的第三篇,将聚焦于微服务架构中的基石——服务注册与发现,并重点讲解如何使用当前主流且功能强大的 Nacos 作为注册中心,替代日渐老旧的 Eureka,并将其与 Spring Boot 应用无缝集成。

摘要:在动态的微服务环境中,服务实例的地址(IP、端口)会因部署、扩缩容而频繁变化。服务消费者如何准确、实时地找到服务提供者?这便是服务注册与发现机制要解决的核心问题。本文将深入探讨服务发现的必要性,介绍 Nacos 作为现代化服务发现解决方案的优势及其核心概念。我们将通过详细的步骤和代码示例,手把手教你如何部署 Nacos Server、如何在 Spring Boot 应用(服务提供者和服务消费者)中集成 Nacos Discovery,并最终实现基于服务名的动态调用。


本文目标

  • 深刻理解在微服务架构中引入服务注册与发现的必要性。
  • 了解 Nacos 作为服务发现组件的核心概念(Namespace, Group, Service, Instance)。
  • 掌握 Nacos Server 的单机模式部署与控制台基本使用。
  • 熟练配置 Spring Boot 应用(提供者/消费者)接入 Nacos 实现服务注册。
  • 掌握服务消费者如何通过 Nacos 发现服务实例并进行调用(使用 @LoadBalanced RestTemplate)。

一、 为何需要服务注册与发现?告别硬编码的噩梦

想象一个没有服务注册中心的微服务世界:

  • 服务 A 需要调用服务 B。服务 A 的开发者需要知道服务 B 当前部署的所有实例的 IP 地址和端口号。
  • 这些地址可能会因为重新部署、服务器故障、弹性伸缩(增加或减少实例)而改变。
  • 每次服务 B 的地址列表发生变化,服务 A 的配置都需要手动更新并重新部署。
  • 如果服务 B 有多个实例,服务 A 还需要自己实现负载均衡逻辑来分发请求。

这种 硬编码静态配置 的方式在动态、大规模的微服务环境中是完全不可行的,会导致配置维护困难、系统脆弱、无法自动适应环境变化。

服务注册与发现机制 就是为了解决这个问题:

  1. 服务注册 (Service Registration):服务提供者(如服务 B)在启动时,将自己的网络地址(IP、端口、服务名等元数据)注册到一个中心化的 服务注册中心 (Service Registry)。同时,它会定期向注册中心发送“心跳”表明自己还活着。
  2. 服务发现 (Service Discovery):服务消费者(如服务 A)在需要调用服务 B 时,向服务注册中心询问:“服务名叫 B 的可用实例有哪些?” 注册中心返回当前存活的服务 B 实例列表。
  3. 健康检查 (Health Check):注册中心会定期检查已注册服务的健康状况(或由服务实例主动上报心跳)。如果某个实例长时间没有心跳或健康检查失败,注册中心会将其从可用列表中剔除,防止消费者调用到故障实例。

通过这种机制,服务间的依赖关系从具体的网络地址解耦为对 服务名 的依赖,实现了服务的动态发现和高可用。

二、 Nacos 闪亮登场:现代化的服务发现与配置中心

Nacos (Naming and Configuration Service) 是阿里巴巴开源的一款功能丰富的平台,用于构建云原生应用。它不仅仅是一个服务注册中心,还集成了分布式配置管理动态 DNS 服务

为何选择 Nacos (相较于 Eureka)?

  • 功能更全面:同时支持服务发现和配置管理,减少了需要引入的中间件数量。
  • 更好的性能与可用性:支持 AP (最终一致性) 和 CP (强一致性) 模式切换(服务发现通常用 AP,配置管理可能需要 CP),底层通信机制更优。
  • 更友好的控制台:提供了易于使用的 Web UI,方便查看服务、配置、集群状态等。
  • 社区活跃,持续发展:阿里巴巴主导,社区活跃,迭代速度快。而 Eureka 已进入维护模式。
  • 支持多种服务类型:支持基于 API、DNS、RPC 的服务发现。

Nacos 服务发现核心概念:

  • Namespace (命名空间):用于进行租户隔离或环境隔离(如开发环境、测试环境、生产环境)。不同的 Namespace 之间的服务、配置默认是隔离的。默认 Namespace 是 public
  • Group (分组):可以将不同的服务或配置划分到同一个组内,实现逻辑上的分类。默认 Group 是 DEFAULT_GROUP
  • Service (服务):一个逻辑上的服务单元,通常对应一个微服务的 spring.application.name
  • Instance (实例):一个服务下的具体运行实例,包含 IP、端口、权重、健康状态、元数据等信息。
  • Cluster (集群):Nacos 实例可以属于某个集群,用于逻辑或物理上的划分(如杭州集群、上海集群)。默认 Cluster 是 DEFAULT
  • Health Check (健康检查):Nacos 支持多种健康检查机制(客户端上报心跳、服务端探测 TCP/HTTP 端口),以确保服务实例的可用性。

理解这些概念有助于我们后续的配置和管理。

三、 Nacos Server 部署:快速启动本地开发环境

对于本地开发和测试,我们可以快速部署一个单机版的 Nacos Server。

1. 下载 Nacos Server:

访问 Nacos 的 GitHub Release 页面:https://github.com/alibaba/nacos/releases

下载最新稳定版的 nacos-server-x.x.x.tar.gz (Linux/macOS) 或 .zip (Windows)。

2. 解压并启动:

解压下载的文件。进入 nacos/bin 目录。

  • Linux/macOS:
    # 启动单机模式 (-m standalone)
    sh startup.sh -m standalone
    
  • Windows:
    # 启动单机模式 (-m standalone)
    cmd startup.cmd -m standalone
    

启动成功后,你会看到类似 Nacos started successfully in stand alone mode. 的日志。

3. 访问 Nacos 控制台:

打开浏览器,访问 http://localhost:8848/nacos

默认用户名和密码都是 nacos

登录后,你可以在左侧菜单看到 “服务管理” -> “服务列表”,目前应该是空的。

生产环境提示:生产环境强烈建议使用集群模式部署 Nacos,并通常需要配置外部数据源(如 MySQL)来持久化数据。具体请参考 Nacos 官方文档。

四、 服务提供者:向 Nacos 注册服务

现在,我们创建一个简单的 Spring Boot 应用作为服务提供者,并将其注册到 Nacos。

1. 创建 Spring Boot 项目 (例如 nacos-provider-demo)

使用 Spring Initializr 创建一个 Maven 项目,添加 Spring Web 依赖。

2. 添加 Nacos Discovery 依赖

修改 pom.xml,引入 Spring Cloud Alibaba 的 BOM 和 Nacos Discovery Starter:

<properties>
    <java.version>1.8</java.version>
    <!-- 确保 Spring Boot 版本与 Spring Cloud Alibaba 版本兼容 -->
    <spring-boot.version>2.6.14</spring-boot.version>
    <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
     <!-- Spring Cloud 版本也需要兼容 -->
    <spring-cloud.version>2021.0.8</spring-cloud.version>
</properties>

<dependencyManagement>
    <dependencies>
        <!-- 引入 Spring Cloud BOM -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 引入 Spring Boot BOM -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-dependencies</artifactId>
             <version>${spring-boot.version}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
        <!-- 引入 Spring Cloud Alibaba BOM -->
        <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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Nacos 服务发现 Starter -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- 建议添加 Actuator 以便 Nacos 进行更准确的健康检查 -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</artifactId>
     </dependency>
</dependencies>

注意: 请根据你选择的 Spring Boot 版本,查找并使用兼容的 Spring Cloud Alibaba 和 Spring Cloud 版本。版本兼容性至关重要!

3. 配置 application.yml

src/main/resources 下创建 application.yml (或 .properties):

server:
  port: 8081 # 服务端口号

spring:
  application:
    name: nacos-provider-service # !! 服务名称,将注册到 Nacos !!
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # Nacos Server 地址
        # namespace: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # 指定命名空间 ID (可选, 默认为 public)
        # group: MY_GROUP # 指定分组 (可选, 默认为 DEFAULT_GROUP)
        # cluster-name: HZ # 指定集群名称 (可选, 默认为 DEFAULT)

# Actuator 配置 (可选,用于健康检查)
management:
  endpoints:
    web:
      exposure:
        include: 'health' # 至少暴露 health 端点
  endpoint:
    health:
      show-details: always
  • spring.application.name 是必须的,它定义了服务在 Nacos 中的名称。
  • spring.cloud.nacos.discovery.server-addr 指定 Nacos Server 的地址。

4. 创建一个简单的 REST Controller

package com.example.nacosproviderdemo;

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 ProviderController {

    @Value("${server.port}")
    private String serverPort;

    // 模拟一个简单的接口
    @GetMapping("/provider/echo/{message}")
    public Map<String, String> echo(@PathVariable String message) {
        Map<String, String> response = new HashMap<>();
        response.put("message", "Received: " + message);
        response.put("fromPort", serverPort);
        return response;
    }
}

5. 启动应用并验证注册

运行 NacosProviderDemoApplication

启动完成后,回到 Nacos 控制台 (http://localhost:8848/nacos),刷新 “服务列表”。你应该能看到名为 nacos-provider-service 的服务,并且实例数量为 1。点击服务名可以查看实例详情,包括 IP、端口、健康状态等。

五、 服务消费者:发现并调用服务

现在,我们创建另一个 Spring Boot 应用作为服务消费者,它将通过 Nacos 发现 nacos-provider-service 并调用其接口。

1. 创建 Spring Boot 项目 (例如 nacos-consumer-demo)

同样使用 Spring Initializr 创建,添加 Spring Web 依赖。

2. 添加 Nacos Discovery 和 LoadBalancer 依赖

修改 pom.xml,引入与 Provider 相同的 BOM 配置,并添加依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Nacos 服务发现 Starter -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- Spring Cloud LoadBalancer 用于客户端负载均衡 (替代 Ribbon) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

注意: 我们引入了 spring-cloud-starter-loadbalancer。虽然 Ribbon 仍然可以通过 Nacos Discovery 传递依赖进来,但 Spring Cloud 官方推荐使用 Spring Cloud LoadBalancer 作为新的客户端负载均衡器。

3. 配置 application.yml

server:
  port: 8082 # 消费者端口号

spring:
  application:
    name: nacos-consumer-service # 消费者自己的服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # Nacos Server 地址

消费者也需要配置 Nacos 地址和自己的 spring.application.name,以便自己也能被发现(如果需要)或进行管理。

4. 配置 @LoadBalanced RestTemplate

为了方便地使用服务名进行调用并自动实现负载均衡,我们需要配置一个带有 @LoadBalanced 注解的 RestTemplate Bean。

创建一个配置类:

package com.example.nacosconsumerdemo.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 // !! 核心注解:启用 Spring Cloud LoadBalancer 的能力 !!
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@LoadBalanced 注解会拦截 RestTemplate 的请求,通过 LoadBalancerClient (由 spring-cloud-starter-loadbalancer 提供) 从 Nacos 获取目标服务(根据 URL 中的服务名)的实例列表,并根据负载均衡策略(默认为轮询)选择一个实例,将请求的 URL 替换为实际的 IP 和端口,最后才真正发出 HTTP 请求。

5. 创建 Consumer Controller 调用 Provider

package com.example.nacosconsumerdemo;

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 org.springframework.web.client.RestTemplate;

import java.util.Map;

@RestController
public class ConsumerController {

    // 注入带有 @LoadBalanced 注解的 RestTemplate
    @Autowired
    private RestTemplate restTemplate;

    // 定义要调用的服务提供者名称
    private final String PROVIDER_SERVICE_URL = "http://nacos-provider-service"; // !! 使用服务名 !!

    @GetMapping("/consumer/call/echo/{message}")
    public Map<String, String> callProviderEcho(@PathVariable String message) {
        // 使用服务名 + 路径 调用提供者接口
        String requestUrl = PROVIDER_SERVICE_URL + "/provider/echo/" + message;
        System.out.println("Requesting URL: " + requestUrl);

        // 发起 GET 请求,期望返回 Map 类型
        // RestTemplate 会自动进行服务发现和负载均衡
        Map<String, String> result = restTemplate.getForObject(requestUrl, Map.class);

        System.out.println("Received response: " + result);
        return result;
    }
}

关键点:调用 URL 时,主机名部分直接使用在 Nacos 中注册的 服务名 (nacos-provider-service),而不是具体的 IP 和端口。RestTemplate (因为 @LoadBalanced) 会自动处理这一切。

6. 启动并测试

  • 确保 Nacos Server 和 nacos-provider-demo (端口 8081) 正在运行。
  • 启动 nacos-consumer-demo (端口 8082)。

访问消费者的接口:http://localhost:8082/consumer/call/echo/HelloNacos

观察 nacos-consumer-demo 的控制台输出,你应该能看到它请求了 http://nacos-provider-service/provider/echo/HelloNacos,并收到了来自 nacos-provider-demo (端口 8081) 的响应。

浏览器会显示类似:

{
  "message": "Received: HelloNacos",
  "fromPort": "8081"
}

负载均衡测试 (可选):
你可以修改 nacos-provider-demoserver.port (例如改为 8083),然后启动第二个 Provider 实例。再次访问消费者的接口多次,你会发现响应中的 fromPort 会在 8081 和 8083 之间轮流切换,证明了负载均衡在生效。

六、 Nacos 健康检查机制简介

Nacos 通过健康检查来判断服务实例是否可用。主要方式:

  1. 客户端心跳 (推荐):Nacos 客户端(集成在你的 Spring Boot 应用中)会定期向 Nacos Server 发送心跳包,告知自己还存活。如果 Server 在一定时间内(可配置)未收到心跳,则认为实例不健康。这是默认且推荐的方式。
  2. 服务端探测:Nacos Server 主动探测实例的 TCP 端口或 HTTP 接口(如 Actuator 的 /actuator/health)。这需要服务端能访问到客户端实例。

当 Spring Boot 应用集成了 spring-boot-starter-actuator 后,Nacos 客户端通常会利用 Actuator 的 /actuator/health 端点信息来上报更准确的健康状态给 Nacos Server。

七、 总结与展望

本文我们深入实践了使用 Nacos 作为服务注册中心的核心流程:

  1. 理解了服务注册与发现对于动态微服务环境的必要性。
  2. 了解了 Nacos 的核心概念并成功部署了单机版 Nacos Server。
  3. 通过 spring-cloud-starter-alibaba-nacos-discovery,将服务提供者注册到了 Nacos。
  4. 配置了 @LoadBalanced RestTemplate,使服务消费者能够基于 服务名 动态发现并调用提供者,同时实现了客户端负载均衡。

至此,我们解决了微服务间“如何找到彼此”的问题。但这仅仅是服务调用的第一步。直接使用 RestTemplate 拼接 URL 仍然有些繁琐,且缺乏类型安全。

在下一篇文章【深度 Mape 之四】中,我们将学习更优雅、更强大的服务间调用方式——使用声明式 REST 客户端 OpenFeign,它将使远程调用如同调用本地方法一样简单,敬请期待!


你在使用 Nacos 或其他注册中心时遇到过什么问题?或者对服务发现有什么更深的理解?欢迎在评论区分享讨论!