微服务架构下服务注册与发现怎么实现和取舍
服务注册与发现没有“唯一最优解”:先在“客户端发现 vs 服务端发现”和“AP vs CP”这两条轴上选型,K8s 集群内直接用 Service + CoreDNS,跨虚拟机、跨集群、多语言混部再上 Nacos 或 Consul;选错了,后面用多少重试都补不回来。
服务注册与发现到底解决什么问题?
结论:它解决的是“调用方怎么拿到一份当前可用的实例 IP:Port 列表”,核心动作只有三个——注册、健康检测、变更推送。
服务启动后把自己的地址写进注册中心(注册),注册中心按固定周期做健康检查(心跳或主动探测),实例下线或探活失败就把它摘掉,同时把变更推给订阅方。整个过程的关键指标是“故障实例被摘除的延迟”,也就是从实例真挂掉到调用方不再打到它身上的时间。这个延迟 = 健康检查周期 × 失败阈值 + 推送/拉取间隔,调优就是调这几个数。
客户端发现和服务端发现有什么区别?
结论:客户端发现把负载均衡放在调用方进程内,服务端发现把它交给基础设施(VIP、DNS、Sidecar),前者灵活、后者省心。
客户端发现:调用方从注册中心拉全量实例列表缓存在本地,自己按轮询/权重/一致性哈希选一个,代表是 Spring Cloud LoadBalancer 配 Nacos、gRPC 的 name resolver。优点是能自定义路由(灰度、同机房优先),缺点是每种语言都得实现一遍客户端逻辑。
服务端发现:调用方只访问一个稳定入口——K8s 里是 ClusterIP 或 order-svc.pay.svc.cluster.local 这类 DNS 名,由 kube-proxy 的 iptables/IPVS 规则转发到具体 Pod。优点是业务代码零感知,缺点是丢掉了应用层的精细路由能力,要补就得引入 Service Mesh。
注册中心选 AP 还是 CP?
结论:服务发现场景优先选 AP,短暂读到过期实例只会多几次失败请求,而 CP 在选主期间会让整个调用链路不可注册、不可发现。
Eureka 是典型 AP,节点间异步复制,靠客户端缓存兜底;ZooKeeper、etcd 是 CP,走 Raft/ZAB,超过半数节点不可用就停止写入。Nacos 做得更细:临时实例(默认,ephemeral=true)走 Distro 协议,是 AP;持久实例走 Raft,是 CP。端口上要记住 Nacos 2.x 除了 8848 的 HTTP,还需要放通 9848、9849 两个 gRPC 端口,否则客户端长连接建不起来,表现是控制台能看到实例但服务调不通。Consul 需要 8301(LAN gossip)、8500(HTTP)、8600(DNS)。
心跳、健康检查和摘除流量该配多少秒?
结论:注册中心侧的心跳决定“多久标记不健康”,K8s 的 readinessProbe 决定“多久从 Endpoints 摘除”,两处都要配,别只配一处。
Eureka 默认续约间隔 30 秒、租约过期 90 秒,即实例挂掉后最长 90 秒才被剔除;开启自我保护(eureka.server.enable-self-preservation=true)后,当每分钟续约数低于阈值的 85% 时,它会停止剔除任何实例,这是网络抖动时“烂实例打不走”的头号原因。
Nacos 1.x 客户端心跳 5 秒,15 秒未收到心跳标记不健康,30 秒删除实例。Consul 的健康检查写成:
check {
http = "http://127.0.0.1:8080/actuator/health"
interval = "5s"
timeout = "2s"
}
deregister_critical_service_after = "30s"
K8s 里摘除流量靠就绪探针:
readinessProbe:
periodSeconds: 5
failureThreshold: 3
即 15 秒内连续 3 次失败就从 Endpoints 移除,默认值 10 秒 × 3 次 = 30 秒,把 periodSeconds 从 10 改到 5 是成本最低的可用性优化。
Kubernetes 环境下还需要独立注册中心吗?
结论:只在集群内跑、用 HTTP 通信,用 Service DNS 就够;一旦有集群外调用、多语言 gRPC、跨集群容灾,独立注册中心仍然必要。
K8s 1.21 起 EndpointSlice 正式 GA,取代单一大对象 Endpoints,单集群 5000 节点规模下变更推送性能明显更好。但 Service DNS 的解析结果默认带 JVM 层缓存,Java 默认 networkaddress.cache.ttl 为 -1(永久缓存),必须显式设成 5-30 秒,否则 Pod 重建后老 IP 会被继续使用几分钟。gRPC 长连接还会把已建立的连接粘在旧 Pod 上,需要在客户端开启 pick_first→round_robin 并配合 keepalive(如 keepaliveTime=30s)触发重连。
落地时最容易踩的坑是什么?
结论:注册时机和摘除时机不对齐,是灰度发布期间 5xx 的主要来源。
正确顺序是:应用完全预热(数据库连接池、缓存加载完成)后再注册,注册后延迟 3-5 秒再接入流量;下线时先从注册中心摘除,等待一个健康检查周期(建议等于 deregister 时间,如 30 秒)再停进程,也就是常说的“先摘流量、再停服务”。反过来做,就会出现注册中心还认为你健康、连接却已经被拒绝的窗口期。
收束一下:选型上先定客户端还是服务端发现,再定 AP 还是 CP,最后把心跳、探针、摘除三组时间参数对齐到同一个数量级(5-30 秒),跨语言和跨集群场景用 Nacos/Consul,纯 K8s 内调用用 Service + CoreDNS 并管好 DNS 缓存。
原文链接:https://www.gj0.com/thread-1007.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。