微服务与 Spring Cloud 组件全景
Spring Cloud 不是一个单独的 RPC 框架,也不是“注册中心 + Feign”的别名。它是一组围绕分布式服务治理的抽象和实现:服务注册发现、远程调用、客户端负载均衡、配置管理、网关、熔断限流、消息事件、链路追踪和云平台集成。
微服务真正困难的地方不是把一个 Maven 工程拆成十个,而是网络、部分失败、数据分散、版本演进和运维复杂度同时出现。本页先建立全局知识地图;每个组件的算法、源码链、Demo 和排查再进入对应专栏。
先按组件边界目录选择单个能力,再按全组件整合调用流程追踪它在启动、同步请求、异步事件和故障恢复中的位置。这样可以避免把“注册发现、配置、调用和治理”误认为一个组件。
一、学完本专栏必须会什么
- 判断系统是否真的需要微服务,而不是为了组件数量拆分。
- 从外部客户端追踪 Gateway、注册发现、负载均衡、Feign、HTTP、提供方和响应解码全过程。
- 区分控制面与数据面,知道注册中心通常不转发业务请求。
- 解释服务启动、注册、心跳、订阅、缓存和实例剔除过程。
- 解释 Feign 代理、请求模板、编解码、HTTP Client 和异常映射。
- 正确组合超时、重试、隔离、限流、熔断和降级,避免流量放大。
- 解释配置中心加载、覆盖、动态刷新和一致性边界。
- 使用 trace、metrics、logs 和 profiles 定位跨服务故障。
- 解释同步 HTTP/RPC、异步 MQ 和事件驱动的适用边界。
- 处理幂等、缓存一致性、分布式事务和最终一致性。
- 对比 Netflix 经典组件、Spring Cloud 当前组件、Alibaba、Kubernetes 与 Service Mesh。
- 说清“不这样设计会怎样”,并给出故障证据和恢复步骤。
二、微服务不等于多个 Spring Boot 项目
微服务是围绕业务能力自治部署的架构方式。一个合格服务通常拥有明确业务边界、独立发布节奏、稳定契约、自己的数据所有权和可独立观测能力。
| 做法 | 是否代表微服务成熟 | 原因 |
|---|---|---|
| 将 Controller 分成多个端口 | 否 | 只是进程变多,边界和数据仍可能混乱 |
| 所有服务共用一套数据库表 | 风险很高 | 任意服务可绕过契约修改别人的数据 |
| 每次页面请求串行调用十个服务 | 风险很高 | 延迟和失败概率沿链路累积 |
| 服务独立拥有领域和数据 | 是重要特征 | 通过契约协作,降低直接耦合 |
| 独立部署但共享可观测标准 | 是重要特征 | 自治不等于无法统一治理 |
不应拆微服务的常见场景:团队很小、业务边界尚不稳定、单体尚未成为交付瓶颈、运维能力不足、拆分只为追技术热点。模块化单体通常比“分布式大泥球”更安全。
三、拆分后必须接受的分布式事实
- 网络调用比本地调用慢几个数量级,并且延迟有长尾。
- 请求可能到达但响应丢失,调用方无法仅凭超时判断业务是否执行。
- 一个服务失败时其他服务仍可能正常,系统会出现部分失败。
- 时钟存在偏差,不能把不同机器时间戳当作绝对先后证据。
- 重试会重复执行和放大流量,必须先考虑幂等和预算。
- 数据分散后,本地 ACID 事务不能自动覆盖所有服务。
- 注册表、配置、权限和缓存都有传播延迟,不能假设瞬时一致。
- 版本会并存,接口必须考虑向后兼容和灰度。
- 日志散落在多实例,没有 traceId 很难重建完整链路。
- 实例“进程存活”不代表“已经准备好接流量”。
四、控制面与数据面
这是理解组件关系最重要的模型之一。
flowchart TD
A["控制面:注册中心、配置中心、规则中心"] --> B["向客户端分发实例、配置和治理规则"]
B --> C["数据面:Gateway、HTTP/RPC客户端、服务实例"]
C --> D["直接传输真实业务请求和响应"]| 平面 | 典型组件 | 主要数据 |
|---|---|---|
| 控制面 | Nacos、Eureka、Consul、Config、Sentinel Dashboard | 实例地址、健康状态、配置、路由和治理规则 |
| 数据面 | Gateway、Feign、LoadBalancer、Dubbo、HTTP Client、Service Mesh Proxy | 订单、用户、库存等业务流量 |
注册中心通常只告诉调用方“服务实例在哪里”,不会成为每次业务请求的中转站。调用方拿到实例后通常直接请求提供方。因此注册中心短暂故障时,本地缓存可能让已有调用继续;但新实例发现、故障摘除和配置更新会受到影响。
五、微服务组件全景
| 能力 | 经典组件 | 当前常见选择 | 核心原理 |
|---|---|---|---|
| 注册发现 | Eureka、ZooKeeper | Nacos、Consul、Eureka、Kubernetes Service/EndpointSlice | 服务名映射实例,健康检测与缓存 |
| 声明式 HTTP | Feign | Spring Cloud OpenFeign、HTTP Interface Client、RestClient、WebClient | 接口代理、编解码、HTTP 传输 |
| RPC | Dubbo | Dubbo 3、gRPC | 代理、协议、序列化、连接和请求响应关联 |
| 客户端负载均衡 | Ribbon | Spring Cloud LoadBalancer、Dubbo Cluster | 从实例列表选择目标 |
| 服务端负载均衡 | Nginx、HAProxy | Gateway、Ingress、云 LB | 请求先到统一代理,再选择后端 |
| API 网关 | Zuul 1 | Spring Cloud Gateway、Kong、APISIX、Envoy | 路由匹配、过滤器链、转发 |
| 熔断隔离 | Hystrix | Resilience4j、Sentinel | 滑动窗口、状态机、并发或线程隔离 |
| 限流 | Guava、Hystrix部分能力 | Sentinel、Gateway、Redis Lua、Envoy | 令牌桶、漏桶、滑动窗口 |
| 配置中心 | Spring Cloud Config | Nacos Config、Apollo、Config、K8s ConfigMap/Secret | 配置源加载、版本、推送/轮询、刷新 |
| 消息事件 | RabbitMQ/Kafka客户端 | Spring Cloud Stream、Kafka、RabbitMQ、RocketMQ | 发布订阅、消费组、Offset/ACK、重试 |
| 链路追踪 | Sleuth、Zipkin | Micrometer Tracing、OpenTelemetry、SkyWalking、Jaeger | trace/span 上下文传播与采样 |
| 服务网格 | 早期 Sidecar | Istio、Linkerd、Envoy | 将部分流量治理下沉到代理层 |
| 密钥管理 | 配置文件 | Vault、KMS、K8s Secret、云密钥服务 | 凭据分发、轮换、最小权限 |
旧组件仍需学习原理,但不应默认用于新项目:Ribbon、Hystrix、Zuul 1 已退出 Spring Cloud 新主线;新项目通常考虑 LoadBalancer、Resilience4j/Sentinel、Gateway。OpenFeign 仍广泛使用,但新项目也应了解 Spring HTTP Interface Client、RestClient 和 WebClient。
六、版本边界必须先确认
Spring Cloud 通过 Release Train 与 Spring Boot 版本配套,不能把任意 Cloud 与任意 Boot 混用。
| 项目基线 | 常见选择 | 说明 |
|---|---|---|
| JDK 8 + Boot 2.7 | Spring Cloud 2021.0.x 代际 | 重要存量基线,使用 javax.* |
| Java 17 + Boot 3.x | 对应官方兼容 Cloud Release Train | 使用 Spring 6 与 jakarta.* |
| 新项目 | 根据官方兼容矩阵选择 | 不要只看单个组件最新版本 |
具体 patch 版本应以项目依赖 BOM 和 Spring 官方兼容矩阵为准。若版本不匹配,常见表现不是编译失败,而是启动期 NoSuchMethodError、条件装配不生效、配置属性改名或 HTTP 客户端行为变化。
七、服务启动阶段发生什么
以 order-service 调用 stock-service 为例:
flowchart TD
A["stock-service 启动并创建 WebServer"] --> B["向注册中心注册实例和元数据"]
B --> C["定期心跳或由注册中心健康探测"]
D["order-service 启动"] --> E["加载配置中心属性"]
E --> F["创建 Feign Client 代理"]
F --> G["订阅或拉取 stock-service 实例"]
G --> H["实例列表进入本地缓存"]
H --> I["order-service 标记为可接流量"]关键顺序:
- 应用先准备 Environment,配置中心可能影响后续 Bean 条件。
- 创建注册客户端、负载均衡器、Feign 代理和治理组件。
- 提供者注册的地址必须是调用方可达地址,不能盲目注册容器内部错误 IP。
- 实例完成必要初始化后才应 Readiness 就绪。
- 停机时先摘流量、停止接新请求,再等待在途请求完成并注销实例。
八、外部请求端到端调用流程
flowchart TD
A["客户端发送 HTTPS 请求"] --> B["DNS、云 LB 或 Ingress"]
B --> C["Spring Cloud Gateway"]
C --> D["Route Predicate 匹配路由"]
D --> E["GlobalFilter 与 GatewayFilter"]
E --> F["鉴权、限流、灰度和 Trace 传播"]
F --> G["lb://order-service"]
G --> H["DiscoveryClient 获取实例快照"]
H --> I["LoadBalancer 选择 order 实例"]
I --> J["HTTP Client 建连或复用连接"]
J --> K["order-service Filter 与 Controller"]
K --> L["Service、数据库和下游调用"]
L --> M["响应沿原路径返回"]每层可能返回不同失败:
| 层 | 常见现象 |
|---|---|
| DNS/LB | 域名解析失败、连接拒绝、TLS 失败 |
| Gateway 路由 | 404、无可用实例、过滤器拒绝 |
| 鉴权限流 | 401、403、429 |
| LoadBalancer | 实例列表为空、选到不可达实例 |
| HTTP 传输 | 连接超时、读取超时、连接池耗尽 |
| 提供方 | 400/409/500/503、业务超时 |
| 响应解码 | Content-Type 或 JSON 结构不兼容 |
九、OpenFeign 内部调用全过程
flowchart TD
A["业务调用 StockClient.reserve"] --> B["Feign 动态代理拦截方法"]
B --> C["MethodHandler 获取方法元数据"]
C --> D["Contract、Encoder 构造 RequestTemplate"]
D --> E["RequestInterceptor 注入 Trace 与鉴权头"]
E --> F["服务名 stock-service 进入负载均衡客户端"]
F --> G["从 DiscoveryClient 获取实例快照"]
G --> H["LoadBalancer 选择实例"]
H --> I["替换为真实 scheme、host、port"]
I --> J["底层 HTTP Client 发请求"]
J --> K["提供方 Spring MVC 处理"]
K --> L["Feign Decoder 解码响应"]
L --> M["错误状态进入 ErrorDecoder 或异常映射"]Feign 让代码看起来像本地调用,但绝不能按本地调用设计:
- 参数与返回值必须可序列化并保持契约兼容。
- 网络可能超时、断开或返回一半。
- 调用有连接池、队列和线程成本。
- 返回成功前提供方可能已经提交事务。
- 调用方取消等待不代表提供方停止执行。
十、注册发现的真实原理
注册中心维护:
serviceName -> [instance1, instance2, instance3]实例信息通常包含 IP、端口、健康状态、权重、区域、版本和自定义元数据。不同产品的健康模型不同:
- Eureka 常见客户端续约、服务端剔除和客户端定期拉取缓存。
- Nacos 同时支持不同实例健康管理模型,并提供订阅推送与本地缓存。
- Consul 常结合 Agent 和健康检查。
- ZooKeeper 常用临时节点和会话表达实例存活。
- Kubernetes 通过 Service、EndpointSlice 和探针组织实例入口。
“注册中心显示 UP”不等于业务可用:健康检查可能只证明进程活着,数据库连接池、关键配置或下游依赖仍可能失败。Liveness 与 Readiness 必须区分。
十一、负载均衡不是简单轮询
客户端负载均衡输入通常是某一时刻的实例快照:
候选实例
→ 过滤不健康/不符合版本或区域的实例
→ 根据策略选择
→ 记录调用结果
→ 发起网络请求常见策略:轮询、随机、权重、最少并发、响应时间感知、一致性哈希、同区域优先、灰度标签。任何策略都依赖实例信息质量;注册表滞后时,再聪明的算法也可能选到已故障实例。
客户端负载均衡与服务端负载均衡区别:
| 类型 | 谁选择实例 | 优势 | 代价 |
|---|---|---|---|
| 客户端 | 调用方进程 | 少一跳、可结合调用结果 | 每种客户端都要治理能力 |
| 服务端 | Gateway/Nginx/LB | 调用方简单、策略集中 | 多一跳,代理成为关键容量点 |
| Service Mesh | Sidecar/节点代理 | 业务代码弱侵入、跨语言 | 基础设施复杂度和资源开销 |
十二、超时、重试、隔离、熔断、限流如何组合
flowchart TD
A["请求进入"] --> B["入口限流"]
B --> C["并发或线程隔离"]
C --> D["带时间预算的远程调用"]
D --> E{"是否为可重试失败"}
E -- "是且幂等" --> F["有限重试与退避"]
E -- "否" --> G["记录失败"]
F --> G
G --> H["滑动窗口统计"]
H --> I{"达到熔断阈值"}
I -- "是" --> J["打开熔断,快速失败"]
I -- "否" --> K["继续允许调用"]
J --> L["按业务语义降级或返回失败"]| 机制 | 保护什么 | 常见误用 |
|---|---|---|
| 超时 | 限制单次等待 | 只设置读取超时,不设连接池获取超时 |
| 重试 | 恢复瞬时故障 | 对扣款、创建等非幂等操作盲目重试 |
| 隔离 | 限制故障占用资源 | 线程池过小造成正常流量被拒绝 |
| 熔断 | 失败集中时快速失败 | 把业务参数错误计入系统熔断 |
| 限流 | 控制进入速率或并发 | 只在最下游限流,上游资源已耗尽 |
| 降级 | 在失败时提供可接受替代 | 核心交易返回伪成功数据 |
时间预算必须逐层递减。若 Gateway 超时 3 秒,order-service 调 payment-service 却配置 5 秒读取超时,上游会先断开,而下游仍占用资源执行。
十三、重试为什么会放大故障
调用链 A → B → C,每层最多重试 3 次,最坏情况下 C 可能承受接近 3 × 3 = 9 次尝试;如果 Gateway 也重试,放大倍数继续增加。
flowchart TD
A["下游变慢"] --> B["上游超时"]
B --> C["多个层级立即重试"]
C --> D["下游收到更多并发"]
D --> E["排队和延迟继续上升"]
E --> B控制方式:只在合适层重试、限制总次数、指数退避并加入随机抖动、遵守总时间预算、只重试幂等请求、熔断快速止损、监控原始请求数与实际尝试数。
十四、配置中心调用流程
flowchart TD
A["应用启动早期"] --> B["根据应用名、环境、namespace加载远程配置"]
B --> C["配置加入 Environment PropertySource"]
C --> D["自动配置条件与 Binder 读取最终值"]
D --> E["创建业务 Bean"]
E --> F["运行期监听配置变更"]
F --> G["刷新支持动态更新的 Bean 或发布事件"]配置中心不是自动一致的魔法:实例接收变更有先后,已有连接池/HTTP Client 未必自动重建,刷新一半失败会产生混合状态。关键配置需要版本、灰度、审计、回滚和一致性验证;数据库密码、Token 等敏感值还需要密钥管理和轮换方案。
十五、同步调用与异步事件如何选择
| 问题 | 同步 HTTP/RPC | 异步 MQ/事件 |
|---|---|---|
| 是否立刻需要结果 | 适合 | 不适合直接返回实时结果 |
| 时间耦合 | 调用时双方都要可用 | 消费者可稍后处理 |
| 失败表现 | 超时/错误直接返回 | 积压、重试、死信 |
| 一致性 | 容易形成长调用链 | 常配合最终一致性 |
| 调试 | 直观但链路可能很长 | 需要事件追踪和消费状态 |
订单创建后发送通知、积分、搜索索引更新通常适合事件;支付确认、库存预占是否同步取决于业务是否必须在当前响应前得到结果。异步不是“更快”,只是将完成时间和失败处理转移到消息链路。
十六、服务拆分后的数据一致性
当订单、库存、支付拥有独立数据库后,一个本地 @Transactional 无法覆盖所有服务。常见方案:
- 强一致需求:XA/JTA、TCC,但资源占用和实现复杂。
- 长事务业务:Saga,按步骤执行并设计补偿。
- 事件驱动:本地消息表/Outbox、事务消息、消费者幂等。
- 状态查询:最大努力通知与主动对账。
任何最终一致方案都必须有:明确状态机、幂等键、可靠重试、失败记录、补偿、告警、对账和人工处理入口。仅仅“发一条 MQ”不等于实现最终一致。
十七、可观测性如何重建调用链
flowchart TD
A["入口生成或接收 traceId"] --> B["Gateway 创建 Span"]
B --> C["通过 HTTP/RPC Header 传播上下文"]
C --> D["每个服务创建子 Span"]
D --> E["日志写入 traceId 与 spanId"]
E --> F["指标统计吞吐、错误和延迟"]
F --> G["Trace 后端还原完整拓扑"]三类信号互补:
- Metrics 告诉你“多少请求变慢、从什么时候开始”。
- Traces 告诉你“慢在哪一个 Span”。
- Logs 告诉你“该步骤具体异常和业务上下文”。
不要把用户 ID、订单号作为 Metrics 高基数标签;它们适合脱敏后进入日志和 Trace 属性。
十八、商业订单场景的组件协作
flowchart TD
A["客户端创建订单"] --> B["Gateway鉴权、限流、路由"]
B --> C["order-service"]
C --> D["Feign调用stock-service预占库存"]
D --> E["注册发现与LoadBalancer选实例"]
E --> F["库存服务幂等预占"]
F --> G["订单与Outbox同事务落库"]
G --> H["投递OrderCreated事件"]
H --> I["支付、通知、搜索消费者"]
I --> J["Trace、指标和日志贯穿"]这里的组件不能互相替代:注册中心提供地址,LoadBalancer 选实例,Feign 构造调用,Sentinel/Resilience4j保护调用,MQ解耦后续事件,Outbox处理数据库与事件的一致性,Trace负责诊断。
十九、JDK 8 服务契约 Demo
JDK 8 不支持 record,DTO 使用普通类:
public class StockResponse {
private Long productId;
private Integer available;
public StockResponse() {
}
public StockResponse(Long productId, Integer available) {
this.productId = productId;
this.available = available;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public Integer getAvailable() {
return available;
}
public void setAvailable(Integer available) {
this.available = available;
}
}Feign 契约:
@FeignClient(name = "stock-service")
public interface StockClient {
@GetMapping("/internal/stocks/{productId}")
StockResponse getStock(
@PathVariable("productId") Long productId,
@RequestHeader("X-Request-Id") String requestId);
}接口方法只是声明;启动期由 Feign 注册代理,运行期代理生成 HTTP 请求。X-Request-Id 用于幂等或追踪时必须由调用链稳定传递,不能每次重试重新生成业务幂等号。
19.1 纯 JDK 8 可运行原理实验
下面的 Demo 不是伪造 Spring Cloud 源码,而是用最少代码复现它的核心数据流:注册表保存 serviceName -> 实例列表,轮询负载均衡选择实例,HTTP 客户端向真实 IP 和端口发送请求。
import com.sun.net.httpserver.HttpServer;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class MicroserviceCallDemo {
public static void main(String[] args) throws Exception {
HttpServer node1 = startStockNode(18081, "stock-node-1");
HttpServer node2 = startStockNode(18082, "stock-node-2");
try {
ServiceRegistry registry = new ServiceRegistry();
registry.register("stock-service", Arrays.asList(
new ServiceInstance("127.0.0.1", 18081),
new ServiceInstance("127.0.0.1", 18082)));
RoundRobinLoadBalancer loadBalancer =
new RoundRobinLoadBalancer();
for (int i = 0; i < 4; i++) {
List<ServiceInstance> snapshot =
registry.getInstances("stock-service");
ServiceInstance selected = loadBalancer.choose(snapshot);
String response = httpGet(
"http://" + selected.host + ":"
+ selected.port + "/stock");
System.out.println("selected=" + selected
+ ", response=" + response);
}
} finally {
node1.stop(0);
node2.stop(0);
}
}
private static HttpServer startStockNode(
int port, String nodeName) throws Exception {
HttpServer server = HttpServer.create(
new InetSocketAddress("127.0.0.1", port), 0);
server.createContext("/stock", exchange -> {
byte[] body = (nodeName + ":available=10")
.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, body.length);
try (OutputStream output = exchange.getResponseBody()) {
output.write(body);
}
});
server.start();
return server;
}
private static String httpGet(String address) throws Exception {
HttpURLConnection connection =
(HttpURLConnection) new URL(address).openConnection();
connection.setConnectTimeout(1000);
connection.setReadTimeout(2000);
connection.setRequestMethod("GET");
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
connection.getInputStream(),
StandardCharsets.UTF_8))) {
return reader.readLine();
} finally {
connection.disconnect();
}
}
static class ServiceRegistry {
private final Map<String, List<ServiceInstance>> registry =
new HashMap<String, List<ServiceInstance>>();
void register(String serviceName,
List<ServiceInstance> instances) {
registry.put(serviceName,
new ArrayList<ServiceInstance>(instances));
}
List<ServiceInstance> getInstances(String serviceName) {
List<ServiceInstance> instances = registry.get(serviceName);
if (instances == null || instances.isEmpty()) {
throw new IllegalStateException(
"No instance for " + serviceName);
}
return new ArrayList<ServiceInstance>(instances);
}
}
static class RoundRobinLoadBalancer {
private final AtomicInteger position = new AtomicInteger();
ServiceInstance choose(List<ServiceInstance> instances) {
int index = Math.floorMod(
position.getAndIncrement(), instances.size());
return instances.get(index);
}
}
static class ServiceInstance {
private final String host;
private final int port;
ServiceInstance(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public String toString() {
return host + ":" + port;
}
}
}预期输出:
selected=127.0.0.1:18081, response=stock-node-1:available=10
selected=127.0.0.1:18082, response=stock-node-2:available=10
selected=127.0.0.1:18081, response=stock-node-1:available=10
selected=127.0.0.1:18082, response=stock-node-2:available=10与真实组件的映射:
| Demo 类 | Spring Cloud 中的对应职责 |
|---|---|
ServiceRegistry | DiscoveryClient 本地实例视图;真实数据来自 Nacos/Eureka 等 |
RoundRobinLoadBalancer | Spring Cloud LoadBalancer 的实例选择职责 |
httpGet | Feign 底层 HTTP Client 的传输职责 |
| 两个 HttpServer | stock-service 的两个独立实例 |
真实 Spring Cloud 还会处理注册订阅、健康状态、元数据过滤、连接池、编解码、错误映射、重试、熔断和 Trace;这个实验只用于把“服务名不是网络地址,必须先解析并选择真实实例”跑通。
二十、服务调用故障排查 Runbook
20.1 服务名找不到实例
检查调用方服务名、namespace/group、注册中心地址、提供者是否注册、健康状态、元数据过滤、本地缓存和网络。注册中心控制台有实例不代表调用方处于同一隔离空间。
20.2 有实例但连接拒绝
检查注册 IP/端口是否可达、容器端口映射、安全组、服务是否只监听 localhost、实例是否在优雅停机窗口、注册表是否尚未剔除旧地址。
20.3 连接超时与读取超时
连接超时关注 DNS、路由、防火墙、目标监听和连接池;读取超时说明连接可能已建立,重点看提供方线程池、GC、数据库、锁、下游调用和响应体大小。
20.4 只有部分请求失败
按目标实例维度聚合错误,检查某个实例版本、配置、节点资源或连接池是否异常;同时检查负载均衡是否倾斜、灰度元数据是否错误。
20.5 上游超时但下游成功
检查上游时间预算、网关超时和网络响应;业务写操作必须通过 requestId 查询最终结果,不能换新业务号盲目重试。
完整决策树见服务调用链路。
二十一、常见反模式
| 反模式 | 后果 |
|---|---|
| 服务共享并随意修改同一数据库 | 边界失效,无法独立演进 |
| 循环调用 A→B→C→A | 超时、线程占用和排查复杂度爆炸 |
| 每一层都配置 3 次重试 | 故障流量指数放大 |
| 所有配置动态刷新 | 实例状态不一致、连接对象未重建 |
| Gateway 编写核心业务 | 网关成为巨型单点和发布瓶颈 |
| fallback 返回伪成功 | 数据错误被隐藏,后续难以补偿 |
| 注册中心作为业务数据库 | 数据模型和一致性能力不匹配 |
| 一个请求聚合几十个同步服务 | 长尾延迟和失败概率不断累积 |
| 只监控平均耗时 | P99 尾延迟和少量坏实例被掩盖 |
二十二、组件深入学习入口
| 主题 | 深入页面 |
|---|---|
| Spring Cloud Alibaba 总装关系 | Alibaba 组件全景、版本与调用链 |
| 新旧组件和迁移 | 技术演进与选型 |
| 端到端调用源码链 | 服务调用链路 |
| 注册发现 | Nacos、Eureka |
| Feign 代理与编解码 | OpenFeign |
| LoadBalancer 与 Ribbon | 负载均衡全过程 |
| 外部网关 | Gateway、Zuul |
| 限流与熔断 | Sentinel、Resilience4j、Hystrix迁移 |
| 配置治理 | 配置中心 |
| Trace、Metrics、Logs | 可观测性 |
| 消息抽象与配置广播 | Stream 与 Bus |
| RPC | Dubbo |
| 分布式理论与事务 | 分布式专栏 |
二十三、面试标准回答
一次 Spring Cloud 调用通常分为控制面和数据面。提供者启动后向 Nacos、Eureka 等注册服务名、IP、端口和元数据;消费者订阅或拉取实例并维护本地缓存。业务调用 Feign 接口时,动态代理根据 Contract 和注解构造 HTTP 请求,RequestInterceptor 注入鉴权与 Trace 上下文,Spring Cloud LoadBalancer 从实例快照选择目标,底层 HTTP Client 复用连接发送请求。提供方按 Servlet/Spring MVC 链处理并返回,Feign Decoder 再转换响应。Gateway 负责外部路由;超时、有限重试、隔离、限流、熔断和降级保护数据面;Micrometer/OpenTelemetry 重建跨服务链路。注册中心通常不转发每次业务流量,只分发实例信息。
本章小结
微服务治理可以归纳为四条线:控制面分发实例、配置和规则;数据面负责真实 HTTP/RPC/消息流量;可靠性机制控制超时、重试、隔离、熔断和限流;一致性与可观测机制负责跨服务状态闭环和故障证据。
掌握组件不等于记住注解,而是能说清组件处于哪条线、接收什么输入、如何改变请求或状态、失败会影响谁,以及如何用日志、指标、Trace、注册表和配置版本证明根因。
