第四章:服务注册与发现: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 三种常见的健康检查方式
- 心跳机制:客户端主动向服务端发送心跳包。Eureka和Nacos都支持这种方式。优点是简单,缺点是客户端可能「装死」——明明业务线程卡死了,心跳线程还在正常工作。
- 服务端主动探测:注册中心主动去访问服务的健康端点。Consul默认使用这种方式。我遇到过一个问题:服务端的健康检查线程太多,把注册中心的CPU打满了。嗯,要注意控制探测频率。
- 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秒是比较合理的范围
我记得有一次线上故障,就是因为健康检查没配好,导致流量打到了已经挂掉的服务上。从那以后,我每次上线前都会检查三件事:注册中心是否正常、健康检查是否生效、服务列表是否准确。这三个点都过了,心里才踏实。