注册中心选型风云:为什么选择 Nacos 而不是 Eureka

一、 引言:从 Eureka 的"停更"说起

微服务架构的核心痛点在于:服务实例是动态不稳定的

在很长一段时间里,Eureka 是我们的首选。它简单、粗暴,奉行 AP(可用性)至上主义。但是,随着 Eureka 2.0 开源计划的搁置,以及微服务架构向 Kubernetes (K8s) 云原生环境的迁移,Eureka 暴露出了明显的短板:

  1. 更新延迟大:Eureka 的三级缓存机制导致服务下线感知慢,极端情况下可能需要分钟级才能感知故障。

  2. 功能单一:仅作注册中心,配置中心还得另搭 Spring Cloud Config + Bus + RabbitMQ,运维极其痛苦。

Nacos (Dynamic Naming and Configuration Service) 的出现,直接进行了一次降维打击。它最大的杀手锏在于:它不选边站,它全都要(支持 AP 和 CP 切换)

二、 深度理论:CAP 的权衡艺术

CAP 定理(Consistency, Availability, Partition Tolerance)是分布式系统的铁律。P(分区容错)是必选项,我们只能在 C 和 A 之间权衡。

1. 为什么 Eureka 只能是 AP?

Eureka 的各个节点是对等的(Peer-to-Peer)。

  • 场景:节点 A 收到注册请求,它会先更新自己,然后异步复制给节点 B。

  • 问题 :如果网络断开,A 和 B 数据可能不一致。但在微服务调用中,偶尔拿到一个脏数据(例如调用了一个正在关闭的节点),客户端通常有重试机制(Retry)兜底,所以可用性(A)比一致性(C)更重要

2. 为什么 Zookeeper 只能是 CP?

Zookeeper 采用 ZAB 协议(类似 Raft)。

  • 场景:它必须选出一个 Leader。只有 Leader 能处理写请求。

  • 问题 :如果 Leader 挂了,集群进入选举期(30s ~ 120s)。这期间整个系统无法进行服务注册!对于分秒必争的电商交易系统,这是不可接受的灾难。

3. Nacos 的"双模"智慧

Nacos 引入了 临时实例 (Ephemeral)持久实例 (Persistent) 的概念,从而实现了 CAP 的混用。

|-------------|-------------------|---------|----------------------------|-----------------------|
| 模式 | 协议 | 数据存储 | 适用场景 | 关键特征 |
| AP (临时) | Distro (阿里自研) | 内存 | Spring Cloud 微服务, K8s Pods | 心跳检测,超时即剔除,访问速度快 |
| CP (持久) | Raft (强一致共识) | 磁盘 + 内存 | MySQL, Redis, 数据库主从节点 | 服务端探测,异常仅标记不健康,数据绝不丢失 |


三、 实战案例:电商大促系统

1. 业务架构分析

系统分为两类核心服务:

  1. 交易链路服务(Order-Service, Product-Service):部署在 K8s 上,大促时会瞬间从 10 个节点扩容到 1000 个节点。IP 随时变。

  2. 核心组件服务(Payment-Gateway, DB-proxy):通常部署在物理机或固定 IP 的虚拟机上,很少变动,但要求绝对稳定,不能把请求发给坏掉的节点。

2. 痛点重现

  • 如果全用 Eureka (AP):Payment-Gateway 挂了一个节点,Eureka 还没感知到,订单服务还在疯狂往挂掉的网关发请求,导致支付失败率飙升。

  • 如果全用 Zookeeper (CP):大促高峰期,某个机房网络抖动,ZK 重新选举,导致成千上万个 Product-Service 无法注册,整个平台瘫痪。

3. Nacos 的混合解决方案

我们可以在同一个 Nacos 集群中,针对不同的服务配置不同的模式。

服务 A:订单服务 (Order-Service) -> AP 模式

我们需要极高的吞吐量和极快的扩容感知。

application.yml 配置:

java 复制代码
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.1.100:8848
        # 设置为临时实例(默认值),开启 AP 模式
        ephemeral: true

效果 :使用 Nacos 的 Distro 协议

  • Order-Service 启动时,向 Nacos 任意节点发送 HTTP/gRPC 注册。

  • 该 Nacos 节点无需等待其他节点确认,直接返回"注册成功"。

  • 数据会在 Nacos 节点间异步同步。

服务 B:支付网关 (Payment-Gateway) -> CP 模式

我们需要强一致性,宁可报错也不要调用错误的节点。

application.yml 配置:

java 复制代码
spring:
  application:
    name: payment-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.1.100:8848
        # 【关键】设置为持久实例,开启 CP 模式
        ephemeral: false

效果 :使用 Nacos 的 Raft 协议

  • Payment-Gateway 注册时,请求会被转发给 Nacos 集群的 Leader。

  • Leader 将日志同步给 Follower,大多数节点写盘成功后,才返回"注册成功"。

  • 如果 Payment-Gateway 宕机,Nacos 不会直接删除它,而是标记为"不健康",保留其元数据,直到人工介入或节点恢复。

四、 核心架构图解

五、 源码探秘:Nacos 是如何实现自动切换的?

面试时如果能说出这一节,绝对是 P7/P8 级别的回答。

在 Nacos Server 端源码中,核心入口在 ServiceManager。当客户端发起注册请求(POST /instance)时,服务端会根据 ephemeral 字段分流。

1. 核心接口 ConsistencyService

Nacos 定义了一个一致性服务接口,有两个主要实现类:

  • DistroConsistencyServiceImpl: 对应 AP 模式。

  • RaftConsistencyServiceImpl: 对应 CP 模式。

java 复制代码
// com.alibaba.nacos.naming.core.ServiceManager

public void registerInstance(String namespaceId, String serviceName, Instance instance) {
    // 1. 根据 serviceName 获取服务定义,如果没有则创建
    createServiceIfAbsent(namespaceId, serviceName, instance.isEphemeral());
    
    Service service = getService(namespaceId, serviceName);
    
    // 2. 检查当前服务实例是否与之前定义的模式冲突
    if (service.isEphemeral() != instance.isEphemeral()) {
        throw new NacosException("你的实例模式变了!不允许从 AP 突然变 CP");
    }

    // 3. 路由到不同的一致性协议处理器
    if (instance.isEphemeral()) {
        // AP 模式:放入内存 map,使用 Distro 协议异步同步
        distroConsistencyService.put(key, instance);
    } else {
        // CP 模式:通过 Raft 协议写入,涉及磁盘 IO 和 Leader 确认
        raftConsistencyService.put(key, instance);
    }
}

3. Distro 协议的巧妙设计 (AP)

Nacos 的 Distro 协议不仅仅是简单的异步复制,它还引入了数据分片的概念:

  • Nacos 集群中,每个节点负责一部分服务的写操作(类似于 Hash 环)。

  • 如果我不负责这个服务,但我收到了写请求,我会处理,然后同步给负责的节点。

  • 新节点加入:新节点启动时,会自动找其他节点拉取全量数据,保证最终一致性。

监听服务变动

java 复制代码
@Component
@Slf4j
public class ServiceListListener implements CommandLineRunner {

    @Autowired
    private NacosServiceManager nacosServiceManager;

    @Autowired
    private NacosDiscoveryProperties properties;

    @Override
    public void run(String... args) throws Exception {
        // 获取 Nacos 原生 NamingService
        NamingService namingService = nacosServiceManager.getNamingService(properties.getNacosProperties());

        // 订阅 "netty-server" 服务的变化
        // 这是 Nacos 的推拉结合机制:
        // 1. 客户端定时 Pull
        // 2. 服务端有变化发送 UDP Push 触发回调
        namingService.subscribe("netty-server", event -> {
            if (event instanceof NamingEvent) {
                NamingEvent namingEvent = (NamingEvent) event;
                List<Instance> instances = namingEvent.getInstances();
                
                log.info("【服务变更】检测到 netty-server 实例列表变化,当前实例数: {}", instances.size());
                
                // 遍历新列表
                for (Instance instance : instances) {
                    if (instance.isHealthy()) {
                        log.info("可用实例: {}:{}", instance.getIp(), instance.getPort());
                        // TODO: 在这里执行:建立新连接、移除旧连接等逻辑
                        // updateConnectionPool(instances);
                    }
                }
            }
        });
    }
}
java 复制代码
@Component
public class GrayRule {
    
    @Autowired
    private DiscoveryClient discoveryClient;

    public ServiceInstance chooseInstance(String serviceName) {
        // 1. 获取所有实例
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
        
        // 2. 筛选逻辑:假设当前请求头里带有 gray=true,只找 v2.0 的实例
        // 真实场景通常配合 Spring Cloud Gateway Filter 实现
        List<ServiceInstance> grayInstances = instances.stream()
            .filter(instance -> {
                String version = instance.getMetadata().get("version");
                return "v2.0".equals(version);
            })
            .collect(Collectors.toList());

        // 3. 如果有灰度实例,从中随机选一个
        if (!grayInstances.isEmpty()) {
            return grayInstances.get(new Random().nextInt(grayInstances.size()));
        }
        
        // 4. 降级:没有灰度实例,返回普通实例
        return instances.get(0);
    }
}

六、 进阶功能:Nacos 作为配置中心的"热更新"

在案例中,大促期间我们需要动态调整日志级别 或者降级开关

1. 传统方式 vs Nacos 方式

  • 传统:修改 Git -> Webhook -> Config Server -> Bus -> MQ -> 微服务。链路太长,容易断。

  • Nacos:控制台修改 -> 客户端感知。链路极短。

2. 案例

User-Service 中需要动态控制"注册功能开关":

java 复制代码
@RestController
@RequestMapping("/user")
@RefreshScope // Spring Cloud Context 注解,核心!
public class UserController {

    // 从 Nacos 配置中心读取值,默认值为 true
    @Value("${user.register.enabled:true}")
    private boolean registerEnabled;

    @PostMapping("/register")
    public String register(@RequestBody UserDto user) {
        if (!registerEnabled) {
            return "当前注册功能已关闭,请稍后再试(服务降级中)";
        }
        // 业务逻辑...
        return "注册成功";
    }
}

3. 配置中心原理:长轮询 (Long Polling)

Nacos 客户端通过客户端长轮询 (Long Polling)实时感知配置变化的

  1. 客户端发起 HTTP 请求给 Nacos Server,询问配置有没有变。

  2. Server 端如果不立即返回,而是将请求挂起(Hold 住)30秒。

    • 如果在 30秒内,配置变了,Server 立马返回结果(类似 Push 的效果)。

    • 如果 30秒到了,配置没变,返回空(类似 Pull 的效果)。

  3. 客户端收到响应后,再次发起请求,周而复始。

这种方式结合了 Push 的实时性和 Pull 的轻量级,非常巧妙。


七、 总结与选型建议

|-------------|--------------|-----------|----------------------------------|
| 维度 | Eureka | Zookeeper | Nacos |
| 一致性协议 | 无 (P2P) | ZAB (CP) | Distro (AP) / Raft (CP) |
| 性能 | 中 (HTTP 短轮询) | 中 (TCP) | 高 (Nacos2.0 引入 gRPC) |
| K8s 亲和性 | 差 | 一般 | 极好 (支持 K8s Service 同步) |
| 功能丰富度 | 仅注册 | 仅注册(KV) | 注册 + 配置 + 流量管理 |
| 社区活跃度 | 停止维护 | 稳定 | 极高 (Spring Cloud Alibaba 核心) |

相关推荐
鸽鸽程序猿11 小时前
【JavaEE】【SpringCloud】注册中心_nacos
java·spring cloud·java-ee
递归尽头是星辰13 小时前
Spring Cloud Alibaba 核心理论体系:Nacos、Sentinel、Seata深度解析
spring cloud·nacos·sentinel·seata·微服务治理
lpfasd12313 小时前
springcloud docker 部署问题排查与解决方案
spring·spring cloud·docker
蓝眸少年CY15 小时前
(第七篇)spring cloud之Hystrix断路器
spring·spring cloud·hystrix
蓝眸少年CY17 小时前
(第八篇)spring cloud之zuul路由网关
后端·spring·spring cloud
码出财富1 天前
SpringBoot 内置的 20 个高效工具类
java·spring boot·spring cloud·java-ee
daladongba1 天前
Spring Cloud Gateway
java·spring cloud·gateway
梁bk2 天前
[spring cloud] Seata分布式事务管理
分布式·spring·spring cloud
张二狗和苗翠花2 天前
Spring Cloud Nacos + @RefreshScope + @Value实现配置项动态刷新
spring·spring cloud
MoFe12 天前
【Docker】windows系统wsl如何操作DOCKER
云原生·eureka