一、Eureka宕机让我们全站瘫痪2019年双十一后的凌晨3点我接到告警电话订单服务大面积超时。登上服务器一看Eureka Server的进程不见了。查看日志发现是内存泄漏导致OOM崩溃。虽然Eureka有客户端缓存机制但问题在于我们的Eureka Client配置的registryFetchIntervalSeconds30s缓存有效期只有30秒过期后客户端需要重新拉取Eureka Server挂了拉取失败本地缓存变空所有服务发现调用返回空列表RPC调用开始大面积失败更悲剧的是我们的熔断器配置了当实例数为0时直接熔断导致整个服务链路瞬间熔断用户下单全部失败。故障复盘时我们意识到Eureka的AP模型虽然保证了可用性但牺牲了一致性。当Eureka Server宕机后客户端缓存的过期策略反而成了故障扩散的原因。从那以后我们开始重新评估注册中心的选型最终迁移到了Nacos。二、注册中心对比┌─────────────────────────────────────────────────────────────────┐ │ 注册中心对比 │ │ │ │ 特性 │ Eureka │ Nacos │ Consul │ ZooKeeper │ │ ───────────────────────────────────────────────────────────── │ │ CAP │ AP │ AP/CP │ CP │ CP │ │ 健康检查 │ 客户端心跳 │ 心跳TCP │ AgentHTTP │ 会话 │ │ 配置中心 │ 否 │ 是 │ 是(KV) │ 否 │ │ 雪崩保护 │ 是 │ 是 │ 否 │ 否 │ │ 自动注销 │ 否(等过期) │ 是 │ 是 │ 是 │ │ 一致性协议 │ 无 │ Distro/Raft│ Raft │ ZAB │ │ K8s集成 │ 否 │ 是 │ 是 │ 否 │ │ 管理界面 │ 简陋 │ 完善 │ 完善 │ 无 │ │ 社区活跃 │ 停更 │ 活跃 │ 活跃 │ 活跃 │ │ │ └──────────────────────────────────────────────────────────────────┘2.1 Eureka的优缺点优点Spring Cloud原生支持开箱即用AP模型保证可用性简单易用学习成本低缺点已停止维护2.x版本官方宣布不再开源只支持AP不适合强一致性场景管理界面简陋排查问题困难健康检查依赖客户端心跳不够精准2.2 Nacos的优缺点优点同时支持AP和CP模式可切换集成配置中心一石二鸟管理界面完善服务治理方便阿里开源社区活跃国内生态好支持DNS协议K8s集成友好缺点功能多意味着复杂度高配置项繁多需要理解透彻集群部署相对复杂2.3 Consul的优缺点优点CP模型强一致性自带服务网格Connect支持多数据中心K8s集成原生支持缺点需要部署Agent运维成本高国内社区相对小众配置中心能力较弱只有KV2.4 ZooKeeper的优缺点优点CP模型强一致性成熟稳定久经考验Hadoop/Kafka等大数据生态默认选择缺点不是专门的服务发现组件使用复杂无管理界面调试困难性能较差不适合大规模服务三、Nacos实战3.1 服务注册/** * Nacos服务注册配置 */ConfigurationpublicclassNacosRegisterConfig{BeanLoadBalancedpublicRestTemplaterestTemplate(){returnnewRestTemplate();}}# bootstrap.yml spring:application:name:order-service cloud:nacos:discovery:server-addr:nacos:8848namespace:production group:DEFAULT_GROUPcluster-name:HZweight:1metadata:version:v2 region:hangzhou3.2 服务发现/** * 服务发现使用 */ServiceSlf4jpublicclassServiceDiscoveryService{AutowiredprivateDiscoveryClientdiscoveryClient;AutowiredprivateNacosServiceManagernacosServiceManager;/** * 获取服务实例 */publicListServiceInstancegetInstances(StringserviceId){returndiscoveryClient.getInstances(serviceId);}/** * 获取所有服务 */publicListStringgetServices(){returndiscoveryClient.getServices();}/** * 订阅服务变更 */publicvoidsubscribe(StringserviceId,EventListenerlistener){nacosServiceManager.getNamingService().subscribe(serviceId,event-{if(eventinstanceofNamingEvent){NamingEventnamingEvent(NamingEvent)event;log.info(服务变更: service{}, instances{},serviceId,namingEvent.getInstances().size());}});}}3.3 Nacos配置中心/** * Nacos配置中心使用 */ServiceSlf4jpublicclassNacosConfigService{/** * 动态配置 */NacosValue(value${order.max-retry:3},autoRefreshedtrue)privateintmaxRetry;/** * 配置变更监听 */NacosConfigListener(dataIdorder-service.properties,groupIdDEFAULT_GROUP)publicvoidonConfigChange(StringnewConfig){log.info(配置变更: {},newConfig);// 刷新本地配置}}3.4 集群部署配置# Nacos集群配置 cluster.conf192.168.1.1:8848192.168.1.2:8848192.168.1.3:8848# 使用MySQL存储生产环境必须spring.datasource.platformmysqldb.num1db.url.0jdbc:mysql://mysql:3306/nacos?characterEncodingutf8connectTimeout1000socketTimeout3000autoReconnecttrueuseUnicodetrueuseSSLfalseserverTimezoneUTCdb.user.0nacosdb.password.0nacos四、踩坑实录坑1临时实例vs持久实例问题Nacos默认使用临时实例ephemeraltrue靠心跳保活。服务假死时心跳线程可能还在运行注册中心不会剔除流量打到假死实例上。踩坑场景我们有个服务因为死锁卡住了但心跳线程还在Nacos认为服务健康继续把流量打过去导致大量请求超时。解决方案spring:cloud:nacos:discovery:# 配置健康检查heart-beat-interval:5000# 心跳间隔5秒heart-beat-timeout:15000# 心跳超时15秒# 或者使用持久实例ephemeral:false坑2注册信息不完整问题只注册了IP和端口没注册权重、版本等元数据导致灰度路由失败。踩坑场景我们要做灰度发布按版本号路由流量结果发现服务实例没有注册version元数据灰度规则全部失效。解决方案spring:cloud:nacos:discovery:metadata:version:v2.0.0region:hangzhouenv:grayweight:80坑3注册中心集群脑裂问题Nacos集群网络分区出现两个Leader服务注册信息不一致。踩坑场景机房网络抖动Nacos集群出现脑裂不同客户端看到的服务列表不一致导致请求时好时坏。解决方案# 切换到CP模式强一致性 nacos.core.protocol.raft.data.enabledtrue # 增加选举超时时间 nacos.core.protocol.raft.data.election-timeout-ms5000坑4服务下线不优雅问题服务直接kill注册中心等心跳超时才剔除期间请求还在打到已下线的实例。踩坑场景发布时直接kill进程客户端还在调用下线实例报大量连接拒绝错误。解决方案BeanpublicGracefulShutdowngracefulShutdown(){returnnewGracefulShutdown();}// 或使用Spring Boot的优雅关闭server:shutdown:graceful spring:lifecycle:timeout-per-shutdown-phase:30s坑5Nacos写入压力问题1000个服务同时上线Nacos写入压力大出现写入延迟。踩坑场景凌晨批量发布几百个服务同时启动注册Nacos扛不住注册延迟长达30秒导致服务启动后找不到依赖。解决方案分批上线错峰启动增加Nacos集群节点调整写入批处理参数坑6命名空间隔离不彻底问题以为用了namespace就能完全隔离结果配置还是串了。踩坑场景我们用namespace区分dev/test/prod环境但有个服务的配置没指定namespace默认用了public结果覆盖了其他环境的配置。解决方案spring:cloud:nacos:discovery:namespace:${NACOS_NAMESPACE:public}config:namespace:${NACOS_NAMESPACE:public}五、选型建议5.1 按场景选型场景推荐方案理由Spring Cloud生态Nacos无缝集成功能全面Kubernetes原生Consul原生支持多数据中心简单Demo项目Eureka开箱即用但不推荐生产强一致性要求ZooKeeperCP模型金融场景国内团队Nacos中文文档社区活跃5.2 最佳实践优先选择Nacos功能完善、社区活跃、国内生态好注册时携带完整元数据版本、区域、权重、环境等配置合理的健康检查心跳间隔、超时时间、健康检查URL优雅上下线主动注销、优雅关闭、预热做好注册中心监控服务数量、实例健康率、注册延迟多环境隔离使用namespace隔离dev/test/prod集群部署至少3节点使用MySQL存储六、血的教训注册中心是微服务的基础设施选型要慎重。我们当初从Eureka迁移到Nacos花了整整两周改造所有服务的注册配置迁移配置中心重新设计灰度发布流程全链路测试迁移成本极高选型时一定要考虑长远。Eureka已经停止维护了新项目千万别用。七、思考题你的系统用了什么注册中心选型时考虑了哪些因素遇到过注册中心故障吗怎么解决的你认为注册中心最重要的是什么可用性还是一致性个人观点仅供参考