服务注册发现深度拆解:Nacos vs Eureka 核心原理、架构选型与生产落地

0 阅读22分钟

微服务架构的核心是将单体应用拆分为多个独立部署、自治的服务单元,而服务之间的通信依赖于准确的服务地址管理。在动态扩缩容、实例故障漂移、多环境部署的常态化场景下,人工维护服务地址的方式完全不可行,服务注册发现组件应运而生,成为微服务架构的“神经中枢”。本文将从底层原理出发,深度拆解两款主流服务注册发现组件Eureka与Nacos的核心架构、运行机制,通过全维度对比给出清晰的选型决策指南,并提供生产级代码实践。

一、服务注册发现的核心本质与核心能力

服务注册发现的本质是服务元数据的分布式存储与动态分发系统,核心解决“服务在哪里”的问题,同时提供健康状态校验、流量管理、故障隔离的基础能力,是微服务架构中服务通信的基石。

其核心能力可拆解为6个核心环节:

  1. 服务注册:服务启动时,将自身的元数据(IP、端口、服务名、集群信息、权重、健康状态等)上报到注册中心,完成服务入驻。
  2. 服务续约:服务运行期间,定时向注册中心发送心跳,证明自身存活,避免被注册中心剔除。
  3. 服务发现:服务消费者从注册中心获取目标服务的实例列表,本地缓存后用于RPC调用,避免每次调用都请求注册中心。
  4. 健康检查:注册中心持续校验服务实例的存活状态,剔除不可用实例,保证服务列表的准确性。
  5. 服务下线:服务正常关闭时,主动通知注册中心,将自身从服务列表中移除,避免消费者调用到无效实例。
  6. 集群同步:注册中心集群节点之间的数据同步,保证多节点之间的数据一致性,实现注册中心的高可用。

二、Eureka 核心原理与架构深度解析

Eureka是Netflix开源的服务注册发现组件,是Spring Cloud Netflix生态的核心组件之一,专为AWS云环境设计,主打高可用性和弹性伸缩。目前Eureka 2.x版本已停止开发,1.x版本处于维护状态。

2.1 Eureka 核心架构

Eureka采用纯Java开发,基于对等节点的分布式架构设计,无中心化主从节点,所有节点地位平等。

架构核心组件分为两类:

  • Eureka Server:注册中心服务端,负责接收服务注册、续约、下线请求,维护服务实例列表,集群节点之间对等复制数据,对外提供查询能力。
  • Eureka Client:客户端组件,集成在服务提供者和消费者中,负责向Server完成注册、续约、下线操作,同时定时从Server拉取服务实例列表并本地缓存,支撑服务调用。

Eureka的核心设计理念是AP优先,基于CAP理论,Eureka在网络分区等异常场景下,优先保证服务的可用性,放弃强一致性。即使集群节点之间数据不一致,每个Eureka Server依然可以接收注册和查询请求,不会拒绝客户端请求,避免整个注册中心瘫痪。

2.2 Eureka 核心运行机制

2.2.1 服务注册机制

服务启动时,Eureka Client会向配置的Eureka Server发送POST请求,携带自身的全量元数据信息。Eureka Server接收到请求后,将实例信息存储到本地的ConcurrentHashMap中,以服务名作为key,实例列表作为value。同时,Server会将注册事件同步到集群中的其他peer节点,完成集群数据同步。

客户端注册失败会自动重试,默认重试3次,间隔1秒,保证注册成功率。

2.2.2 服务续约机制(心跳)

服务注册成功后,Eureka Client会每隔30秒(默认)向Eureka Server发送一次PUT请求,即心跳续约,证明自身存活。Eureka Server接收到续约请求后,会更新该实例的最后续约时间,标记实例为存活状态。

核心配置参数:

  • eureka.instance.lease-renewal-interval-in-seconds:续约间隔,默认30秒
  • eureka.instance.lease-expiration-duration-in-seconds:租约过期时间,默认90秒,即Server超过90秒未收到心跳,会判定实例失效

2.2.3 服务下线机制

服务正常关闭时,Eureka Client会向Eureka Server发送DELETE请求,通知Server下线自身实例。Server接收到请求后,将该实例从服务列表中移除,并同步下线事件到集群其他节点,同时通知订阅该服务的客户端更新实例列表。

2.2.4 服务剔除机制

Eureka Server会开启一个定时任务,每隔60秒(默认)执行一次,遍历所有服务实例,检查实例的最后续约时间是否超过租约过期时间。如果超过阈值,就将该实例标记为失效,从服务列表中剔除,避免消费者调用到不可用实例。

2.2.5 自我保护机制

自我保护机制是Eureka最核心的特性之一,也是生产环境最容易踩坑的设计。 设计背景:在网络分区场景下,Eureka Server和大量客户端之间的网络不通,导致大量实例心跳失败,此时如果Eureka Server剔除所有这些实例,会导致消费者本地缓存的实例列表被清空,无法调用任何服务,即使这些实例在网络分区的另一边是正常运行的,完全违背了Eureka的AP设计理念。

触发条件:Eureka Server会统计每分钟收到的客户端续约总数,与预期的续约总数对比,如果实际收到的续约数占预期的比例低于阈值(默认0.85,即85%),且持续15分钟,就会触发自我保护机制。

触发后的行为:Eureka Server不会再剔除任何服务实例,即使实例的租约已经过期,会保留所有的注册信息,直到网络恢复,续约比例恢复到阈值以上,才会退出自我保护机制。

2.3 Eureka 集群数据同步机制

Eureka Server集群采用对等复制(Peer-to-Peer) 架构,无主从节点,所有节点都可以接收客户端的写请求,然后将写事件同步到其他所有peer节点。

同步流程:

  1. 客户端向某个Eureka Server节点发送注册/续约/下线请求
  2. 该节点完成本地存储更新后,将事件封装成ReplicationTask,放入异步同步队列
  3. 后台线程异步将任务同步到其他所有peer节点
  4. 其他节点接收到同步任务后,更新本地的实例信息

为了避免同步死循环,Eureka Server在同步事件时会带上节点标识,如果收到的同步事件是自身发出的,会直接忽略,不会再次同步。同步过程为异步执行,集群节点之间的数据会有短暂的不一致,最终达到一致状态。

2.4 Eureka 服务发现机制

Eureka Client(消费者)会每隔30秒(默认)向Eureka Server发送GET请求,拉取全量的服务实例列表,更新本地缓存。如果本地缓存已有数据,请求会带上版本号,Server对比版本号后如果没有变化,会返回304状态码,客户端继续使用本地缓存,减少网络传输开销。

消费者发起RPC调用时,直接从本地缓存中获取目标服务的实例列表,通过负载均衡算法选择一个实例发起调用,不会每次调用都请求注册中心,保证了调用性能。

三、Nacos 核心原理与架构深度解析

Nacos是阿里巴巴开源的动态服务发现、配置管理和服务管理平台,是Spring Cloud Alibaba生态的核心组件,专为云原生微服务架构设计,提供了比Eureka更丰富的功能和更高的性能。

3.1 Nacos 核心架构

Nacos采用分层架构设计,除了服务发现能力外,还内置了配置管理、流量管理等能力,是一站式的微服务管理组件。

架构分层说明:

  1. 核心API层:对外提供HTTP/gRPC接口,接收客户端的注册、发现、配置管理等请求,2.x版本默认使用gRPC协议,大幅提升了传输性能。
  2. 业务模块层:分为Naming Service(服务发现)和Config Service(配置管理)两大核心模块,本文重点讲解Naming Service模块。
  3. 一致性协议层:提供两种一致性协议,Distro协议(AP模式)和Raft协议(CP模式),满足不同场景的一致性需求。
  4. 存储层:本地内存存储+MySQL持久化存储,内存存储保证服务实例查询的高性能,MySQL存储保证持久化数据不丢失。

Nacos的核心设计理念是AP与CP双模式支持,用户可以根据业务场景选择对应的模式,同时兼顾可用性和一致性,提供了比Eureka更灵活的选择。

3.2 Nacos 核心数据模型

Nacos采用了分级的服务元数据模型,比Eureka的扁平模型更灵活,支持多环境、多租户、多集群的精细化管理,模型层级如下:

命名空间(Namespace)→ 分组(Group)→ 服务(Service)→ 集群(Cluster)→ 实例(Instance)

每个层级的核心作用:

  1. 命名空间:最顶层的隔离单元,用于多租户、多环境隔离,比如dev、test、prod环境分别创建不同的命名空间,不同命名空间之间的数据完全隔离,默认命名空间为public。
  2. 分组:服务的二级隔离单元,用于对服务进行分组管理,比如同一个命名空间下,分为电商组、支付组、物流组,不同分组的服务名可以重复,默认分组为DEFAULT_GROUP。
  3. 服务:服务的唯一标识,对应业务中的一个微服务,比如user-service、order-service。
  4. 集群:服务的实例集群划分,比如同一个服务下,分为上海集群、北京集群,实现同机房优先调用,降低网络延迟,默认集群为DEFAULT。
  5. 实例:服务的具体节点,包含IP、端口、权重、健康状态、元数据等信息。

同时,Nacos将实例分为两种核心类型,这是与Eureka最核心的差异之一:

  • 临时实例(Ephemeral Instance) :默认类型,对应AP模式,客户端主动上报心跳,服务端不持久化实例数据,仅存储在内存中,客户端失联后会被自动剔除,适合常规的微服务实例、动态扩缩容频繁的场景。
  • 持久化实例(Persistent Instance) :对应CP模式,服务端主动探测健康状态,实例数据会持久化到MySQL中,即使客户端全部下线,实例信息依然会保留,适合数据库、中间件等固定地址的服务实例。

3.3 Nacos 核心运行机制

3.3.1 服务注册机制

服务启动时,Nacos Client会向Nacos Server发送注册请求,携带命名空间、分组、服务名、集群、实例元数据、实例类型等信息。

  • 临时实例:注册请求通过Distro协议处理,Server将实例信息存储到本地内存中,同时同步到集群中的其他节点,立即返回注册成功响应。
  • 持久化实例:注册请求通过Raft协议处理,必须集群中超过半数的节点写入成功,才会返回注册成功响应,保证强一致性,同时将实例数据持久化到MySQL中。

3.3.2 健康检查机制

Nacos提供了两种健康检查模式,分别对应不同的实例类型:

  • 客户端心跳上报模式(临时实例) :客户端每隔5秒(默认)向Server发送一次心跳请求,更新实例的最后心跳时间。Server如果15秒未收到心跳,会将实例标记为不健康;如果30秒未收到心跳,会将实例从服务列表中剔除。
  • 服务端主动探测模式(持久化实例) :Server会定时向实例发起健康检查,支持TCP、HTTP、MySQL等多种探测方式,默认TCP探测。如果探测失败,会将实例标记为不健康,不会直接剔除,直到实例恢复正常。

3.3.3 服务下线机制

  • 主动下线:服务正常关闭时,Nacos Client会向Server发送下线请求,Server将实例从服务列表中移除,同步到集群其他节点,并推送变更事件给订阅了该服务的消费者。
  • 自动下线:临时实例心跳超时30秒,Server自动剔除实例,同步集群并推送变更事件。

3.3.4 服务发现与订阅推送机制

这是Nacos和Eureka最大的性能差异点之一。Nacos采用推拉结合的订阅模式,而Eureka仅支持客户端定时拉取模式。

  • 拉取模式:客户端启动时,会向Server拉取目标服务的全量实例列表,本地缓存。
  • 推送模式:客户端会和Server建立gRPC长连接,订阅目标服务的变更事件。当服务的实例列表发生变化(注册、下线、健康状态变化)时,Server会立即通过长连接将增量变更事件推送给所有订阅的客户端,客户端实时更新本地缓存。

这种模式的优势:客户端不需要定时全量拉取,大幅减少了网络IO和CPU开销,同时服务变更的感知延迟从Eureka的30秒降低到秒级甚至毫秒级,实时性大幅提升。

3.4 Nacos 集群一致性协议

Nacos的核心竞争力之一就是支持两种一致性协议,分别对应不同的业务场景:

3.4.1 Distro协议(AP模式)

Distro是阿里自研的最终一致性协议,专为临时实例的服务发现场景设计,核心设计理念是分区负责、对等同步、最终一致。

核心原理:

  • 集群中的每个节点负责一部分服务的写入请求,根据服务名的hash值分配到对应的节点
  • 每个节点将自己负责的服务数据同步到集群中的其他所有节点,保证全集群的数据最终一致
  • 每个节点都可以处理所有的读请求,即使节点之间数据不一致,依然可以对外提供服务,保证可用性
  • 数据仅存储在内存中,不持久化到MySQL,节点重启后会从其他节点同步全量数据

适用场景:常规的微服务临时实例,需要高可用性,允许短暂的数据不一致。

3.4.2 Raft协议(CP模式)

Raft是业界通用的强一致性分布式共识协议,Nacos用它来处理持久化实例的注册请求和配置管理数据。

核心原理:

  • 集群中会选举出一个Leader节点,所有的写请求都必须发送到Leader节点,Leader节点处理后,将数据同步到Follower节点
  • 只有当集群中超过半数的Follower节点写入成功,Leader才会提交数据,返回客户端写入成功,保证强一致性
  • 如果Leader节点故障,集群会重新选举新的Leader,选举期间集群无法处理写请求,保证数据一致性,牺牲了短暂的可用性
  • 数据会持久化到MySQL中,保证节点重启后数据不丢失

适用场景:持久化实例、配置管理等需要强一致性的场景,不允许数据不一致。

四、Nacos vs Eureka 全维度核心对比

对比维度EurekaNacos
CAP模型仅支持AP模式,优先可用性,最终一致性双模式支持,临时实例AP模式,持久化实例CP模式
健康检查机制仅支持客户端主动心跳上报支持客户端心跳上报、服务端TCP/HTTP/MySQL主动探测
集群同步机制Peer-to-Peer对等异步复制,最终一致AP模式用Distro协议分区对等同步,CP模式用Raft协议强一致同步
服务订阅模式客户端定时全量拉取(默认30秒),实时性差推拉结合,gRPC长连接增量推送,实时性毫秒级
数据模型扁平模型,仅服务名+实例,隔离能力弱分级模型:命名空间→分组→服务→集群→实例,精细化多维度隔离
单机性能单机最大支持1w+服务实例,吞吐量较低2.x版本单机支持10w+服务实例,吞吐量是Eureka的10倍以上
功能丰富度仅提供基础的服务注册发现能力服务注册发现、配置管理、流量管理、权重路由、熔断降级、灰度发布等一体化能力
负载均衡能力仅依赖客户端负载均衡组件内置服务端权重配置、同机房优先、就近路由等负载均衡策略
社区活跃度1.x处于维护状态,2.x已停更,无新特性迭代阿里持续维护迭代,社区活跃,国内用户基数大,问题响应快
生态适配原生适配Spring Cloud Netflix生态原生适配Spring Cloud Alibaba、Dubbo、K8s等主流微服务生态
运维成本部署简单,仅依赖JDK,无外部依赖单机部署简单,集群持久化部署依赖MySQL,运维复杂度略高
国内适配无特殊适配,国内网络环境下镜像拉取、问题排查不便国产开源,中文文档完善,适配国内云环境,国产化支持好

五、架构选型决策指南

5.1 优先选择Eureka的场景

  1. 存量Spring Cloud Netflix技术栈项目,已经深度集成Eureka,业务稳定,无额外功能需求,不需要大规模重构。
  2. 业务场景对服务注册发现的要求仅为基础的地址管理,不需要配置管理、流量管理等额外功能,追求极简部署和运维。
  3. 部署环境资源有限,无法提供MySQL等外部依赖,仅需单JDK环境即可运行。
  4. 业务对可用性要求极高,允许短暂的服务列表不一致,完全不能接受注册中心集群不可用的场景。

5.2 优先选择Nacos的场景

  1. 新建微服务项目,技术栈选型Spring Cloud Alibaba,追求一站式的微服务解决方案,减少组件依赖。
  2. 业务需要多环境、多租户、多集群的精细化服务管理,比如大型企业级项目,多团队协作开发。
  3. 服务实例规模大,单集群实例数超过1w,需要更高的性能和更低的延迟。
  4. 业务需要一体化的配置管理能力,不想同时维护注册中心和配置中心两套组件。
  5. 业务需要精细化的流量管理能力,比如权重路由、灰度发布、同机房优先调用等。
  6. 项目有国产化适配要求,需要国产开源组件,中文文档和社区支持完善。

5.3 选型避坑核心提醒

  1. Eureka的自我保护机制在生产环境需要根据场景调整阈值,避免网络波动触发后,大量无效实例被保留,导致消费者调用失败。
  2. Nacos的实例类型选择是核心,常规微服务实例必须使用临时实例,不要使用持久化实例,否则会导致Raft协议的写性能瓶颈,同时实例下线后不会被自动剔除。
  3. Eureka集群节点数建议3-5个,过多的节点会导致集群同步压力过大,数据不一致的时间变长。
  4. Nacos集群部署时,AP模式至少3个节点,CP模式必须使用奇数个节点(3/5/7个),保证Raft协议的选举正常。

六、生产级落地代码实践

统一环境说明:

  • JDK版本:JDK 17
  • Spring Boot版本:3.2.5
  • Spring Cloud版本:2023.0.2
  • 项目管理:Maven

6.1 Eureka 落地实践

分为三个模块:eureka-server(注册中心)、eureka-provider(服务提供者)、eureka-consumer(服务消费者)

6.1.1 Eureka Server 集群搭建

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-server</name>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <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>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

spring:
  application:
    name: eureka-server
  profiles:
    active: node1

---
spring:
  config:
    activate:
      on-profile: node1
server:
  port: 8761
eureka:
  instance:
    hostname: eureka-node1
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka-node2:8762/eureka/,http://eureka-node3:8763/eureka/
  server:
    renewal-percent-threshold: 0.85
    enable-self-preservation: true

---
spring:
  config:
    activate:
      on-profile: node2
server:
  port: 8762
eureka:
  instance:
    hostname: eureka-node2
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka-node1:8761/eureka/,http://eureka-node3:8763/eureka/
  server:
    renewal-percent-threshold: 0.85
    enable-self-preservation: true

---
spring:
  config:
    activate:
      on-profile: node3
server:
  port: 8763
eureka:
  instance:
    hostname: eureka-node3
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka-node1:8761/eureka/,http://eureka-node2:8762/eureka/
  server:
    renewal-percent-threshold: 0.85
    enable-self-preservation: true

EurekaServerApplication.java

package com.jam.demo.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * Eureka注册中心服务端启动类
 *
 * @author ken
 */
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

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

}

6.1.2 Eureka 服务提供者开发

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>eureka-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-provider</name>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <mysql.version>8.4.0</mysql.version>
        <guava.version>33.2.0-jre</guava.version>
        <fastjson2.version>2.0.52</fastjson2.version>
        <springdoc.version>2.5.0</springdoc.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <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>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

spring:
  application:
    name: user-service-provider
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jam_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
server:
  port: 8080
eureka:
  instance:
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 30
    lease-expiration-duration-in-seconds: 90
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/,http://127.0.0.1:8762/eureka/,http://127.0.0.1:8763/eureka/
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
springdoc:
  swagger-ui:
    path: /swagger-ui.html
  api-docs:
    path: /v3/api-docs

MySQL建表语句

CREATE TABLE IF NOT EXISTS `t_user` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` VARCHAR(64NOT NULL COMMENT '用户名',
  `age` INT DEFAULT NULL COMMENT '年龄',
  `email` VARCHAR(128DEFAULT NULL COMMENT '邮箱',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

User.java

package com.jam.demo.provider.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;

/**
 * 用户实体类
 *
 * @author ken
 */
@Data
@TableName("t_user")
@Schema(description = "用户实体")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    @Schema(description = "用户ID", example = "1")
    private Long id;

    @Schema(description = "用户名", example = "zhangsan")
    private String username;

    @Schema(description = "年龄", example = "20")
    private Integer age;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    private String email;
}

UserMapper.java

package com.jam.demo.provider.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.provider.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * 用户Mapper接口
 *
 * @author ken
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

UserService.java

package com.jam.demo.provider.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.provider.entity.User;

import java.util.List;

/**
 * 用户服务接口
 *
 * @author ken
 */
public interface UserService extends IService<User> {

    /**
     * 根据ID查询用户
     *
     * @param id 用户ID
     * @return 用户实体
     */
    User getUserById(Long id);

    /**
     * 查询所有用户
     *
     * @return 用户列表
     */
    List<User> listAllUsers();
}

UserServiceImpl.java

package com.jam.demo.provider.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.provider.entity.User;
import com.jam.demo.provider.mapper.UserMapper;
import com.jam.demo.provider.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
 * 用户服务实现类
 *
 * @author ken
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, Userimplements UserService {

    @Override
    public User getUserById(Long id) {
        if (ObjectUtils.isEmpty(id)) {
            log.warn("查询用户失败,用户ID为空");
            return null;
        }
        return this.getById(id);
    }

    @Override
    public List<User> listAllUsers() {
        return this.list();
    }
}

UserController.java

package com.jam.demo.provider.controller;

import com.jam.demo.provider.entity.User;
import com.jam.demo.provider.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * 用户服务控制器
 *
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户相关操作接口")
public class UserController {

    private final UserService userService;

    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户", description = "传入用户ID,返回对应用户信息")
    public User getUserById(
            @Parameter(description = "用户ID", required = true, example = "1")
            @PathVariable Long id) {
        log.info("收到查询用户请求,用户ID:{}", id);
        if (ObjectUtils.isEmpty(id)) {
            log.warn("用户ID为空,查询失败");
            return null;
        }
        return userService.getUserById(id);
    }

    @GetMapping("/list")
    @Operation(summary = "查询所有用户", description = "返回所有用户列表")
    public List<User> listAllUsers() {
        log.info("收到查询所有用户请求");
        return userService.listAllUsers();
    }
}

ProviderApplication.java

package com.jam.demo.provider;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * 服务提供者启动类
 *
 * @author ken
 */
@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {

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

}

6.1.3 Eureka 服务消费者开发

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>eureka-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-consumer</name>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
        <guava.version>33.2.0-jre</guava.version>
        <fastjson2.version>2.0.52</fastjson2.version>
        <springdoc.version>2.5.0</springdoc.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <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>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

spring:
  application:
    name: order-service-consumer
server:
  port9090
eureka:
  instance:
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZonehttp://127.0.0.1:8761/eureka/,http://127.0.0.1:8762/eureka/,http://127.0.0.1:8763/eureka/
springdoc:
  swagger-ui:
    path: /swagger-ui.html
  api-docs:
    path: /v3/api-docs
feign:
  client:
    config:
      default:
        connect-timeout5000
        read-timeout10000

User.java

package com.jam.demo.consumer.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;

/**
 * 用户实体类
 *
 * @author ken
 */
@Data
@Schema(description = "用户实体")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Schema(description = "用户ID", example = "1")
    private Long id;

    @Schema(description = "用户名", example = "zhangsan")
    private String username;

    @Schema(description = "年龄", example = "20")
    private Integer age;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    private String email;
}

UserFeignClient.java

package com.jam.demo.consumer.feign;

import com.jam.demo.consumer.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

/**
 * 用户服务Feign客户端
 *
 * @author ken
 */
@FeignClient(name = "user-service-provider", path = "/user")
public interface UserFeignClient {

    @GetMapping("/{id}")
    User getUserById(@PathVariable("id") Long id);

    @GetMapping("/list")
    List<User> listAllUsers();
}

OrderController.java

package com.jam.demo.consumer.controller;

import com.jam.demo.consumer.entity.User;
import com.jam.demo.consumer.feign.UserFeignClient;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * 订单服务控制器
 *
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/order")
@RequiredArgsConstructor
@Tag(name = "订单管理", description = "订单相关操作接口")
public class OrderController {

    private final UserFeignClient userFeignClient;

    @GetMapping("/user/{id}")
    @Operation(summary = "查询订单对应用户信息", description = "传入用户ID,调用用户服务获取用户信息")
    public User getOrderUser(
            @Parameter(description = "用户ID", required = true, example = "1")
            @PathVariable Long id) {
        log.info("收到查询订单用户请求,用户ID:{}", id);
        if (ObjectUtils.isEmpty(id)) {
            log.warn("用户ID为空,查询失败");
            return null;
        }
        return userFeignClient.getUserById(id);
    }

    @GetMapping("/user/list")
    @Operation(summary = "查询所有用户列表", description = "调用用户服务获取所有用户列表")
    public List<User> listAllUsers() {
        log.info("收到查询所有用户列表请求");
        return userFeignClient.listAllUsers();
    }
}

ConsumerApplication.java

package com.jam.demo.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * 服务消费者启动类
 *
 * @author ken
 */
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {

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

}

6.2 Nacos 落地实践

分为两个模块:nacos-provider(服务提供者)、nacos-consumer(服务消费者),Nacos Server版本2.4.3

6.2.1 Nacos 服务提供者开发

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>nacos-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>nacos-provider</name>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.1.2</spring-cloud-alibaba.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <mysql.version>8.4.0</mysql.version>
        <guava.version>33.2.0-jre</guava.version>
        <fastjson2.version>2.0.52</fastjson2.version>
        <springdoc.version>2.5.0</springdoc.version>
    </properties>
    <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.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <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>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

spring:
  application:
    name: user-service
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jam_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: dev
        group: BUSINESS_GROUP
        cluster-name: SH
        ephemeral: true
        weight: 1.0
        metadata:
          version: 1.0.0
          env: dev
server:
  port: 8080
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
springdoc:
  swagger-ui:
    path: /swagger-ui.html
  api-docs:
    path: /v3/api-docs

实体类、Mapper、Service、Controller代码与Eureka服务提供者完全一致,启动类如下:

NacosProviderApplication.java

package com.jam.demo.nacos.provider;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * Nacos服务提供者启动类
 *
 * @author ken
 */
@EnableDiscoveryClient
@SpringBootApplication
public class NacosProviderApplication {

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

}

6.2.2 Nacos 服务消费者开发

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>nacos-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>nacos-consumer</name>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.1.2</spring-cloud-alibaba.version>
        <guava.version>33.2.0-jre</guava.version>
        <fastjson2.version>2.0.52</fastjson2.version>
        <springdoc.version>2.5.0</springdoc.version>
    </properties>
    <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-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <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>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr127.0.0.1:8848
        namespace: dev
        group: BUSINESS_GROUP
        cluster-name: SH
        ephemeral: true
server:
  port9090
springdoc:
  swagger-ui:
    path: /swagger-ui.html
  api-docs:
    path: /v3/api-docs
feign:
  client:
    config:
      default:
        connect-timeout5000
        read-timeout10000
  loadbalancer:
    nacos:
      enabled: true

实体类、Feign客户端、Controller代码与Eureka服务消费者完全一致,启动类如下:

NacosConsumerApplication.java

package com.jam.demo.nacos.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * Nacos服务消费者启动类
 *
 * @author ken
 */
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class NacosConsumerApplication {

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

}

七、生产环境最佳实践与避坑指南

7.1 Eureka 生产环境最佳实践

  1. 集群节点数控制在3-5个,避免过多节点导致同步压力过大,同时保证高可用。
  2. 自我保护机制的阈值根据实际业务场景调整,大规模实例集群可适当降低阈值,避免频繁触发。
  3. 核心业务可缩短续约间隔到10秒,过期时间到30秒,加快故障感知速度。
  4. 开启健康检查端点,配置eureka.client.healthcheck.enabled=true,让Eureka根据应用的健康状态更新实例状态,而非仅靠心跳。
  5. 生产环境配置合适的日志级别,避免DEBUG日志过多导致磁盘IO压力过大。
  6. 集群节点配置hosts解析,避免网络波动导致节点间通信失败。

7.2 Nacos 生产环境最佳实践

  1. 实例类型严格区分:常规微服务实例必须使用临时实例,仅固定地址的中间件、数据库使用持久化实例。
  2. 集群部署规范:AP模式至少3个节点,CP模式必须使用奇数个节点,生产环境建议3个节点起步,大规模集群建议5个节点。
  3. 持久化配置:生产环境必须使用MySQL集群作为持久化存储,避免单机存储故障导致数据丢失。
  4. 命名空间规划:严格按照环境划分命名空间,dev、test、prod完全隔离,避免跨环境调用。
  5. 健康检查配置:临时实例使用默认的心跳模式,持久化实例根据服务类型选择合适的探测方式。
  6. 权限控制:生产环境必须开启Nacos的权限控制,为不同的团队分配不同的命名空间权限,避免误操作。
  7. 性能调优:大规模实例集群,调整JVM堆内存到4G以上,同时调整Distro协议的同步参数,提升同步效率。
  8. 版本选择:生产环境必须使用稳定版,定期更新小版本修复安全漏洞。

7.3 通用避坑指南

  1. 避免注册中心和业务服务部署在同一台机器,防止业务服务的资源占用导致注册中心不可用。
  2. 生产环境必须配置注册中心集群,避免单点故障。
  3. 消费者必须本地缓存服务实例列表,同时设置合理的缓存刷新时间,保证服务变更的实时性。
  4. 服务调用必须配置超时时间、重试机制、熔断降级,避免注册中心故障导致服务调用雪崩。
  5. 注册中心的监控告警必须完善,监控集群节点的健康状态、CPU、内存、磁盘IO、请求量、实例数等指标,设置合理的告警阈值。

服务注册发现是微服务架构的核心基础设施,Eureka和Nacos都是经过生产验证的优秀组件,没有绝对的优劣之分,只有适配场景的差异。Eureka胜在极简、稳定、高可用,适合存量Netflix生态项目;Nacos胜在功能丰富、性能强劲、一站式解决方案,适合新建项目和企业级微服务架构。选择适配业务场景的组件,结合生产最佳实践,才能构建稳定、高效、高可用的微服务体系。