Skip to content

微服务与 Spring Cloud 组件全景

Spring Cloud 不是一个单独的 RPC 框架,也不是“注册中心 + Feign”的别名。它是一组围绕分布式服务治理的抽象和实现:服务注册发现、远程调用、客户端负载均衡、配置管理、网关、熔断限流、消息事件、链路追踪和云平台集成。

微服务真正困难的地方不是把一个 Maven 工程拆成十个,而是网络、部分失败、数据分散、版本演进和运维复杂度同时出现。本页先建立全局知识地图;每个组件的算法、源码链、Demo 和排查再进入对应专栏。

先按组件边界目录选择单个能力,再按全组件整合调用流程追踪它在启动、同步请求、异步事件和故障恢复中的位置。这样可以避免把“注册发现、配置、调用和治理”误认为一个组件。

一、学完本专栏必须会什么

  1. 判断系统是否真的需要微服务,而不是为了组件数量拆分。
  2. 从外部客户端追踪 Gateway、注册发现、负载均衡、Feign、HTTP、提供方和响应解码全过程。
  3. 区分控制面与数据面,知道注册中心通常不转发业务请求。
  4. 解释服务启动、注册、心跳、订阅、缓存和实例剔除过程。
  5. 解释 Feign 代理、请求模板、编解码、HTTP Client 和异常映射。
  6. 正确组合超时、重试、隔离、限流、熔断和降级,避免流量放大。
  7. 解释配置中心加载、覆盖、动态刷新和一致性边界。
  8. 使用 trace、metrics、logs 和 profiles 定位跨服务故障。
  9. 解释同步 HTTP/RPC、异步 MQ 和事件驱动的适用边界。
  10. 处理幂等、缓存一致性、分布式事务和最终一致性。
  11. 对比 Netflix 经典组件、Spring Cloud 当前组件、Alibaba、Kubernetes 与 Service Mesh。
  12. 说清“不这样设计会怎样”,并给出故障证据和恢复步骤。

二、微服务不等于多个 Spring Boot 项目

微服务是围绕业务能力自治部署的架构方式。一个合格服务通常拥有明确业务边界、独立发布节奏、稳定契约、自己的数据所有权和可独立观测能力。

做法是否代表微服务成熟原因
将 Controller 分成多个端口只是进程变多,边界和数据仍可能混乱
所有服务共用一套数据库表风险很高任意服务可绕过契约修改别人的数据
每次页面请求串行调用十个服务风险很高延迟和失败概率沿链路累积
服务独立拥有领域和数据是重要特征通过契约协作,降低直接耦合
独立部署但共享可观测标准是重要特征自治不等于无法统一治理

不应拆微服务的常见场景:团队很小、业务边界尚不稳定、单体尚未成为交付瓶颈、运维能力不足、拆分只为追技术热点。模块化单体通常比“分布式大泥球”更安全。

三、拆分后必须接受的分布式事实

  1. 网络调用比本地调用慢几个数量级,并且延迟有长尾。
  2. 请求可能到达但响应丢失,调用方无法仅凭超时判断业务是否执行。
  3. 一个服务失败时其他服务仍可能正常,系统会出现部分失败。
  4. 时钟存在偏差,不能把不同机器时间戳当作绝对先后证据。
  5. 重试会重复执行和放大流量,必须先考虑幂等和预算。
  6. 数据分散后,本地 ACID 事务不能自动覆盖所有服务。
  7. 注册表、配置、权限和缓存都有传播延迟,不能假设瞬时一致。
  8. 版本会并存,接口必须考虑向后兼容和灰度。
  9. 日志散落在多实例,没有 traceId 很难重建完整链路。
  10. 实例“进程存活”不代表“已经准备好接流量”。

四、控制面与数据面

这是理解组件关系最重要的模型之一。

mermaid
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、ZooKeeperNacos、Consul、Eureka、Kubernetes Service/EndpointSlice服务名映射实例,健康检测与缓存
声明式 HTTPFeignSpring Cloud OpenFeign、HTTP Interface Client、RestClient、WebClient接口代理、编解码、HTTP 传输
RPCDubboDubbo 3、gRPC代理、协议、序列化、连接和请求响应关联
客户端负载均衡RibbonSpring Cloud LoadBalancer、Dubbo Cluster从实例列表选择目标
服务端负载均衡Nginx、HAProxyGateway、Ingress、云 LB请求先到统一代理,再选择后端
API 网关Zuul 1Spring Cloud Gateway、Kong、APISIX、Envoy路由匹配、过滤器链、转发
熔断隔离HystrixResilience4j、Sentinel滑动窗口、状态机、并发或线程隔离
限流Guava、Hystrix部分能力Sentinel、Gateway、Redis Lua、Envoy令牌桶、漏桶、滑动窗口
配置中心Spring Cloud ConfigNacos Config、Apollo、Config、K8s ConfigMap/Secret配置源加载、版本、推送/轮询、刷新
消息事件RabbitMQ/Kafka客户端Spring Cloud Stream、Kafka、RabbitMQ、RocketMQ发布订阅、消费组、Offset/ACK、重试
链路追踪Sleuth、ZipkinMicrometer Tracing、OpenTelemetry、SkyWalking、Jaegertrace/span 上下文传播与采样
服务网格早期 SidecarIstio、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.7Spring 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 为例:

mermaid
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 标记为可接流量"]

关键顺序:

  1. 应用先准备 Environment,配置中心可能影响后续 Bean 条件。
  2. 创建注册客户端、负载均衡器、Feign 代理和治理组件。
  3. 提供者注册的地址必须是调用方可达地址,不能盲目注册容器内部错误 IP。
  4. 实例完成必要初始化后才应 Readiness 就绪。
  5. 停机时先摘流量、停止接新请求,再等待在途请求完成并注销实例。

八、外部请求端到端调用流程

mermaid
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 内部调用全过程

mermaid
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 让代码看起来像本地调用,但绝不能按本地调用设计:

  • 参数与返回值必须可序列化并保持契约兼容。
  • 网络可能超时、断开或返回一半。
  • 调用有连接池、队列和线程成本。
  • 返回成功前提供方可能已经提交事务。
  • 调用方取消等待不代表提供方停止执行。

完整源码链见服务调用链路OpenFeign

十、注册发现的真实原理

注册中心维护:

text
serviceName -> [instance1, instance2, instance3]

实例信息通常包含 IP、端口、健康状态、权重、区域、版本和自定义元数据。不同产品的健康模型不同:

  • Eureka 常见客户端续约、服务端剔除和客户端定期拉取缓存。
  • Nacos 同时支持不同实例健康管理模型,并提供订阅推送与本地缓存。
  • Consul 常结合 Agent 和健康检查。
  • ZooKeeper 常用临时节点和会话表达实例存活。
  • Kubernetes 通过 Service、EndpointSlice 和探针组织实例入口。

“注册中心显示 UP”不等于业务可用:健康检查可能只证明进程活着,数据库连接池、关键配置或下游依赖仍可能失败。Liveness 与 Readiness 必须区分。

十一、负载均衡不是简单轮询

客户端负载均衡输入通常是某一时刻的实例快照:

text
候选实例
→ 过滤不健康/不符合版本或区域的实例
→ 根据策略选择
→ 记录调用结果
→ 发起网络请求

常见策略:轮询、随机、权重、最少并发、响应时间感知、一致性哈希、同区域优先、灰度标签。任何策略都依赖实例信息质量;注册表滞后时,再聪明的算法也可能选到已故障实例。

客户端负载均衡与服务端负载均衡区别:

类型谁选择实例优势代价
客户端调用方进程少一跳、可结合调用结果每种客户端都要治理能力
服务端Gateway/Nginx/LB调用方简单、策略集中多一跳,代理成为关键容量点
Service MeshSidecar/节点代理业务代码弱侵入、跨语言基础设施复杂度和资源开销

十二、超时、重试、隔离、熔断、限流如何组合

mermaid
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 也重试,放大倍数继续增加。

mermaid
flowchart TD
    A["下游变慢"] --> B["上游超时"]
    B --> C["多个层级立即重试"]
    C --> D["下游收到更多并发"]
    D --> E["排队和延迟继续上升"]
    E --> B

控制方式:只在合适层重试、限制总次数、指数退避并加入随机抖动、遵守总时间预算、只重试幂等请求、熔断快速止损、监控原始请求数与实际尝试数。

十四、配置中心调用流程

mermaid
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”不等于实现最终一致。

详细内容见分布式事务CAP/BASE

十七、可观测性如何重建调用链

mermaid
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 属性。

十八、商业订单场景的组件协作

mermaid
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 使用普通类:

java
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 契约:

java
@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 和端口发送请求。

java
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;
        }
    }
}

预期输出:

text
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 中的对应职责
ServiceRegistryDiscoveryClient 本地实例视图;真实数据来自 Nacos/Eureka 等
RoundRobinLoadBalancerSpring Cloud LoadBalancer 的实例选择职责
httpGetFeign 底层 HTTP Client 的传输职责
两个 HttpServerstock-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 组件全景、版本与调用链
新旧组件和迁移技术演进与选型
端到端调用源码链服务调用链路
注册发现NacosEureka
Feign 代理与编解码OpenFeign
LoadBalancer 与 Ribbon负载均衡全过程
外部网关GatewayZuul
限流与熔断SentinelResilience4jHystrix迁移
配置治理配置中心
Trace、Metrics、Logs可观测性
消息抽象与配置广播Stream 与 Bus
RPCDubbo
分布式理论与事务分布式专栏

二十三、面试标准回答

一次 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、注册表和配置版本证明根因。