想象一座城市里开了上千家连锁奶茶店,市民想喝奶茶时,却发现店铺经常搬家:新店开张、老店换址、还有分店悄无声息就关了。如果每个市民手里都攥着一张打印好的店铺名单,那这名单永远都是过时的。于是城市在中央广场竖起一块大公告栏:每家店开业就来登记地址,关张就自行撤下,市民想喝奶茶时来查一下,还能顺便订阅“某某奶茶品牌开新店”的消息。这块公告栏,就是技术世界里的“注册中心”。 今天要聊的两位主角,是建造这块公告栏的两种方案。Consul 像一座既有中央政务大厅、又在每个街区安插“街坊情报员”的城市:重大事项盖章确认,街坊之间靠口耳相传快速同步消息。etcd 则像一座只有唯一权威登记处的城市:一切变更都要到中心盖章,任何人看到的公告都完全一致。两种思路都解决了同一个问题——服务发现,但适用的场景截然不同。这篇文章会沿着“城市公告栏”的视角,把 Consul 与 etcd 的架构、健康检查、通知机制讲清楚,最后告诉你该怎么选。

岗位关联

服务发现是微服务与云原生架构的基础设施技能,学会 Consul 与 etcd 的权衡之道,等于掌握了分布式系统“一致性与可用性”的决策思维,是走向系统架构师的关键一步。

目录
  1. 可视化演示
  2. 1. 服务发现问题:会搬家的奶茶店
  3. 2. 注册中心原理:公告栏的营业规则
  4. 3. Consul 架构:政务大厅加街坊情报员
  5. 4. etcd 架构:唯一权威的政务大厅
  6. 5. 健康检查:谁来决定一家店算不算活着
  7. 6. 通知机制:告示一贴出就喊你
  8. 7. 选型决策:照着自己城市的样子挑
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示微服务注册中心的服务注册与发现流程、Raft共识协议Leader选举与日志复制、健康检查与watch通知机制,切换模式理解分布式服务发现核心原理。

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

服务注册与发现流程 服务提供者启动时向注册中心(Consul/etcd)注册IP+端口和元数据 服务消费者从注册中心查询可用实例列表,通过负载均衡选择实例调用 Provider → Register → Registry | Consumer → Query → Registry → Provider 注册中心是微服务架构核心基础设施

1. 服务发现问题:会搬家的奶茶店

在“城市公告栏”出现之前,市民是怎么知道奶茶店地址的?答案是:街道办把店铺名单打印成册,发到每家每户。奶茶店一搬家,市民手里的小册子就废了。微服务架构也是这样:一个订单服务,为了扛住高峰流量会同时启动 6 个实例,每个实例都有独立的 IP 和端口;业务低峰时又缩回 2 个。如果调用方把地址写死在配置文件里,那么每一次弹性伸缩,都要人工去改一遍所有配置,再重启所有调用方。

这种“静态配置”的痛点,在实例数量变多后会急剧放大。假设有 M 个消费者都要调用同一个服务,而服务有 N 个动态实例,那么系统中需要维护的地址条目数就是两者的乘积。任何一个实例扩容、宕机或下线,M 条配置全部作废,改配置的速度永远追不上实例变化的速度。

所以,与其让每个市民都维护一份手抄名单,不如把名单集中到中央广场:消费者想喝奶茶时来查一次,改动只发生在一处。注册中心(Registry)就是这块公告栏,而“服务发现”就是“查公告栏找到该去哪家店”的整个过程。

C=M×N\displaystyle C = M \times N
配置条目数 = 消费者数量 × 服务实例数量。实例一变,所有消费者的配置都要跟着变。
  1. M 个消费者每个调用方都要保存一份服务地址清单,M 代表消费者数量。
    M=consumer countM = \text{consumer count}
  2. N 个动态实例服务实例会弹性伸缩,N 代表某一时刻的实例总数。
    N=instance countN = \text{instance count}
  3. 乘起来的灾难两者相乘得到总配置条目数,任何一次实例变更都会让全部条目失效。
    C=M×NC = M \times N

2. 注册中心原理:公告栏的营业规则

公告栏要运转起来,需要一套明确的规则。首先,每家奶茶店开业时必须到中央广场登记地址,这叫“服务注册”;登记不是永久的——公告栏规定,每家店每 3 秒必须派人来报到一次,否则就视为倒闭并把告示撤下,这叫“心跳续约”;市民来查时,公告栏把当前所有在册地址交给他,这叫“服务发现”;如果一家店连续超过规定时间没来报到,公告栏就会把它的信息擦掉,这个过程由“租约”(Lease)机制完成。

租约本质上是一段“有效期”。每次心跳到达,注册中心就把该实例的过期时刻向后顺延一个 TTL(Time To Live)。当“当前时刻”减去“最后一次心跳到达时刻”大于 TTL 时,注册中心判定该实例失联,将其从在册名单中剔除。这套机制保证了公告栏上的信息总是“新鲜的”,而不是堆着一堆僵尸地址。

市民会问:如果公告栏自己坏了怎么办?这正是下一节两位主角分道扬镳的地方——Consul 和 etcd 用完全不同的方式保证公告栏本身的可靠。

tnow−tlast>TTL⇒evict\displaystyle t_{now} - t_{last} > TTL \Rightarrow \text{evict}
当前时间减去最后心跳时间,若超过租约有效期,则判定实例失联并剔除。
  1. 当前时刻 t_now注册中心用来做比较的“现在”。
    tnow=current timet_{now} = \text{current time}
  2. 最后心跳 t_last实例最近一次成功报到的时间戳。
    tlast=last heartbeat timet_{last} = \text{last heartbeat time}
  3. 失效判定两者的差超过 TTL,说明实例在租约期内没有续约。
    tnow−tlast>TTLt_{now} - t_{last} > TTL
  4. 执行剔除将失联实例从公告栏上擦掉,避免消费者拿到死地址。
    ⇒evict\Rightarrow \text{evict}

3. Consul 架构:政务大厅加街坊情报员

Consul 的城市治理方案是“双轨制”。一方面,它在市中心建了一座政务大厅,所有店铺的开业、更名、关张等重大登记,都要由大厅里的办事员们集体表决通过后才生效,这就是 Raft 共识协议:一份变更只有获得多数办事员(quorum)盖章,才算正式写进总账簿。另一方面,Consul 在每个街区安插了情报员,情报员之间靠 gossip 协议快速传递“谁家新开了、谁家倒闭了”的消息,这样政务大厅的变更能在几百毫秒内传遍全城,而且某个街区网络不通时,本地依然能通过街坊口述获得大致的服务名单。

政务大厅的多数派决策有一个关键数字:quorum。假设大厅里有 N 个办事员,一次表决至少需要多少票才能通过?答案是超过一半:半数向下取整再加一。这个数字保证了任何两次表决的赞成人群一定有交集,从而不会出现“两个大厅各自为政”的分裂。

回到公告栏的比喻:Consul 的厉害之处在于,政务大厅(Raft)保证总账簿准确,而街坊情报员(gossip)让消息传播得又快又广。多数据中心场景下,每个城市都有自己的政务大厅和情报网络,城市之间各管各的,跨城调用时再互相通气——这就是 Consul 支持多数据中心的底气。

Q=⌊N2⌋+1\displaystyle Q = \left\lfloor \frac{N}{2} \right\rfloor + 1
表决通过所需的最少票数 = 半数向下取整 + 1。N = 5 时需要 3 票。
  1. 总办事员数 N集群中的节点总数,例如 5。
    N=5N = 5
  2. 半数向下取整⌊N/2⌋ 表示不超过一半的最大整数。
    ⌊N2⌋=2\left\lfloor \frac{N}{2} \right\rfloor = 2
  3. 加一形成多数派quorum = 3,任何两个 3 人小组必然至少有 1 人重合。
    Q=2+1=3Q = 2 + 1 = 3

4. etcd 架构:唯一权威的政务大厅

与 Consul 的“双轨制”相反,etcd 的城市只有一座政务大厅里的一本总账簿,规则极其简单:任何变更(包括登记、变更、注销)都必须由大厅的多数办事员盖章才能生效,没有任何街坊情报员来帮忙传话。这就是“纯 Raft 强一致”模型——整个城市只有一个真相源,任何人读到任何信息,都跟总账簿完全一致,不存在“听说”和“传言”。

etcd 还引入了一个有趣的设计:总账簿的每一页都有递增的页码(revision)。每发生一次变更,页码加一,所有历史页面都保留,支持按版本查询。同时,etcd 把“店铺租约”做成了第一等公民:一家店可以领取一张长期租约,以租约 ID 关联自己的告示,只要租约不过期,告示就一直有效;续租时只需给租约续期,而不用一条条重新登记告示,这比逐个刷新地址高效得多。

为了保证读到的内容一定是新的,etcd 的读请求也要访问多数节点——读集合与写集合必须相交。读写多数派条件写作:

用公告栏比喻:etcd 没有街坊情报员,所有消息都以政务大厅盖章的版本为准,因此它的一致性最硬、也最容易推理。代价是:公告栏的每一次心跳、每一次读,都要折返大厅跑一趟,没有“顺路听街坊说一句”的便宜可占。

R+W>N,R=W=⌊N2⌋+1\displaystyle R + W > N, \quad R = W = \left\lfloor \frac{N}{2} \right\rfloor + 1
一次读访问的节点数与一次写确认的节点数之和大于总节点数,保证读到的值至少包含最新一次已提交的写入。
  1. 读集合 R一次线性一致读必须访问的节点数。
    R=⌊N2⌋+1R = \left\lfloor \frac{N}{2} \right\rfloor + 1
  2. 写集合 W一次写必须确认的节点数,与读相同。
    W=⌊N2⌋+1W = \left\lfloor \frac{N}{2} \right\rfloor + 1
  3. 必然相交两个多数集合必有共同节点,读一定能碰到刚提交过该值的事务员。
    R+W=2⌊N2⌋+2>NR + W = 2\left\lfloor \frac{N}{2} \right\rfloor + 2 > N

5. 健康检查:谁来决定一家店算不算活着

公告栏上挂着的地址,只有是健康店铺的才是可用的。两家城市用了不同的办法判断“这家店是否还活着”。Consul 的做法是“网格化巡检”:每个街区的情报员亲自上门探访,开店的人不用自己跑政务大厅报到,而是由情报员主动发起检查——可以是敲一下 HTTP 接口、拨一次 TCP 连接,甚至执行一段自定义脚本,连续失败一定次数就把店铺标记为“异常”,并立刻广播全城。

etcd 则沿用自己“一切以租约为准”的简洁逻辑:店铺自己定期向政务大厅续租,续租成功就代表活着;一旦租约在 TTL 内没有续期,大厅直接把与之关联的告示全部撤下。也就是说,Consul 的检查偏向“主动上门”(agent 执行检查),etcd 的检查偏向“本人报到”(客户端续租)。这也是为什么 Consul 能检查 MySQL 进程是否存活、磁盘是否满了这类“业务健康”,而 etcd 只能判断“这个节点还连不连得上”。

运维上,一个直观指标是服务健康可用率,即健康实例数占总实例数的百分比:

回到比喻:Consul 像是社区医生上门体检,能查出各种疑难杂症;etcd 像是定期在政务大厅签到,不签到就除名。前者信息丰富,后者逻辑极简——没有谁绝对更好,只看你想“体检”还是“点名”。

A=HN×100%\displaystyle A = \frac{H}{N} \times 100\%
健康可用率 = 健康实例数 ÷ 实例总数 × 100%,是判断服务是否该扩缩容的第一信号。
  1. 健康实例数 H通过健康检查、处于可用状态的实例数量。
    H=healthy countH = \text{healthy count}
  2. 实例总数 N注册中心在册的全部实例数量。
    N=total countN = \text{total count}
  3. 求百分比两者相除再乘 100,得到 0% 到 100% 的可用率。
    A=HN×100%A = \frac{H}{N} \times 100\%

6. 通知机制:告示一贴出就喊你

市民不可能每秒钟都跑去公告栏盯着看,但新店开业的消息晚知道一分钟,就可能少做一单生意。于是两个城市都提供了“订阅”功能。etcd 的订阅叫 Watch:市民告诉公告栏“我想关注某某类型店铺”,然后挂起一个长轮询,公告栏一旦检测到相关页面版本号变化,立刻返回“有变化”,市民再快速拉取最新内容;如果很长时间没变化,请求也会超时返回,然后市民重新挂起,如此循环。

Consul 的订阅机制叫阻塞查询(blocking query):名字不同,本质相似——消费者带着自己已知的最新 index 号来问,公告栏发现 index 没变化就一直“hold 住”请求不回复,直到有变更或超时才返回。两者的差异在版本化能力上:etcd 每次变更产生单调递增的 revision,消费者可以从任意版本号开始监听,不漏掉任何一次变更,即使中间离线了也能从断点续传。

Watch 的核心是“从哪个版本开始监听”。客户端记住自己已经处理到的版本号,下一次请求指定从该版本加一开始:

用市民的视角看:Watch 就像在公告栏旁边留了个“有新告示就拍我肩膀”的约定,而且每一张告示都编号,即使你打了个盹,醒来也知道该从哪张看起。Consul 的阻塞查询同样会拍你肩膀,但它的“编号”体系比 etcd 的版本化账本要轻量得多。

start_revision=current_revision+1\displaystyle \text{start\_revision} = \text{current\_revision} + 1
客户端从自己已消费的版本号的下一个版本开始监听,保证变更事件不重不漏。
  1. 当前版本号客户端已处理到的最新 revision。
    current_revision\text{current\_revision}
  2. 下一个版本从 current_revision + 1 开始监听。
    start_revision=current_revision+1\text{start\_revision} = \text{current\_revision} + 1
  3. 崩溃恢复进程重启后,仍能从上次消费位置继续跟,不丢事件。
    resume from current_revision+1\text{resume from } \text{current\_revision} + 1

7. 选型决策:照着自己城市的样子挑

讲了这么多,最后的问题最实际:我该用哪一种?这里要引入分布式系统最著名的“不可能三角”——CAP 定理。当网络发生分区(P = 1,两片街区暂时失去联系)时,你必须在“一致性”(C,全城看到同一张公告)和“可用性”(A,尽量让市民能查到信息)之间二选一:

结合前几节的对比,选型套路已经很清晰:如果你的城市是 Kubernetes——etcd 就是唯一的官方登记处,深度绑定,直接使用;如果你的业务是 Spring Cloud 这类微服务,需要 HTTP 健康检查、DNS 接口、多数据中心容灾,Consul 更顺手;如果你对一致性极度敏感、不能容忍任何“街坊传言”造成的旧数据,etcd 的纯 Raft 更让人安心。而如果追求“查得到就行,稍微旧点无所谓”,Consul 的 gossip 加阻塞查询会让你更从容。

最终判断标准就一句话:你想要一个“永远准确、但偶尔要排队等大厅”的城市,还是一个“遍布情报员、消息稍慢半拍但始终能问到路”的城市?答案没有对错,只有适不适合。

C+A=1(P=1)\displaystyle C + A = 1 \quad (P = 1)
分区发生时,一致性收益与可用性收益之和等于 1,只能保住一个。etcd 选 C,Consul 在容灾模式下偏 A。
  1. 分区发生 P=1网络故障使集群被切分成互不相通的几片。
    P=1P = 1
  2. 一致性 C所有访问都返回最新且唯一的值,但故障侧可能响应失败。
    C=1,A=0C = 1, A = 0
  3. 可用性 A所有请求都有响应(可能返回旧数据),但无法保证最新。
    C=0,A=1C = 0, A = 1

AI 应用场景

微服务网关接入 Consul:让每次调用自动发现最新地址

阶段:模型服务部署与推理流量调度(AI 在线推理服务同样需要服务发现)

解决的问题:AI 推理服务(如 LLM 推理服务)经常按 GPU 资源伸缩实例,旧做法把模型服务地址写死在网关配置里,扩缩容一次就要改一次配置并重启网关。接入 Consul 后,网关启动时从注册中心拉取模型服务实例列表,并通过阻塞查询实时感知实例上下线,扩容的实例自动被调度,宕机实例自动被摘除。

具体案例:某推荐系统团队将排序模型的 TensorFlow Serving 实例注册到 Consul,网关按健康检查结果把流量分配到存活的实例上,一次模型服务扩容从“人工改配置 + 重启”变为“自动生效”,发布耗时从 30 分钟降到 1 分钟。

Kubernetes 依赖 etcd:集群控制面的“唯一权威登记处”

阶段:分布式训练任务编排与配置下发(kube-apiserver 读 etcd 获取节点和 Pod 状态)

解决的问题:Kubernetes 集群需要所有节点、Pod、配置的统一视图,任何节点的心跳与状态变化都必须被所有控制面组件一致地看到。etcd 用纯 Raft 强一致和 Watch 机制,保证 kube-apiserver 总是拿到最新状态,并支持从任意 revision 续传,非常适合“集群状态”这类要求不重不漏的场景。

具体案例:某 AI 平台用 Kubernetes 托管 2000 张 GPU 卡的训练集群,节点心跳全部写入 etcd,节点失联后控制器在秒级收到 Watch 事件并驱逐 Pod,重调度时间从分钟级缩短到秒级。

服务网格 sidecar 与 Consul Connect:调用前的地址与证书双重发现

阶段:AI 服务间调用链路的流量治理(每个服务调用前先向注册中心取地址与身份)

解决的问题:AI 服务之间存在大量互相调用(特征服务、模型服务、存储服务),流量加密和权限控制以往靠写死在代码里。Consul Connect 让每个服务实例先向注册中心获取对端地址和一次性 TLS 证书,再建立 mTLS 连接,把“发现”和“身份认证”合并到同一步。

具体案例:某自动驾驶数据平台在 20 个微服务间启用 Consul Connect,sidecar 通过 gossip 快速感知对端实例变化,证书轮换自动化,安全审计日志完整。

Python 演示

python57 行
import time  class Registry:    """迷你注册中心: 用内存字典维护地址与租约到期时间"""     def __init__(self, ttl=2.0):        self.ttl = ttl        self._addrs = {}        self._watchers = []     def register(self, name, addr):        self._addrs[name + '#' + addr] = time.time() + self.ttl        self._notify('register', name, addr)     def heartbeat(self, name, addr):        key = name + '#' + addr        if key in self._addrs:            self._addrs[key] = time.time() + self.ttl            return True        return False     def evict_expired(self):        now = time.time()        for key, expire in list(self._addrs.items()):            if expire < now:                del self._addrs[key]                name, addr = key.split('#')                self._notify('evict', name, addr)     def discover(self, name):        return sorted(k.split('#')[1] for k in self._addrs                      if k.startswith(name + '#'))     def watch(self, callback):        self._watchers.append(callback)     def _notify(self, event, name, addr):        for cb in self._watchers:            cb(event, name, addr)  def demo():    reg = Registry(ttl=2.0)    reg.watch(lambda e, n, a: print('[event]', e, n, a))    reg.register('payment', '10.0.0.1:8080')    reg.register('payment', '10.0.0.2:8080')    for i in range(6):        reg.heartbeat('payment', '10.0.0.1:8080')        time.sleep(0.7)        reg.evict_expired()        print('round', i + 1, 'visible:', reg.discover('payment'))    print('final table:', reg.discover('payment'))  if __name__ == '__main__':    demo()
目的:模拟注册中心的核心流程(注册、心跳续约、TTL 过期剔除与 watch 通知),展示服务发现如何维护动态实例地址。
  1. self._addrs[name + '#' + addr] = time.time() + self.ttl注册时用“服务名#地址”作为键,值为租约过期时刻。
  2. if key in self._addrs:心跳前必须确认该实例已注册,防止陌生实例混入。
  3. self._addrs[key] = time.time() + self.ttl续约就是把过期时刻向后推 TTL 秒,相当于奶茶店续报到。
  4. for key, expire in list(self._addrs.items()):用 list() 做快照遍历,避免边删边遍历报错。
  5. if expire < now:过期判定:租约到期时刻早于当前时刻就剔除。
  6. return sorted(k.split('#')[1] for k in self._addrs if k.startswith(name + '#'))发现服务就是按前缀筛选并排序返回地址列表。
  7. reg.watch(lambda e, n, a: print('[event]', e, n, a))注册 watcher 回调,模拟订阅通知。
  8. time.sleep(0.7)模拟实例心跳周期,周期小于 TTL 才能保持存活。

常见误区

误区etcd 没有街坊情报员那种 gossip 协议,成员之间消息传递肯定比 Consul 慢,集群一有节点变动就容易乱套。

正解etcd 的成员管理同样由 Raft 负责,节点变动通过日志复制在所有节点上顺序达成一致,根本不需要 gossip。gossip 的优势是传播快、实现简单,但它带来的代价是信息可能短暂不一致。etcd 选择纯 Raft 是用“稍慢一点”的路径换取“绝对有序”,两种设计各得其所,不存在谁更“群龙无首”。

为何常见:很多人只看到 Consul 明确宣传 gossip,而 etcd 宣传强一致,容易把“协议种类多少”等同于“可靠程度”。

误区公告栏倒了,市民就完全找不到奶茶店了,所以注册中心一挂整个系统必挂。

正解实际系统中,消费者会把拉取到的地址表缓存在本地内存里,注册中心故障只会影响“变更的感知”(例如新扩容的实例不会被发现、失联实例不会被剔除),但存量调用不受影响。真正会导致瘫痪的是“消费者完全没有缓存且每次调用都强依赖注册中心”的设计,好的架构师会同时做好缓存和降级。

为何常见:把“注册中心”类比成“数据库”这类读写必经之路,而忽略了它的设计初衷是“变更通知中心”而非“每次调用的转发网关”。

误区服务发现把一堆地址给我,我再随机挑一个,这不就是负载均衡吗?

正解这是两个紧密相连但不同的环节。服务发现负责“有哪些可用地址”,解决的是“地址动态变化、我该去哪儿问”;负载均衡负责“在已知的地址列表里按策略选一个”,解决的是“流量如何分配”。以公告栏比喻:查公告栏是发现,从名单里挑一家排队最少的店是负载均衡。两者常配合使用,但不能混为一谈。

为何常见:两个组件总是一前一后出现,接口看起来都是“给我一个地址”,初学者容易从结果倒推成一个东西。

误区etcd watch 推给我一条“实例下线”事件,我直接用事件里的信息更新本地列表就够了。

正解Watch 是“通知”而非“数据快照”。它告知你某类 key 发生了变化(以及变化的类型),但为了让本地缓存与集群完全对齐,客户端正确的姿势是收到通知后立即发起一次读取,把相关 key 的最新值拉回来。这与公告栏“拍你肩膀告诉你告示变了,你去看一眼新告示”是一样的逻辑。忽略“通知后拉取”这一步,容易缺失被批量更新漏掉的中间状态。

为何常见:把“事件通知”误当成“数据推送”,本质上混淆了“告诉你发生了一件事”和“把完整结果给你”两种语义。

面试题

Q1. 服务发现要解决的核心问题是什么?

单选题 · easy

  • A.提高网络传输带宽
  • B.获取服务实例动态地址← 正确答案
  • C.替代数据库存储业务数据
  • D.对请求内容进行加解密

答案:B。服务发现解决“实例地址随时变化”的问题,就像市民通过公告栏获取奶茶店最新店址。A(带宽)、C(数据库)、D(加解密)都与“拿到地址”无关,属于对服务发现职责的误解。

Q2. 服务实例注册后,靠什么向注册中心证明自己还活着?

单选题 · easy

  • A.定期发送心跳续约← 正确答案
  • B.重新打包部署一遍
  • C.发送自己的业务日志
  • D.更换新的服务端口

答案:A。心跳续约是租约机制的基础,实例每隔一段时间报到一次,相当于奶茶店定期去公告栏报到,逾期不到就会被判定失联。B、C、D 都不是注册中心定义下的存活证明方式。

Q3. etcd 集群用哪种共识算法保证数据一致?

单选题 · easy

  • A.Raft← 正确答案
  • B.Gossip
  • C.一致性哈希
  • D.深度学习训练

答案:A。etcd 使用纯 Raft 算法,所有变更需多数节点确认后落账。Gossip 是 Consul 辅助传播成员消息的协议;一致性哈希用于分布式缓存路由;D 与共识算法无关。

Q4. Consul 集群中,负责在局域网内快速传播成员变更消息的是?

单选题 · mid

  • A.Raft 日志
  • B.Gossip 协议← 正确答案
  • C.HTTP 轮询
  • D.DNS 解析

答案:B。Consul 是"Raft + gossip"混合架构:Raft 负责总账簿的强一致写入,gossip 负责把成员变更像街坊口耳相传一样在局域网内快速扩散。Raft 日志只覆盖数据中心内部共识,HTTP 轮询和 DNS 都不是成员传播协议。

Q5. etcd 中租约(Lease)的主要作用是什么?

单选题 · mid

  • A.给 key 附加 TTL 保活能力← 正确答案
  • B.对 key 做加密存储
  • C.自动做流量负载均衡
  • D.把 key 同步到其他集群

答案:A。租约相当于一张“营业许可证”,关联的 key 只要租约未过期就有效,续租只需更新租约本身,无需逐个刷新 key。加密、负载均衡、跨集群同步都不是租约的职责。

Q6. 消费者收到 watch 的下线通知后,正确的下一步是什么?

单选题 · mid

  • A.忽略通知继续用旧地址
  • B.拉取最新地址表并更新缓存← 正确答案
  • C.重启消费者进程
  • D.删除注册中心的记录

答案:B。watch 是“拍肩膀”的变更通知,不是数据快照,正确姿势是收到事件后重新读取最新值,才能把本地缓存与注册中心对齐。忽略会导致继续调用已下线实例;重启与删除记录都不在消费者职责内。

Q7. etcd 线性一致读满足 R + W > N 的根源意义是什么?

单选题 · hard

  • A.读集合与写集合必有交集,保证读到已提交的最新值← 正确答案
  • B.读请求不需要访问任何节点
  • C.写请求只需写入一个节点
  • D.读和写必须串行执行

答案:A。读写各自访问多数派节点,两个多数集合必然相交(R + W > N),所以读至少会碰到一个持有最新提交值的节点,从而保证线性一致。B 违背“访问多数派”前提;C 违背多数派写;D 把“交集保证”误当成“串行互斥”。

Q8. 服务发现的基本环节包括哪些?

多选题 · mid

  • A.服务注册← 正确答案
  • B.心跳续约← 正确答案
  • C.查询发现← 正确答案
  • D.流量加密

答案:ABC。注册(登记地址)、心跳(定期续约证明存活)、发现(查询在册地址)是服务发现的三个基本环节。流量加密属于传输安全范畴,不是服务发现流程的组成部分。

Q9. 相比 etcd,Consul 的典型特点包括哪些?

多选题 · mid

  • A.内置丰富的健康检查← 正确答案
  • B.支持多数据中心← 正确答案
  • C.局域网 gossip 成员管理← 正确答案
  • D.只提供 KV 一种存储模型

答案:ABC。Consul 提供 HTTP/TCP/脚本健康检查、多数据中心复制与 gossip 成员管理。D 说法错误——Consul 还提供服务目录(catalog)、DNS 接口、KV 等多种能力,并非只有 KV。

Q10. 以下哪些是选择 Consul 而非 etcd 的合理理由?

多选题 · hard

  • A.业务需要 HTTP 接口级健康检查← 正确答案
  • B.需要跨数据中心的服务容灾← 正确答案
  • C.应用与 Kubernetes 控制面深度绑定
  • D.已有 Spring Cloud 微服务生态← 正确答案

答案:ABD。Consul 的 agent 巡检型健康检查天然适配 HTTP 接口,多数据中心是它的招牌能力,Spring Cloud Consul 集成成熟。而 Kubernetes 全栈深度绑定 etcd,属于选择 etcd 而非 Consul 的理由,因此 C 不选。