第四章:服务注册与发现:Eureka原理与架构、Consul vs Nacos vs Zookeeper对比、健康检查机制

服务注册与发现,说白了就是微服务的「通讯录」。

没有它,你的服务之间就像一群没有手机的人,谁也找不到谁。我刚开始做微服务那会儿,觉得这东西可有可无,直接写死IP地址不就行了?结果上线第一天就被打脸——扩缩容的时候,运维同事差点没把我骂死。

4.1 Eureka:Netflix的经典之作

Eureka是Netflix开源的服务注册中心,也是Spring Cloud早期的标配。我个人习惯把它比作「小区物业」——每个服务进来先登记,离开时注销,其他服务想知道谁在,直接查物业的登记表就行。

4.1.1 Eureka的核心架构

Eureka分为两个角色:Eureka Server(注册中心)和Eureka Client(服务提供者/消费者)。

  • 服务提供者:启动时向Server注册自己的IP、端口、服务名
  • 服务消费者:从Server拉取服务列表,然后根据负载均衡策略调用
  • 心跳机制:Client每30秒向Server发送一次心跳,证明自己还活着

关键点:Eureka采用的是AP原则(可用性+分区容错性),牺牲了强一致性。这意味着你查到的服务列表可能不是最新的,但系统不会因为网络分区而崩溃。

为什么会这样?Netflix的设计哲学是:在分布式系统中,网络故障是常态,与其让整个系统不可用,不如容忍短暂的数据不一致。我在项目中遇到过几次网络抖动,Eureka集群依然能正常工作,而Zookeeper那边已经罢工了——嗯,这就是AP和CP的区别。

4.1.2 Eureka的自我保护机制

这是Eureka最容易被误解的特性。当短时间内大量服务实例丢失心跳时,Eureka不会立即把它们踢掉,而是进入自我保护模式。

你想想看,如果因为网络波动导致所有服务都被注销了,那整个系统就瘫痪了。Eureka的做法是:宁可保留可能已经挂掉的服务,也不让系统完全不可用

避坑指南:我曾经在生产环境遇到过一个问题——某个服务确实挂了,但Eureka因为自我保护机制一直保留着它的注册信息,导致调用方一直往死节点发请求。解决方案是:在测试环境关闭自我保护,生产环境根据实际情况调整阈值。

# 关闭自我保护(测试环境推荐)
eureka.server.enable-self-preservation=false

# 调整心跳阈值(生产环境)
eureka.server.renewal-percent-threshold=0.85

4.2 Consul vs Nacos vs Zookeeper:三强争霸

这三个东西经常被拿来比较。我直接说结论:没有最好的,只有最合适的

特性 Eureka Consul Nacos Zookeeper
CAP原则 AP CP AP/CP可切换 CP
健康检查 客户端心跳 服务端主动探测 心跳+主动探测 Session过期
一致性协议 无(最终一致) Raft Raft/Distro ZAB
配置管理 不支持 支持(Key-Value) 支持(强项) 支持(需二次开发)
多数据中心 不支持 支持 支持 不支持
社区活跃度 已停止维护 中等 高(阿里主导) 高(Apache)

4.2.1 我的选型建议

  • 如果你还在用Spring Cloud Netflix:Eureka虽然停更了,但老项目跑得稳就别动它。我有个项目跑了三年没出过问题。
  • 如果你需要强一致性:选Consul或Zookeeper。比如金融场景,服务列表必须准确无误。
  • 如果你想要一站式解决方案:Nacos是首选。注册中心+配置中心二合一,省得你维护两套系统。
  • 如果你追求轻量级:Consul的二进制文件只有几十兆,部署极其简单。

个人经验:我最近两个新项目都用了Nacos。原因很简单——配置管理太香了。以前用Eureka+Spring Cloud Config,配置变更要重启服务,现在Nacos支持动态刷新,改完配置秒级生效。

4.3 健康检查机制:服务的「体检报告」

健康检查是服务注册与发现的灵魂。没有它,注册中心就是个摆设。

4.3.1 三种常见的健康检查方式

  1. 心跳机制:客户端主动向服务端发送心跳包。Eureka和Nacos都支持这种方式。优点是简单,缺点是客户端可能「装死」——明明业务线程卡死了,心跳线程还在正常工作。
  2. 服务端主动探测:注册中心主动去访问服务的健康端点。Consul默认使用这种方式。我遇到过一个问题:服务端的健康检查线程太多,把注册中心的CPU打满了。嗯,要注意控制探测频率。
  3. Session过期:Zookeeper的方式。客户端和ZK保持长连接,连接断了就认为服务挂了。这种方式最实时,但网络抖动时容易误判。

4.3.2 实战中的健康检查配置

以Nacos为例,我一般会这样配置健康检查:

# application.yml
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        # 心跳间隔(毫秒)
        heart-beat-interval: 5000
        # 心跳超时(毫秒)
        heart-beat-timeout: 15000
        # 实例删除的超时时间(毫秒)
        ip-delete-timeout: 30000

我曾经踩过的坑:有一次我把心跳间隔设成了1秒,结果Nacos服务端的CPU直接飙到90%。原因是每个服务实例每秒都要发送心跳包,100个实例就是每秒100个请求。后来我改成了5秒,CPU降到10%以下。别把心跳频率设得太高,没必要。

4.3.3 自定义健康检查端点

很多时候,默认的健康检查不够用。比如你的服务依赖数据库,数据库挂了但服务进程还在,这时候注册中心应该把服务标记为不健康。

解决方案是自定义健康检查端点:

@Component
public class DatabaseHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        try {
            // 检查数据库连接
            boolean isHealthy = checkDatabaseConnection();
            if (isHealthy) {
                return Health.up().build();
            } else {
                return Health.down().withDetail("database", "连接失败").build();
            }
        } catch (Exception e) {
            return Health.down(e).build();
        }
    }
}

这样配置后,Consul或Nacos的健康检查就会调用这个端点。数据库挂了,服务自动从注册中心摘除——调用方就不会再往这个节点发请求了。

4.4 总结一下

服务注册与发现,说白了就是微服务的「基础设施」。选型的时候别盲目跟风,要根据自己的业务场景来。

  • 追求简单稳定?用Eureka(老项目)或Nacos(新项目)
  • 追求强一致性?用Consul或Zookeeper
  • 健康检查一定要自定义,别用默认的
  • 心跳频率别设太低,5-10秒是比较合理的范围

我记得有一次线上故障,就是因为健康检查没配好,导致流量打到了已经挂掉的服务上。从那以后,我每次上线前都会检查三件事:注册中心是否正常、健康检查是否生效、服务列表是否准确。这三个点都过了,心里才踏实。