摘要:服务一多且老变(扩容、宕机、升级、缩容),它的地址就不再是写死在配置文件里的固定值。服务发现解决"该找谁、它在哪、还活着吗"三个问题。本文讲通注册中心 + 心跳、客户端发现与服务端发现两种模式,以及注册中心自己挂了怎么办。
网关知道要调"订单服务",但订单服务现在有几个实例?132.168.1.7 还是 .12?地址是不是刚因为扩容换过了?如果把这些写死进配置文件,那你每次扩缩容都要改配置、重启、多版本并存——这在几十个服务的世界里就是一场灾难。服务发现就是为"地址漂移"而生的。
没有服务发现时,你是这样调服务的:
# 每个环境都要维护一份"谁在哪"的地图,改了配置就得重启 ORDER_SERVICE_1=http://10.0.3.14:8080 ORDER_SERVICE_2=http://10.0.3.15:8080
一旦加一台实例、宕一台机器、升级一次,这份 map 就过期了。服务发现把这件事自动化:服务起来时自己报个到,挂掉时自动注销,调用方随时问注册中心"现在谁还活着"。
典型流程如下:
三件事分别回答三个问题:
服务发现有个经典分叉:发现逻辑放在谁那边。
用一张表对比:
| 模式 | 谁去查 | 优点 | 缺点 |
|---|---|---|---|
| 客户端发现 | 调用方 | 实现直接、无中间跳 | 每个服务都要写发现逻辑,依赖注册中心 |
| 服务端发现 | 负载均衡器/平台 | 客户端薄、运维集中 | 多了均衡器一层,配置稍复杂 |
实际趟过水的团队大多会告诉你:能用平台自带的服务发现(比如 k8s 的 Service)就别自己造一个 Galera 全家桶。自己造注册中心+发现框架,是很多团队当年最痛的弯路之一——不是不会,而是维护成本远超想象。
服务发现把所有地址都系在一个注册中心上,它自己挂了岂不是全崩?这里要说透:注册发现机制是"易失的、可牺牲的",真正不能牺牲的是你的调用链路。应对三板斧:
这本质上是在说:服务发现的设计目标不是"永不故障",而是"故障时把它对业务的影响压到最小"。这一思想在第 6 章弹性容错里会被正式升格成原则。
注册中心和真实状态之间必然有一点滞后:刚宕的实例还在列表上、刚起的实例还没被看到。这不是 bug,是分布式系统的固有代价。所以调用方要有"调过去发现连不上了就换下一个实例"的容错,而不能拿"注册中心说了算"当护身符。这里又接回第 6 章要讲的重试和熔断。
最常见的错,是把注册中心的地址列表当成"一切以它为准"的圣旨,连自己的本地缓存都不敢置信。结果注册中心抖一下,全站跟着哆嗦。正确的心态是:注册中心提供一个近期的、经常对的提示,真正的存活状态要靠调用时的探活来兜底。两套信息互相校验,才稳。
服务发现里最容易糊弄过去的细节,是把一个服务"还活着"当成"能干活"。心跳证明的只是进程心跳在跳、端口还开着——可一个端口开着的服务,完全可能因为数据库连接满、缓存失效、线程池卡死而"活而无效"。这就是为什么越来越多平台把探活分成两档:liveness(我还活着吗,死了就重启) 和 readiness(我能接流量吗,暂时接不了就摘除法补)。前者管进程生死,后者管对外准入。
这两档分开的价值很具体:如果一个服务因为"临时欠了资源"而暂时接不了流量,平台应该把它从可用列表摘下来、让调用方再缓冲一瞬,而不是立刻把它当坏的杀掉反复重启。把"存活"和"就绪"分开管,是在服务发现之上又加了一层真实的业务可用性判断——这也是很多系统"注册中心没问题、但请求照样超时"时真正差的那一环。
一句话:服务发现的价值,是让"地址该变了"和"谁还活着"自动化,别让你手动设
服务发现解决"该找谁、在哪、还活着吗",应对地址漂移
三件套是注册 + 心跳 + 发现:起来报名、定期报活、查询列表
客户端发现自研实现更直,服务端发现运维更集中(k8s Service 最省心)
注册中心可牺牲,本地缓存 + 探活兜底保证调用链路不断
别把注册中心当唯一真相,状态滞后是常态,靠调用容错补齐