想象你走进一座三层楼的美食城:几十家摊位分布在不同的楼层和角落,烤鱼在 2F-12 号,拌面在 1F-03 号,奶茶在 3F-07 号。作为食客,你不可能挨家挨户去问“你卖什么”,好在入口处立着一块电子导览屏——每家摊位开业时都会主动登记位置和招牌菜,你输入“拌面”,屏幕上立刻列出所有卖拌面的摊位。这块导览屏,就是微服务世界里大名鼎鼎的“注册中心”(Registry)。 在单体时代,一切功能都装在一座大饭店里,地址固定、调用直接;而微服务把系统拆成了几十个独立部署的服务,机器地址会随着扩缩容、故障转移随时变化。如果调用方把服务地址“写死”在配置里,就像把摊位位置印在传单上——摊位第二天搬走了,传单就成了废纸。服务发现与注册中心,正是为解决“服务在哪、是否活着、如何找到”这三个问题而生的基础设施。 本文沿用美食城导览屏这个比喻,带你从服务注册模型、服务发现机制、健康检查,一路走到一致性模型与主流注册中心的选型与高可用架构,把分布式系统最核心的基础设施一次讲透。

岗位关联

对于后端架构师与分布式系统工程师,掌握服务发现与注册中心,是设计高可用微服务架构的入场券——它直接决定系统在千万级流量下的弹性与稳定。

目录
  1. 可视化演示
  2. 服务注册模型:摊位的“开业登记”
  3. 服务发现机制:食客怎么找到摊位
  4. 健康检查机制:怎么知道摊位还开着
  5. 一致性模型:多块导览屏如何不打架
  6. 主流注册中心:四家“导览屏厂商”对比
  7. 选型与架构:给美食城挑系统
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示服务注册流程、健康检查机制及负载均衡服务发现的三模态交互动画。

本节配有交互动效(进入视口后自动加载,约 3KB)。

服务注册流程 注册中心 服务实例 Register(IP,Port) 服务实例启动时向注册中心注册地址信息消费者从注册中心查询可用实例

服务注册模型:摊位的“开业登记”

走进一座三层楼的美食城,每个摊位开业的第一件事,不是急着点火炒菜,而是到管理处填一张登记表:摊位编号、楼层位置、招牌菜、营业时间。为什么?因为食客不认识你,导览屏才是所有人的寻路地图。这张登记表,就是分布式系统中的“服务注册”(Service Registration)——服务实例上线后,第一件事就是去注册中心“报到”,把自己证明给整个世界看。

在微服务架构中,每个服务实例启动时,会向注册中心发起注册请求,提交的数据包括:IP 地址、端口号、服务名、版本号、健康检查接口 URL 等,这些统称为“元数据”。注册中心把元数据写入服务注册表(Service Registry),供其他服务查询。注册是服务实例从“进程”变成“可用服务”的分水岭——没注册的实例,就像没登记的摊位,食客再多也找不到你。

注册表绝不是一个“填一次就永久有效”的花名册,而是基于租约机制的动态名单。注册中心会给每条注册记录分配一个租期(Lease),实例需要周期性续约(Renew),否则记录到期后会被回收;当服务优雅下线时,实例也会显式发起注销(Deregister)请求,把记录立即删除,避免调用方继续请求一个已经关闭的地址。

这就像管理处给每个摊位发一张“营业时限卡”:摊位每 5 分钟去管理处盖一次章,如果 30 分钟内没有盖章,导览屏就会撤下它的名字。食客永远不会照着地图走进一家已经空掉的铺位——注册、续约、注销三个动作循环往复,构成了服务实例在注册中心的完整生命周期。

Texpire=TlastHeartbeat+Tlease\displaystyle T_{expire} = T_{lastHeartbeat} + T_{lease}
这句公式表示:一条注册记录的最后心跳时间加上租约时长,就是它的过期时刻。一旦当前时间超过这个时刻,注册中心就会判定实例失联,并将它从注册表中移除。
  1. 最后心跳时刻服务实例最近一次向注册中心证明自己还活着的时刻,它取所有心跳时间戳中的最大值。
    TlastHeartbeat=max⁡{ti}T_{lastHeartbeat} = \max\{t_i\}
  2. 租约时长注册中心允许实例“失联”的最大宽限时间,实践中通常是 10~90 秒,可视为心跳间隔乘以容忍次数。
    Tlease=TrenewInterval×kT_{lease} = T_{renewInterval} \times k
  3. 过期时刻两者相加得到的结果,它是判断实例是否失效的临界时间点。
    Texpire=TlastHeartbeat+TleaseT_{expire} = T_{lastHeartbeat} + T_{lease}

服务发现机制:食客怎么找到摊位

老食客找拌面,从来不会死记“1F-03 号”——他只需要记住一件事:在导览屏输入“拌面”,屏幕上就会列出所有卖拌面的摊位。这就是服务发现的精髓:调用方只依赖服务名,不依赖具体实例地址。地址列表是动态的,今天 3 个实例、明天 30 个实例,对调用方完全透明。

服务发现有两种主流模式。客户端发现(Client-Side Discovery):消费者直接查询注册中心拿到实例列表,再自行选择,配合负载均衡策略(轮询、加权、最小连接数等)。服务端发现(Server-Side Discovery):消费者只请求网关或代理,由代理去注册中心查询并转发,消费者对后端地址完全无感——就像食客对前台说“来一碗拌面”,由服务员替他跑腿去面摊。前者更灵活、少一跳,后者把发现逻辑收敛到基础设施层,对开发者更友好。

无论哪种模式,高频查询都会给注册中心带来压力,于是“缓存+推送”成了标配:消费者首次查询后把实例列表缓存到本地,后续直接读缓存;注册中心通过 Watch 机制(ZooKeeper)、长轮询(Nacos)或 gRPC 流(etcd)在列表变化时主动推送,让缓存紧跟事实。缓存的价值可以用平均延迟公式量化:

这就像美食城的官方 App:打开秒进,展示的是上次同步的摊位列表;导览屏一旦发现摊位搬走,立刻推送一条通知,App 后台自动刷新。用一点点“数据可能旧几秒”的代价,换来了整体低得多的查询压力——这是所有大型注册中心都在用的平衡术。

Davg=Dcache×Phit+Dmiss×(1−Phit)\displaystyle D_{avg} = D_{cache} \times P_{hit} + D_{miss} \times (1 - P_{hit})
平均发现延迟 = 缓存命中延迟 × 命中率 + 未命中延迟 × 未命中率。命中次数越多,整体延迟越接近本地读内存的速度。
  1. 缓存命中延迟消费者直接读本地内存中的实例列表,速度极快,通常在亚毫秒级。
    Dcache≈0.1 msD_{cache} \approx 0.1\,\text{ms}
  2. 缓存命中率所有发现请求中命中的比例,由推送时效与查询频次共同决定。
    Phit=NhitNtotalP_{hit} = \frac{N_{hit}}{N_{total}}
  3. 未命中延迟远程查询注册中心一次所花费的时间,包含网络往返与序列化开销。
    Dmiss≈1∼50 msD_{miss} \approx 1 \sim 50\,\text{ms}

健康检查机制:怎么知道摊位还开着

登记表再漂亮,也不能保证摊位真在营业——老板可能昨晚就收摊跑路了。管理处想了个办法:每个摊位每隔几分钟要对着对讲机喊一声“还在营业”;同时管理员不定期巡场,看看灶台有没有火、门口有没有人。两种手段缺一不可。

对应到技术世界,前者叫心跳上报(Heartbeat):服务实例以固定间隔(通常 5~10 秒)向注册中心发送续约信号,证明自己还活着。后者叫主动探测(Active Probe):注册中心或负载均衡器反向请求实例的健康检查端点(如 HTTP /health),确认它真的能处理业务请求。两者各有侧重:心跳成本低、频率高,适合持续监控;主动探测更真实,能发现“进程活着但服务已瘫痪”的假死状态——就像摊位的灯亮着,但老板已经半个月没进食材了。

健康检查的目的不仅是“知道”,更是“行动”。当连续失败次数达到阈值,注册中心执行故障摘除(Eviction):把实例从可用列表中移除,并向订阅方广播变更通知,让消费者立刻把流量切到其他健康实例。摘除阈值太小容易误杀(一次网络抖动就被踢出),太大则延长故障感知时间,实践中通常取 2~3 次。

这和管理处巡场如出一辙——摊位一时没开门不等于倒闭,但连续三天“喊不应、灯不亮”,就会被移出导览屏并广播给所有食客。健康检查,就是注册中心保持名单“可信”的保鲜剂。

evict(i)  ⟺  failCounti≥K\displaystyle \text{evict}(i) \iff \text{failCount}_{i} \geq K
当某个实例的连续失败次数达到阈值 K 时,注册中心将其摘除。K 取 2~3 较为合理,既避免抖动误杀,又不至于让故障实例长期污染流量。
  1. 连续失败次数实例 i 自上次成功后,连续心跳超时或探测失败的累计次数,每次成功即归零。
    failCounti←{failCounti+1,fail0,ok\text{failCount}_i \leftarrow \begin{cases} \text{failCount}_i + 1, & \text{fail}\\ 0, & \text{ok} \end{cases}
  2. 摘除阈值人为设定的容忍上限,需在故障发现速度与误杀风险之间权衡。
    K∈{2,3}K \in \{2, 3\}
  3. 摘除判定当次数达到阈值,注册中心把实例移出可用列表。
    evict(i)  ⟺  failCounti≥K\text{evict}(i) \iff \text{failCount}_{i} \geq K

一致性模型:多块导览屏如何不打架

美食城太大了,每层楼都立着一块导览屏。如果 1F 的屏说 A 摊位在三楼,3F 的屏却说 A 摊位已搬走,食客就要在两楼之间白跑一趟。不同屏上的信息不一致,就是分布式系统里最让人头疼的问题。

谈一致性必须从 CAP 理论说起:当网络发生分区(Partition)时,系统必须在一致性(Consistency)与可用性(Availability)之间二选一。注册中心是典型的“读多写少、数据量小”系统,不同产品选择了不同取向:ZooKeeper、etcd、Consul 偏好 CP——分区时宁可拒绝写入,也保证读到的一定是最新数据;Nacos 支持 AP/CP 双模式,AP 模式下优先保证注册与发现“随时可用”,哪怕暂时读到旧地址。旧地址意味着调用可能失败一次,而“不可用”意味着所有新服务都无法上线——在注册场景里,AP 往往更划算。

实现强一致的核心是共识算法。Raft 是当下最主流的方案:集群先选举一个 Leader,所有写请求由 Leader 接收并复制日志到其他节点,当超过半数的节点确认写入后,才认为提交成功。这个“半数以上确认”就是法定人数(Quorum)。为什么必须过半数?因为只有过半,才能保证网络分区时只有一个分区拥有多数派,从而避免脑裂——两个分区各自选出 Leader、各自为政。

把视角拉回美食城:每块导览屏选出一块“主屏”,所有摊位信息变更先上报主屏,主屏同步给多数派分屏后,才对外宣布“更新完成”。少数派分屏发现自己落后了,会主动找主屏补齐日志——这就是 Raft 在注册中心里的日常。

Q=⌊N2⌋+1\displaystyle Q = \left\lfloor \frac{N}{2} \right\rfloor + 1
法定人数 Q 等于集群节点数 N 的一半向下取整再加一。例如 5 节点集群,Q=3:只要 3 个节点确认,数据就算落盘成功。
  1. 集群节点总数参与 Raft 共识的节点数,通常配置为奇数,如 3、5、7。
    N=2F+1N = 2F + 1
  2. 可容忍故障数集群最多允许同时挂掉的节点数,由 N 推出。
    F=N−12F = \frac{N - 1}{2}
  3. 法定人数一次写操作需要的最小确认节点数,超过半数即可保证唯一性。
    Q=⌊N2⌋+1Q = \left\lfloor \frac{N}{2} \right\rfloor + 1

主流注册中心:四家“导览屏厂商”对比

导览屏也有厂商之分:有的屏绝对准确但偶尔罢工,有的屏反应飞快但可能延迟片刻才修正,还有的屏本地功能极其丰富。美食城管理方要做的,是根据客流量和预算挑一个趁手的系统。生产环境中最常见的是四个选手,它们各有鲜明的性格。

ZooKeeper:Hadoop 生态出身,基于 ZAB 协议,强一致 CP,写性能弱于读性能,适合 Kafka、HBase 等对一致性要求极高的分布式协调场景。etcd:Raft 协议的开源实现,是 Kubernetes 的配置与元数据存储底座,提供 Watch 和租约能力,是云原生生态的事实标准。Consul:内置 DNS 与 HTTP 双接口、开箱即用的健康检查和跨数据中心同步,运维友好,适合多机房场景。Nacos:阿里巴巴开源,支持 AP/CP 双模式切换,同时兼做配置中心,是国内微服务(尤其 Spring Cloud Alibaba)生态的主流选择。

这些系统在集群规模上与可用性直接相关。假设单台机器的故障概率为 p,法定人数为 Q,则集群中所有法定节点同时存活的概率可近似为 \( (1-p)^Q \approx 1 - Q \cdot p \)。这说明三节点集群只是底线,五节点才能在容错(允许 2 台故障)与写放大之间取得较好平衡;节点继续增多,Q 相应提高,跨节点确认成本也会上升。

好比导览屏厂商的“三屏方案”和“五屏方案”:三屏便宜,坏一台就告急;五屏贵一些,但坏两台仍能正常服务。选哪家、装几屏,本质上是成本、一致性、可用性三者之间的三角权衡。

A≈1−Q⋅p\displaystyle A \approx 1 - Q \cdot p
集群的近似可用性等于“全部法定节点都活着”的概率。p 是单节点故障概率,Q 是法定人数。节点越多,需要同时存活的数量也越多,所以集群规模并非越大越好。
  1. 单节点故障概率一台机器在观察周期内宕机的概率,健康机房通常能做到 0.01 以下。
    p=0.01p = 0.01
  2. 法定人数过半确认所需节点数,它决定了写操作要等待多少个响应。
    Q=⌊N2⌋+1Q = \left\lfloor \frac{N}{2} \right\rfloor + 1
  3. 集群近似可用性全部法定节点同时存活的一阶近似概率,忽略高阶小量。
    A≈(1−p)Q≈1−Q⋅pA \approx (1 - p)^Q \approx 1 - Q \cdot p

选型与架构:给美食城挑系统

管理层最终拍板:导览屏用哪家厂商?装在哪些楼层?停电了怎么办?东区新开业要不要加屏?这些落地问题的答案,就是注册中心的选型与架构设计。

选型有四个维度。功能:是否支持健康检查、DNS 接口、配置中心集成、多机房同步;一致性模式:CP 还是 AP,是否可切换;生态契合:与 Kubernetes、Spring Cloud 等主流体系的整合成熟度;运维成本:部署是否简单、监控告警是否完善。没有“最好”的注册中心,只有“最匹配当前系统形态”的方案——Kubernetes 环境选 etcd 是顺势而为,Spring Cloud Alibaba 体系选 Nacos 则事半功倍,对强一致有硬要求的协调场景仍要回到 ZooKeeper。

高可用设计是三件套:第一,集群部署,至少 3 个节点组成共识集群,绝不允许单点;第二,消费端本地缓存,即使注册中心全体宕机,已缓存的地址列表仍能支撑调用;第三,健康检查自动摘除,把故障实例的影响范围压缩在探测周期以内。多环境部署则建议每套环境(开发、测试、生产)使用独立的注册中心,避免互相干扰;迁移时采用“双注册”策略,新旧注册中心并存期间,消费端逐步切换并验证,最终平滑下线旧集群。多副本的整体可用性可表达为:

就像美食城在东西两个入口各装一块导览屏,一块坏了另一块照常干活——两块同时坏的概率是 \(0.01 \times 0.01 = 0.0001\)。注册中心的高可用设计,本质上就是把这些"0.01"一个一个乘下去,直到故障变成几乎不可能发生的小概率事件。

R=1−∏i=1k(1−ri)\displaystyle R = 1 - \prod_{i=1}^{k}\left(1 - r_i\right)
整个注册中心系统的可用性 R:只要 k 条备份链路中任一条可用,系统就可用;所有链路同时故障的概率是各条失效概率的乘积。副本越多,分母上的“不可能”就越接近现实。
  1. 单条链路可用性第 i 个机房或节点组的可用性,如单机房 0.99。
    ri=0.99r_i = 0.99
  2. 单链路失效概率该链路坏掉的可能性。
    1−ri=0.011 - r_i = 0.01
  3. 整体可用性所有链路同时失效的概率之补集,多副本让整体可用性逼近 1。
    R=1−(0.01)kR = 1 - (0.01)^k

AI 应用场景

大促弹性扩容中的服务自动发现

阶段:系统架构设计与基础设施选型

解决的问题:传统做法在配置中心手工维护“IP+端口”列表,每次扩容要人工改配置、重启进程,几十上百个实例变更时极易出错。引入注册中心后,新实例启动即注册,下线自动摘除,彻底告别手写地址簿。

具体案例:某电商平台大促前将支付服务从 20 个副本扩容到 200 个副本,实例启动的瞬间自动注册到 Nacos,网关通过服务发现立即感知并开始分发流量;某台机器宕机后,心跳中断,10 秒内被摘除,消费者自动把流量切到健康实例,全程无需人工介入。

Kubernetes 云原生环境的服务寻址

阶段:云原生基础设施搭建

解决的问题:容器频繁重建导致 Pod 的 IP 随时漂移,用“写死 IP”的方式根本无法通信。Kubernetes 用 etcd 存储 Service 与 Endpoint 数据,并内置 DNS 做服务发现,容器只需记住服务名。

具体案例:金丝雀发布时,新版本 Pod 带着新的标签启动,注册信息自动写入 etcd;DNS 解析按权重把部分流量引向新版本,验证通过后逐步扩大权重,失败则一键回滚——整个发布过程基于服务发现机制完成,没有任何地址写死。

多机房双活容灾设计

阶段:高可用架构设计

解决的问题:单机房故障会导致注册中心整体不可用,进而让所有服务调用失去“地图”。通过注册中心的多数据中心复制能力(如 Consul 的 WAN 联邦),跨机房同步注册信息,分区时各机房独立服务本区流量。

具体案例:某互联网金融公司在北京、上海各部署一套 Consul 集群,两地数据通过 WAN 同步。某次北京机房光纤中断,上海集群继续提供注册与发现服务,已建立的调用链路与本地缓存共同兜底,业务可用性从 99.0% 提升至 99.99%。

Python 演示

python74 行
import randomimport time  class ServiceRegistry:    """极简注册中心:模拟服务注册、心跳续约、过期摘除与发现"""     def __init__(self, lease_seconds=10):        self._services = {}        self.lease_seconds = lease_seconds     def register(self, service_id, address, metadata=None):        self._services[service_id] = {            "address": address,            "metadata": metadata or {},            "last_heartbeat": time.time(),        }        self._print("注册", service_id, address)     def heartbeat(self, service_id):        if service_id not in self._services:            raise KeyError("服务未注册: " + service_id)        self._services[service_id]["last_heartbeat"] = time.time()        self._print("心跳", service_id, "续约成功")     def discover(self, service_name):        self.purge()        return [            {"id": sid, "address": info["address"], "metadata": info["metadata"]}            for sid, info in self._services.items()            if info["metadata"].get("name") == service_name        ]     def purge(self):        now = time.time()        expired = [            sid for sid, info in self._services.items()            if now - info["last_heartbeat"] > self.lease_seconds        ]        for sid in expired:            self._print("摘除", sid, "心跳超时")            del self._services[sid]     def _print(self, action, service_id, detail):        print("[%s] %s %s" % (action, service_id, detail))  def simulate():    registry = ServiceRegistry(lease_seconds=3)    for i in range(3):        registry.register(            "svc-order-%d" % i,            "10.0.%d.%d:8080" % (i, i),            {"name": "order"},        )     for tick in range(8):        time.sleep(1)        print("---- 第 %d 秒 ----" % tick)        for i in range(3):            if tick == 4 and i == 1:                print("[模拟故障] svc-order-1 停止发送心跳")                continue            if random.random() < 0.8:                registry.heartbeat("svc-order-%d" % i)        alive = registry.discover("order")        print("消费者查找到 %d 个可用实例: %s" % (            len(alive),            [item["address"] for item in alive],        ))  if __name__ == "__main__":    simulate()
目的:用 60 多行实现一个迷你注册中心,模拟三个服务实例的注册、心跳续约、随机抖动,以及第 4 秒时一台实例宕机后如何被自动摘除——完整演示“注册→续约→摘除→发现”的全流程。
  1. def register(self, service_id, address, metadata=None):服务启动时登记地址与元数据,相当于摊位开业填表。
  2. "last_heartbeat": time.time()记录当前时刻作为最近一次心跳,是租约判定的起点。
  3. def heartbeat(self, service_id):实例周期性续约,刷新最后心跳时刻,相当于摊位盖章。
  4. expired = [sid for sid, info in self._services.items() if now - info["last_heartbeat"] > self.lease_seconds]找出超过租约未续约的实例,即“30 分钟没盖章”的摊位。
  5. def discover(self, service_name):消费者按服务名查询健康实例,发现前先做一次清理。
  6. if info["metadata"].get("name") == service_name用元数据中的服务名过滤,模拟导览屏上“输入拌面只显示面摊”。
  7. if tick == 4 and i == 1:在第 4 秒人为让 1 号实例停止心跳,模拟宕机故障。
  8. alive = registry.discover("order")消费者每次查询都能拿到当前活着的实例列表,故障实例不再出现。

常见误区

误区“服务启动时注册成功了,它就会一直留在注册表里,调用方可以永远访问到它。”

正解注册只是“开业登记”,之后还要靠心跳续约和主动探测持续刷新状态。租约过期或连续失败达到阈值,实例照样被摘除。就像摊位开业当天登记了,之后如果一直不交“营业章”,导览屏照样撤下它的名字。注册是一瞬间的动作,健康检查才是长期保障。

为何常见:把“注册”理解为一次性写入,忽略了租约机制和健康检查的存在,只看到了注册表里的初始状态。

误区“注册中心是单点,一旦它挂了,所有服务之间都无法通信,系统直接瘫痪。”

正解成熟方案都有降级手段:消费端本地缓存地址列表、客户端直连备选、多集群冗余部署。注册中心宕机只会导致“新增实例不被发现”,已建立的调用链路仍然可用。真正危险的不是注册中心宕机,而是“没有缓存+单点”的错误设计——那相当于把整张地图只印在一张纸上,还会被风吹走。

为何常见:把动态发现建立在“每次调用都要查注册中心”的错误前提上,忽略了缓存与降级的设计。

误区“ZooKeeper 这类 CP 系统保证数据绝对一致,所以一定比 AP 模式的 Nacos 更适合做注册中心。”

正解CP 系统在选主或分区期间会拒绝写入甚至短暂不可用,这对“读多写少、允许短暂读到旧地址”的注册场景反而是负担。注册中心存放的是服务地址,旧地址导致一次调用失败通常可重试;而“不可用”意味着新服务根本无法上线。选型要看场景,而不是看谁的名字更“一致”。

为何常见:把“一致性”当作绝对优点,忽略了 CAP 理论中一致性背后付出的可用性代价。

误区“心跳间隔越短,故障发现越快,所以最好改成 1 秒发一次。”

正解心跳越密,注册中心承受的 QPS 越高,网络与 CPU 开销呈倍数放大——假设 1000 个实例、1 秒一次心跳,注册中心每秒要处理 1000 个写请求。实践上 5~10 秒间隔加 2~3 次容忍阈值,既能做到秒级感知故障,又能把压力控制在合理范围;对精确性要求高的场景,用主动探测补充。

为何常见:只看到“更快发现故障”的好处,忽略了对注册中心的请求放大效应。

误区“注册中心返回实例列表后,加上负载均衡策略就万事大吉了,发现和均衡是一回事。”

正解服务发现只回答“有哪些可用实例”,负载均衡回答“这次选哪一个”。发现是前提,均衡是策略——先拿到列表,再按轮询、加权、一致性哈希等策略挑选。两者是上下游关系,就像导览屏告诉你哪些面摊开着,而选哪家取决于你是想吃近的、快的一定还是评分高的。

为何常见:发现与调用在代码里紧挨着发生,容易把“找到”和“选择”混为一谈。

面试题

Q1. 服务实例启动后,第一步应当在注册中心完成什么动作?

单选题 · easy

  • A.上报自身地址并登记元数据← 正确答案
  • B.立刻向其他服务发起请求
  • C.随机选择端口并重启进程
  • D.等待网关推送全局配置

答案:A。服务启动后必须先“报到”,即把自己 IP、端口和元数据提交给注册中心,其他服务才能发现它。就像新摊位开业要先填登记表,而不是先急着拉客。

Q2. 注册中心判定实例失效,最常用的依据是什么?

单选题 · easy

  • A.依据实例上报的日志数量
  • B.心跳超时或主动探测失败← 正确答案
  • C.实例占用 CPU 的实时数值
  • D.实例运行时长是否足够长

答案:B。注册中心主要靠两条链路判断存活:实例主动发心跳(被动等待),注册中心主动探测健康端点(主动出击)。日志数量、CPU 数值都只是辅助指标,不能直接作为存活依据。

Q3. 租约(Lease)机制在注册中心中的主要作用是什么?

单选题 · easy

  • A.为注册信息设定一个有效期← 正确答案
  • B.为实例分配动态临时端口
  • C.限制服务实例的最大并发数
  • D.加密服务实例间的通信数据

答案:A。租约给每条注册记录附加“保鲜期”,实例需要周期性续约,否则记录过期被清理。它保证了注册表中不存在僵尸数据,相当于摊位的“营业时限卡”。

Q4. 客户端发现与服务端发现模式的关键区别是什么?

单选题 · mid

  • A.由谁来查询注册表并选择实例← 正确答案
  • B.服务实例是否共用同一个注册表
  • C.注册中心是否需要做健康检查
  • D.服务之间是否使用异步消息通信

答案:A。客户端发现由消费者自己查注册中心并选实例;服务端发现由网关或代理代劳,消费者无需感知后端地址。两者的本质差异是“谁去问路”,而不是注册中心本身。

Q5. 注册中心的本地缓存最直接的价值是什么?

单选题 · mid

  • A.降低查询延迟和注册中心压力← 正确答案
  • B.提升服务端接口的并发能力
  • C.替代负载均衡器的选路策略
  • D.保证注册数据写入的强一致性

答案:A。本地缓存让大多数发现请求命中内存,延迟从毫秒级降到微秒级,同时大幅削减注册中心 QPS。它不负责选路,也不保证写一致性,只优化“读”的路径。

Q6. 在 5 节点的 Raft 注册中心集群中,法定人数 Q 是多少?

单选题 · mid

  • A.全部五个节点都需要同步确认
  • B.至少三个节点确认即可提交← 正确答案
  • C.至少四个节点确认即可提交
  • D.仅需一个节点确认即可提交

答案:B。法定人数 \(Q = \lfloor 5/2 \rfloor + 1 = 3\)。只要 3 个节点确认写入,数据即算提交成功。剩余 2 个节点即使失联,也不影响集群继续写,这是过半数机制的核心价值。

Q7. ZooKeeper 这类 CP 注册中心在网络分区时最可能的表现是什么?

单选题 · mid

  • A.分区时拒绝写入以保持一致性← 正确答案
  • B.分区时继续接受所有写请求
  • C.分区时自动降级为单节点运行
  • D.分区时回滚数据到初始版本

答案:A。CP 系统的原则是“宁可不可用,不可不一致”。分区时少数派无法凑齐法定人数,只能拒绝写入。这正是它与 AP 系统的分水岭:AP 优先保证随时能写,哪怕读到旧数据。

Q8. 以下哪些属于注册中心的健康检查手段?

多选题 · mid

  • A.实例定期向注册中心发送心跳← 正确答案
  • B.注册中心主动探测健康端点← 正确答案
  • C.依据连续心跳超时判定失效← 正确答案
  • D.依据 DNS 解析速度判断健康

答案:ABC。心跳上报是实例主动证明存活,主动探测是注册中心反向检查,连续超时判定是摘除逻辑的基础。DNS 解析速度只能反映域名系统状况,与实例是否健康没有直接关系。

Q9. 下列哪些措施可以提升注册中心的整体可用性?

多选题 · hard

  • A.消费端维护本地缓存做降级兜底← 正确答案
  • B.多机房多副本冗余部署集群← 正确答案
  • C.心跳失败后立即物理关机止损
  • D.主动探活并在失败后摘除← 正确答案

答案:ABD。本地缓存保证注册中心宕机时调用不中断,多副本消除单点,主动探活与摘除把故障实例快速隔离。心跳失败后立即物理关机是典型误操作——一次网络抖动就会误杀正常机器。

Q10. 关于 CP 与 AP 注册中心的选择,正确的理解是什么?

多选题 · extreme

  • A.CP 适合要求严格一致的协调场景← 正确答案
  • B.AP 模式在分区时优先保证可用性← 正确答案
  • C.注册中心场景通常对旧地址容忍度高← 正确答案
  • D.CP 系统在选主期间完全无任何延迟

答案:ABC。CP 适合 Kafka、分布式锁等对一致性有硬要求的场景;AP 在分区时保证可用性,注册中心读到旧地址最多导致一次请求失败,容忍度高,因此 AP 往往更合适。CP 系统在选主期间会拒绝写入,存在短暂的不可用窗口,D 说法错误。