SpringCloud(一):微服务架构与注册中心
SpringCloud(一):微服务架构与注册中心
导语:Spring Cloud 的第一层考点是"为什么要有微服务、它和 Boot/Dubbo 什么关系、版本怎么对齐";第二层是几乎所有面试都会问的"服务注册与发现"——Eureka 的 AP 设计、自我保护,与 Zookeeper 的 CP 取舍,以及为什么现在主流转向 Nacos(Distro + Raft 双协议、临时/持久实例)。注册中心是"服务的地基",这一层答不好,后面的调用/网关都会站不住,共 13 题。
一、微服务与 Spring Cloud 体系
1. 什么是微服务架构?它有哪些优缺点?
答: 微服务是将单一应用拆分为一组小型、独立部署的服务的架构风格:每个服务运行在独立进程、围绕业务能力构建、通过轻量级机制(HTTP/RPC/消息)通信,并各自拥有独立的数据存储。
| 优点 | 缺点 |
|---|---|
| 独立开发部署:团队并行、发布互不阻塞 | 分布式复杂度:网络不可靠、一致性、分布式事务 |
| 故障隔离:单服务故障不必然拖垮全局 | 运维成本:服务数激增、治理与排障变难 |
| 技术异构:可按场景选语言/存储 | 调试与测试变难:跨进程链路、契约变更 |
| 细粒度扩缩容:只扩热点服务 | 资源开销:每个服务都有进程/容器/JVM 成本 |
必须点出的一句:微服务不是银弹——服务拆得太细会变成"分布式单体"(调用链长、事务难、发布互相依赖),成本反而更高。拆分要围绕 DDD 限界上下文,而不是按技术分层。
2. Spring Boot 和 Spring Cloud 有什么区别和关系?
答:
- Spring Boot:解决"单个服务怎么快速开发、配置、运行"——自动配置、Starter、内嵌容器、Actuator。
- Spring Cloud:解决"多个服务怎么协调治理"——注册发现、配置管理、负载均衡、熔断限流、网关、链路追踪、消息总线。
- 关系:Cloud 构建在 Boot 之上(每个微服务都是一个 Boot 应用),但 Cloud 是对"分布式系统问题"的抽象集合;Boot 可以完全脱离 Cloud 使用。
一句话:Boot 造"零件",Cloud 把零件"组装成分布式系统"——Boot 关注"单体内部",Cloud 关注"服务之间"。
3. Spring Cloud 与 Dubbo 有什么区别?怎么选?
答:
| 维度 | Dubbo | Spring Cloud |
|---|---|---|
| 定位 | RPC 框架(聚焦服务间高性能调用) | 微服务一站式解决方案(全家桶) |
| 通信 | 默认 Dubbo 协议(二进制、长连接、高性能) | 默认 HTTP + JSON(REST,通用、跨语言友好) |
| 注册中心 | Zookeeper / Nacos 等 | Eureka / Nacos / Consul |
| 服务治理 | 自带(路由、集群容错、限流早期较弱) | 由各组件组合(Sentinel、Gateway…) |
| 生态完整度 | 需要自己拼(网关/配置/追踪) | 开箱即用(配置、网关、熔断、追踪) |
| 适用 | 内部高并发、低延迟、接口稳定的服务间调用 | 需要完整治理体系、团队希望标准化 |
结论:
- 追求极致性能 + 内部服务 → Dubbo(或 gRPC);
- 追求体系完整 + 快速落地 → Spring Cloud;
- 国内主流是"Spring Cloud Alibaba"(Nacos + Sentinel + Seata + Gateway + Dubbo 可选)——把两者的长处合并:阿里系组件同时补齐了注册/配置/容错/事务。
4. Spring Cloud 的版本号为什么是"地名"?版本如何与 Spring Boot 对应?
答: Spring Cloud 由多个节奏不同的子项目组成,为避免"版本号互相牵扯",采用 Release Train(发布列车) 模式——用伦敦地铁站名按字母序命名大版本(Finchley → Greenwich → Hoxton → Ilford → Jubilee → Kilburn → Leyton),子项目共享同一个版本列车。
| Spring Cloud | 代号 | Spring Boot |
|---|---|---|
| 2023.0.x | Leyton | 3.2.x / 3.3.x |
| 2022.0.x | Kilburn | 3.0.x / 3.1.x |
| 2021.0.x | Jubilee | 2.6.x / 2.7.x |
| 2020.0.x | Ilford | 2.4.x / 2.5.x |
| Hoxton | Hoxton | 2.2.x / 2.3.x |
必须知道的两个演进事实:
- 2020.0(Ilford)起移除 Netflix 系:Ribbon(→ Spring Cloud LoadBalancer)、Hystrix(→ Resilience4J / Sentinel)、Zuul 1(→ Spring Cloud Gateway)、Archaius 等被移除;Eureka 虽保留但已非官方主推(社区普遍转向 Nacos / Consul)。
- 2022.0(Kilburn)起基线 Java 17、Jakarta EE 9+,与 Boot 3.x 严格绑定——版本不匹配是启动失败的高频原因(用 Spring Cloud BOM +
spring-cloud-dependencies统一管理,不要手写子项目版本)。
5. 什么是服务注册与发现?为什么微服务必须要它?
答: 微服务实例的地址是动态的(弹性扩缩容、发布重启、容器漂移、IP 变化),硬编码地址不可行。注册中心扮演"通讯录":
① 注册(Register):服务启动 → 把自己的 IP/端口/元数据写入注册中心
② 心跳/健康检查:服务定期续约,注册中心剔除失联实例
③ 发现(Discover):调用方拉取(或订阅)可用实例列表
④ 负载均衡:调用方从列表中挑一个发起调用(客户端 LB)
⑤ 变更通知:实例上下线时主动推送/让客户端感知它解决的核心问题:
- 解耦:调用方不需要知道提供方的具体部署位置;
- 弹性:实例增减对调用方透明;
- 可用性:自动摘除故障实例,避免请求打到坏节点。
加分点:注册中心本质是一个 AP 或 CP 的取舍问题(见第 9 题)——服务发现场景通常优先可用性:宁可短暂拿到"过期的列表",也不能让整个注册中心不可用。
二、Eureka 与注册中心选型
6. Eureka 的核心架构与工作流程是怎样的?
答: Eureka 分为 Eureka Server(注册中心) 与 Eureka Client(提供者 + 消费者),各节点对等(Peer to Peer),没有主从:
| 交互 | 说明 | 默认参数 |
|---|---|---|
| Register | 客户端启动时向 Server 注册 IP/端口/元数据 | — |
| Renew(心跳续约) | 客户端定期发送心跳表示存活 | 每 30 秒一次 |
| Cancel(下线) | 客户端正常关闭时通知 Server 剔除 | — |
| Get Registry(拉取注册表) | 客户端本地缓存服务列表,周期性增量拉取 | 每 30 秒一次 |
| Evict(剔除) | Server 移除超时未续约的实例 | 默认 90 秒未续约 |
| Replicate | Server 之间互相同步注册表 | 保证最终一致 |
两条关键设计:
- 客户端缓存(Client Cache):消费者把注册表缓存在本地——注册中心短暂不可用时,调用仍可继续(这是 Eureka "AP 高可用"的核心来源)。
- Server 对等复制:任一节点都能读写,节点间异步复制 → 最终一致(因此可能短暂读到不同节点不一致的列表)。
一句话:Eureka 的设计目标是"可用性优先"——它宁可让你拿到稍旧的列表,也不让你拿不到列表。
7. 什么是 Eureka 的自我保护机制?
答: 当 Eureka Server 在 15 分钟内心跳续约成功率低于 85%(通常是网络分区、大面积抖动,而非真的实例全挂),会进入自我保护模式:
- 不再剔除因心跳超时未续约的服务实例(保留注册表);
- 仍接受注册与查询请求,但不再向其他节点同步(避免把"误剔除"扩散);
- 网络恢复、续约率回升后自动退出保护模式并重新同步。
设计意图:宁可保留"可能不健康"的实例,也不因网络抖动误删大量健康实例——因为误删会导致流量瞬间打到少数实例、引发雪崩。
eureka:
server:
enable-self-preservation: true # 生产建议保持开启
eviction-interval-timer-in-ms: 60000答题要点:自我保护是"用一致性换可用性(AP)"的具体体现——与 Zookeeper 在选举期间整体不可用形成鲜明对比(见下题)。
8. Eureka 和 Zookeeper 作为注册中心有什么区别?(CAP 取舍)
答: 本质是 CAP 取舍不同:
| 维度 | Eureka(AP) | Zookeeper(CP) |
|---|---|---|
| 一致性模型 | 最终一致(节点异步复制) | 强一致(ZAB 多数派写) |
| 分区/Leader 故障时 | 各节点继续提供注册与查询 | 需重新选主(约 30~120 秒),期间集群整体不可用,注册服务瘫痪 |
| 客户端缓存 | 有(本地缓存注册表) | 需要 Watch + 重连 |
| 数据正确性 | 可能读到"已下线的实例"(短暂) | 不会读到脏数据 |
| 注册中心容错 | 极高(部分节点挂不影响) | 少数派分区无法提供写服务 |
结论:
注册中心对"可用性"的要求 > 对"强一致"的要求
(宁可返回稍旧的列表,也不能整个注册服务不可用)
→ 因此"服务发现"场景更偏好 Eureka / Nacos 这类 AP(或可切 AP)的模型注意:Zookeeper 的 CP 特性在"分布式锁、Leader 选举、配置强一致"场景反而是优势——没有最好的模型,只有匹配场景的模型(见《架构 / 分布式(一)》)。
9. 为什么现在主流用 Nacos / Spring Cloud Alibaba,而不是 Eureka?
答:
- Netflix 系停更:Eureka 2.x 停止开源、Eureka 1.x 进入维护;Spring Cloud 自 2020.0 起移除 Ribbon/Hystrix/Zuul,Netflix 生态整体不再是官方方向;
- Nacos 一体两用:同时提供注册中心 + 配置中心,架构更简洁(少维护一套组件);
- Nacos 支持 AP / CP 可切换:临时实例用 Distro 协议(AP)、持久实例用 Raft 协议(CP),按需选择;
- 生态协同:与 Sentinel(限流熔断)、Seata(分布式事务)、Dubbo、Gateway 无缝配合;
- 运维友好:自带控制台、命名空间/分组隔离、权重与元数据、灰度能力。
结论:新项目优先 Spring Cloud Alibaba(Nacos + Sentinel + Gateway);只有存量系统(老 Boot 2.x + Eureka)才继续用 Netflix 系。涉及 Seata 的部分见《中间件 / Seata》。
10. Nacos 的服务注册与发现原理是什么?临时实例和持久实例有什么区别?
答: Nacos 把"服务"分为两类实例,分别用两套一致性协议:
| 临时实例(ephemeral,默认) | 持久实例(persistent) | |
|---|---|---|
| 一致性协议 | Distro(自研 AP 协议,最终一致) | Raft(CP,基于 JRaft) |
| 健康检查 | 客户端主动上报心跳(Nacos 2.x 用 gRPC 长连接) | 服务端主动探测(TCP/HTTP/MySQL) |
| 不健康时 | 自动剔除 | 标记不健康但不剔除(保留在列表中) |
| 适用 | 普通微服务实例(弹性扩缩容) | 数据库、MQ、DNS 类固定节点 |
注册与订阅流程(Nacos 2.x):
① 客户端启动 → 通过 gRPC 长连接向 Nacos 注册实例(临时实例走 Distro)
② 客户端订阅服务 → 建立推送通道
③ 服务端变更(上线/下线/权重变化)→ 主动推送(push)到订阅的客户端
④ 客户端本地缓存 + 定时对账(兜底,避免推送丢失)与 1.x 的差异(2.x 重点): 1.x 用 UDP 推送 + HTTP 长轮询(29.5s);2.x 改为 gRPC 长连接,推送实时性更好、连接数更省、性能显著提升。
加分点:能指出"Nacos 的临时实例是"客户端心跳 + AP",持久实例是"服务端探测 + CP"",并说明"为什么数据库这类固定节点适合持久实例(不该因为探测抖动被剔除)",就说明真正理解了设计动机。
11. 常见注册中心如何选型?各自的适用场景是什么?
答:
| 注册中心 | CAP | 一致性/协议 | 特点 | 适用 |
|---|---|---|---|---|
| Nacos | AP / CP 可切 | Distro / Raft | 注册 + 配置一体、控制台完善、阿里生态 | 国内首选(Spring Cloud Alibaba) |
| Eureka | AP | 对等复制 | 简单、客户端缓存、已停更 | 存量 Netflix 项目 |
| Consul | CP | Raft | 自带健康检查、KV、多数据中心支持好 | 多云/多机房、非 Java 生态(可跨语言) |
| Zookeeper | CP | ZAB | 强一致、生态成熟但选主期不可用 | Dubbo 老体系、需强一致的协调场景 |
| Etcd | CP | Raft | K8s 生态标准、Watch 机制优秀 | 云原生 / K8s 环境 |
选型原则:
Java + Spring 生态 + 要配置中心 → Nacos
多云/多语言 + 多数据中心 → Consul
K8s 原生 → Etcd(或 K8s Service + DNS 直接做服务发现)
Dubbo 老体系 → Zookeeper(逐步迁 Nacos)12. 注册中心挂了会怎样?如何保证它的高可用?
答: 要分"已经跑着的服务"和"新变化"两种情况回答:
| 状态 | 影响 |
|---|---|
| 已有服务调用 | 客户端有本地缓存的服务列表,仍可正常调用(可能列表不是最新) |
| 新实例上线 | 无法注册 → 新实例收不到流量(滚动发布可能失败) |
| 实例下线/故障 | 列表无法更新 → 可能把请求打到已下线实例(靠客户端重试/熔断兜底) |
| 配置变更 | 若注册与配置同源(Nacos),配置也刷新不了(需评估是否分离部署) |
高可用手段:
- 集群部署 + 多副本:Nacos 至少 3 节点(Raft 多数派)、Eureka 多节点对等;跨机房部署(Nacos 支持多集群);
- 客户端缓存 + 兜底:本地缓存列表、失败重试、连接失败时切换节点;
- 健康检查与自动摘除:避免把请求打到坏实例;
- 注册中心与业务解耦:不要让注册中心成为"调用链上的强依赖"——调用走客户端缓存,不每次请求都查注册中心;
- 容量与监控:长连接数、推送延迟、心跳超时率告警(Nacos 2.x 长连接数是有上限的)。
一句话:注册中心的正确设计是"平时用于变更传播,故障时不影响存量调用"——它就是分布式系统里最典型的"AP 优先"组件。
13. 服务注册与发现有哪些常见"坑"?
答:
| 坑 | 说明与对策 |
|---|---|
| 服务"假死"仍在注册表中 | JVM 存活但无法处理请求(线程池打满、GC 停顿)→ 心跳仍正常。对策:接口级健康检查(readiness 探针校验关键依赖)、优雅停机、上线前预热 |
| 优雅下线不走注册中心 | 直接 kill -9 → 客户端还持有该实例直到缓存过期 → 请求失败。对策:先调用 deregister//actuator/shutdown,配合 preStop 摘流量 |
| 注册表变更风暴 | 大批实例同时上下线导致推送/拉取风暴。对策:分批发布、客户端限流对账、缓存与去抖 |
| IP/端口注册错误 | 容器/多网卡环境下注册成 127.0.0.1 或错误网卡。对策:显式指定 spring.cloud.nacos.discovery.ip / network-interface,或使用 K8s 中正确的 Pod IP |
| 命名空间/分组隔离混乱 | dev/test/prod 混在一个命名空间 → 跨环境调用。对策:环境按 namespace、业务按 group 严格隔离 |
| 健康检查依赖链过长 | readinness 检查了所有下游 → 下游抖动导致整片实例被摘。对策:只检查"自身必需依赖",区分 liveness/readiness |
本章小结:注册中心的本质是「一份动态的服务地址表 + 一套健康判定 + 一种一致性取舍」。面试时把"Eureka 的 AP / ZK 的 CP / Nacos 的双协议"这条主线讲清,再补上"客户端缓存保证注册中心故障不影响存量调用",这一块就完整了。
