Skip to content

六、SpringCloud 生态(12 题)

6.1 ★★★微服务架构的核心组件有哪些?请描述一个完整的微服务请求链路。微服务架构的优缺点是什么?

📖 点击查看答案

核心组件

  1. 服务注册与发现:Nacos、Eureka、Consul、Zookeeper
  2. 服务调用:OpenFeign、Dubbo、RestTemplate + LoadBalancer
  3. 负载均衡:Spring Cloud LoadBalancer、Ribbon(已维护模式)
  4. API 网关:Spring Cloud Gateway、Zuul
  5. 配置中心:Nacos Config、Apollo、Spring Cloud Config
  6. 熔断降级:Sentinel、Hystrix(停止维护)、Resilience4j
  7. 分布式事务:Seata、RocketMQ 事务消息
  8. 链路追踪:SkyWalking、Zipkin、Jaeger
  9. 消息总线:Spring Cloud Bus
  10. 分布式调度:XXL-Job、ElasticJob

完整请求链路(以一次外部请求为例):

  1. 客户端请求 → 网关(Gateway,统一鉴权、限流、路由)
  2. 网关从注册中心获取服务实例列表
  3. 网关通过负载均衡选择一个实例,转发请求
  4. 服务 A 处理请求,若需调用服务 B:
    • Feign 生成动态代理,从注册中心获取服务 B 实例
    • 负载均衡选择实例发起 HTTP/RPC 调用
    • 熔断器(Sentinel)拦截,判断是否降级
  5. 调用过程中,链路追踪生成 Trace ID 并透传(通过 HTTP Header / MDC)
  6. 服务从配置中心读取配置,配置变更通过消息总线推送刷新
  7. 异步场景通过消息队列(RocketMQ/Kafka)解耦
  8. 跨服务写操作通过分布式事务保证一致性(Seata AT/TCC)
Client → Gateway → Service A → (Feign + LB) → Service B

                         Sentinel(熔断限流)

                         Nacos(注册+配置)

                     SkyWalking(链路追踪)

优点

  • 服务拆分,职责单一,易于维护和扩展
  • 独立部署、独立扩缩容,按需分配资源
  • 技术栈灵活,不同服务可用不同语言
  • 故障隔离,单个服务故障不会击垮整个系统

缺点

  • 分布式复杂性:网络问题、数据一致性、分布式事务
  • 运维成本高:服务数量多,监控、日志、部署复杂
  • 接口调用成本高(HTTP/RPC 序列化),相比单体进程内调用慢
  • 测试调试困难,需要链路追踪辅助
  • 团队协作成本:接口契约、版本管理

6.2 ★★服务注册与发现的完整流程是什么?Server/Client、心跳机制是怎样工作的?

📖 点击查看答案

角色

  • Server(注册中心服务端):Nacos Server、Eureka Server,存储服务实例信息
  • Client(服务提供者/消费者):启动时注册自身,并拉取服务列表

服务注册流程(服务提供者):

  1. 服务启动时,Client 向 Server 发送注册请求,包含:服务名、IP、端口、元数据(权重、版本等)
  2. Server 将信息存储在注册表中(内存 Map)
  3. Server 返回注册成功,Client 开启定时任务发送心跳

服务发现流程(服务消费者):

  1. Client 启动时从 Server 拉取服务实例列表并缓存到本地
  2. Server 维护长连接(Nacos UDP 推送)或 Client 定时拉取(Eureka 30s)
  3. 服务列表变更时,Server 主动推送(Nacos)或 Client 定时拉取刷新

心跳机制

  • Client 定时(Nacos 5s、Eureka 30s)向 Server 发送心跳,表明自己存活
  • Server 在一定时间内未收到心跳,标记实例为不健康(Nacos 15s),再超时摘除(Nacos 30s、Eureka 90s)
  • Nacos 1.x 还区分临时实例(客户端心跳)和持久实例(服务端主动探测)

服务下线

  • 正常下线:Client 发送 deregister 请求,Server 立即摘除
  • 异常宕机:心跳超时后由 Server 自动摘除
java
// Nacos 客户端注册核心
NamingService naming = NacosNamingManager.createNamingService(serverAddr);
naming.registerInstance(serviceName, ip, port);

// 心跳任务:延迟 1s,间隔 5s
scheduledExecutor.scheduleWithFixedDelay(() -> {
    serverProxy.sendBeat(beatInfo, false);
}, 1000, 5000, TimeUnit.MILLISECONDS);

保护机制

  • Eureka 自我保护:15 分钟内心跳失败比例 > 85% 时,不再剔除实例(防止网络分区误删)
  • Nacos:类似保护阈值,当健康实例比例 < 阈值时返回所有实例(包括不健康的)

6.3 ★★★★Nacos 作为注册中心和配置中心的原理分别是什么?如何实现配置的动态刷新?@RefreshScope 的原理和局限性?

📖 点击查看答案

Nacos 注册中心原理

  • 基于 Distro 协议(AP)或 Raft 协议(CP)实现集群数据同步
  • 临时实例:客户端发送心跳维持,AP 模式,Distro 协议同步
  • 持久实例:服务端主动探测,CP 模式,Raft 协议同步
  • 客户端通过长轮询(Long Polling)+ UDP 推送获取服务列表变更

Nacos 配置中心原理

  1. 客户端启动时从 Nacos 拉取配置文件(dataId + group)
  2. 客户端通过长轮询监听配置变更:默认每 30s 向 Nacos 发送请求,带配置的 MD5 值
  3. Nacos 比较客户端 MD5 和服务端 MD5,不一致则立即返回变更的 dataId;一致则 hold 住请求 30s
  4. 客户端收到变更通知后,拉取最新配置并触发 Listener 回调
  5. Nacos 服务端内部通过 AsyncContext 实现 long polling 的 hold

动态刷新机制

方式一:@RefreshScope + /actuator/refresh

  • 配置变更时调用 /actuator/refresh 或监听 Nacos 配置变更
  • 触发 RefreshEventContextRefresher 销毁所有 @RefreshScope 标注的 Bean
  • 下次访问时重新创建 Bean,注入最新配置

方式二:@NacosConfigListener / @ConfigurationProperties

  • 配置变更时自动绑定到 @ConfigurationProperties

@RefreshScope 原理

  1. @RefreshScope 标记的 Bean 会被 RefreshScope(作用域)管理
  2. 真正的 Bean 被代理(CGLIB),实际实例缓存到 RefreshScopeConcurrentHashMap
  3. 配置刷新时,调用 RefreshScope#refreshAll() 清空缓存
  4. 下次访问 Bean 时,重新走 Bean 创建流程,注入最新配置
java
@RestController
@RefreshScope
public class ConfigController {
    @Value("${app.name}")
    private String appName;   // 配置刷新后,下次访问时为新值
}

局限性

  1. 只对 @RefreshScope 标注的 Bean 生效,普通 @Value 不会刷新
  2. @RefreshScope Bean 需要重新创建,有一定性能开销
  3. 静态变量、@PostConstruct 初始化的字段不会自动刷新
  4. 数据库连接池、线程池等已初始化的资源不会因配置刷新而重建(需配合 @RefreshScope 或自定义监听)
  5. 刷新会触发 Bean 销毁重建,期间有短暂不可用
  6. @ConfigurationProperties 的 Bean 默认会自动刷新(Spring Cloud 2020+ 需手动配置)

6.4 ★★★Nacos 的 AP/CP 模式切换是什么意思?Nacos 和 Eureka 的对比?注册中心 AP vs CP 如何选择?

📖 点击查看答案

Nacos 的 AP/CP 模式

  • AP 模式(默认):Distro 协议,每个节点都能处理写请求并同步,强调可用性,允许短暂数据不一致
    • 适用于临时实例(服务注册),客户端心跳维持
    • 集群脑裂时各节点仍可独立提供服务列表
  • CP 模式:Raft 协议,只有 Leader 处理写请求,半数以上节点确认才提交,强调一致性
    • 适用于持久实例(如数据库、DNS),服务端主动探测
    • Leader 选举期间不可写

切换方式

shell
# 临时实例默认 AP,持久实例默认 CP
curl -X PUT 'http://nacos:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
  • 临时实例只能用 AP,持久实例只能用 CP
  • @NacosValue / spring.cloud.nacos.discovery.ephemeral=false 设置持久实例

Nacos vs Eureka

特性NacosEureka
CAP 模式AP + CP 可切换仅 AP
配置中心是(集成)
长连接长轮询 + UDP 推送定时拉取(30s)
实例类型临时 + 持久仅临时
健康检查心跳 + 主动探测心跳
集群协议Distro + RaftP2P 复制
一致性AP 模式最终一致最终一致
保护机制保护阈值自我保护
社区活跃度活跃(阿里维护)2.x 停止维护

AP vs CP 选择

  • 选 AP(大多数微服务场景):
    • 服务发现优先可用性,短暂脏数据可接受
    • 网络分区时各分区仍可独立提供服务
    • 临时实例、心跳维持的注册场景
  • 选 CP(强一致性要求):
    • 配置管理、分布式协调(如选主、分布式锁)
    • 数据库、存储服务等基础组件注册
    • 不能容忍数据不一致的场景

实际选择:Spring Cloud Alibaba 默认 Nacos(AP),Spring Cloud Netflix 用 Eureka(AP),Zookeeper/Consul 偏 CP。微服务注册中心一般选 AP,因为注册中心故障不应影响服务调用(客户端有本地缓存)。


6.5 ★★★Spring Cloud Ribbon 如何实现负载均衡?有哪些负载均衡策略?如何自定义负载均衡规则?

📖 点击查看答案

Ribbon 工作原理

  1. @LoadBalanced 注解标记 RestTemplate,为其添加拦截器 LoadBalancerInterceptor
  2. 拦截器拦截请求,从 URL 提取服务名(如 http://user-service/users/1 中的 user-service
  3. 调用 LoadBalancerClient#choose(),通过 ILoadBalancer 选择实例
  4. ILoadBalancer 从注册中心获取实例列表,调用 IRule#choose() 根据策略选择实例
  5. 用真实 IP:Port 替换服务名,发起请求
java
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}
// 调用:restTemplate.getForObject("http://user-service/users/1", User.class);

内置负载均衡策略(实现 IRule):

  1. RoundRobinRule(默认):轮询,按顺序依次选择
  2. RandomRule:随机选择
  3. WeightedResponseTimeRule:根据响应时间加权,响应快的权重高
  4. BestAvailableRule:选择并发数最小的实例
  5. AvailabilityFilteringRule:过滤熔断器跳闸的实例,选择并发数小的
  6. ZoneAvoidanceRule(默认,Spring Cloud Alibaba 默认):综合区域可用性和实例可用性过滤
  7. RetryRule:轮询 + 失败重试

配置策略

yaml
user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    ConnectTimeout: 3000
    ReadTimeout: 5000
    MaxAutoRetries: 0              # 同实例重试次数
    MaxAutoRetriesNextServer: 1    # 切换实例重试次数
    OkToRetryOnAllOperations: true

自定义负载均衡规则

java
public class MyRule extends AbstractLoadBalancerRule {
    @Override
    public Server choose(Object key) {
        List<Server> servers = getLoadBalancer().getReachableServers();
        // 自定义选择逻辑,如基于灰度版本
        return servers.stream()
            .filter(s -> s.getMetaInfo().getInstanceId().contains("gray"))
            .findFirst()
            .orElse(servers.get(0));
    }
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {}
}

@Configuration
public class RibbonConfig {
    @Bean
    public IRule rule() { return new MyRule(); }
}

Spring Cloud LoadBalancer(替代 Ribbon):

  • Spring Cloud 2020+ 推荐,基于 ReactorLoadBalancer
  • 内置 RoundRobinLoadBalancer、RandomLoadBalancer
  • 通过 @LoadBalancerClient(name = "user-service", configuration = MyLBConfig.class) 自定义

6.6 ★★★OpenFeign 的底层工作原理是什么?如何进行性能调优?连接池和超时如何配置?

📖 点击查看答案

OpenFeign 工作原理

  1. @EnableFeignClients 扫描 @FeignClient 接口
  2. 通过 FeignClientFactoryBean 创建代理对象(JDK 动态代理 InvocationHandler
  3. 调用方法时,代理对象将方法转为 RequestTemplate(URL、参数、Header)
  4. 通过负载均衡器选择实例,替换服务名为真实 IP:Port
  5. 通过 Client(默认 HttpURLConnection)发起 HTTP 请求
  6. Decoder 解码响应,Encoder 编码请求体
  7. 拦截器链(RequestInterceptor)统一处理鉴权、日志等
java
@FeignClient(name = "user-service", fallback = UserFallback.class)
public interface UserClient {
    @GetMapping("/users/{id}")
    User getUser(@PathVariable Long id);
}

性能调优

1. 替换默认 HTTP Client 为连接池(默认 HttpURLConnection 无连接池,每次新建连接):

xml
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
<!-- 或 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>
yaml
feign:
  httpclient:
    enabled: true
    max-connections: 200          # 最大连接数
    max-connections-per-route: 50 # 每个路由最大连接数
    connection-timeout: 2000
    time-to-live: 900            # 连接保活时间(秒)
  okhttp:
    enabled: true

2. 超时配置

yaml
feign:
  client:
    config:
      default:                  # 全局配置
        connect-timeout: 2000   # 连接超时 2s
        read-timeout: 5000      # 读取超时 5s
      user-service:             # 针对特定服务
        read-timeout: 10000
  • Ribbon 超时若配置会覆盖 Feign 超时(ribbon.ReadTimeout),Spring Cloud LoadBalancer 时以 Feign 超时为准

3. 开启 Gzip 压缩

yaml
feign:
  compression:
    request:
      enabled: true
      mime-types: application/json
      min-request-size: 2048
    response:
      enabled: true

4. 日志级别(生产环境用 NONE/BASIC,性能影响最小):

yaml
feign:
  client:
    config:
      default:
        logger-level: BASIC   # NONE/BASIC/HEADERS/FULL

5. 开启熔断降级:避免级联故障,配合 Sentinel/Hystrix 的 fallback

6. 异步调用:Feign + CompletableFuture 或 Reactor 异步化,避免阻塞工作线程


6.7 ★★★★Spring Cloud Gateway 的核心架构是什么?Predicate(谓词)和 Filter(过滤器)是如何工作的?

📖 点击查看答案

核心架构

  • 基于 Spring WebFlux + Reactor + Netty,非阻塞异步
  • 核心概念:Route(路由)Predicate(谓词)Filter(过滤器)

Route(路由):由 ID、目标 URI、Predicate 集合、Filter 集合组成,是网关的基本构建块。

Predicate(谓词):匹配 HTTP 请求,决定路由到哪个服务,基于 Java 8 Predicate

  • Path:路径匹配,Path=/api/user/**
  • Method:HTTP 方法匹配,Method=GET,POST
  • Header:请求头匹配,Header=X-Request-Id, \d+
  • Query:查询参数匹配,Query=token, \d+
  • Host:Host 匹配,Host=**.example.com
  • After / Before / Between:时间匹配
  • Cookie:Cookie 匹配
  • RemoteAddr:IP 匹配

Filter(过滤器):对请求/响应进行处理:

  • GatewayFilter(局部):作用于单个路由,如 AddRequestHeaderPrefixPath
  • GlobalFilter(全局):作用于所有路由,如鉴权、日志、限流

工作流程

  1. 客户端请求 → HandlerMapping 匹配路由(基于 Predicate)
  2. 匹配成功 → 进入过滤器链
  3. 过滤器分前置(pre)后置(post),前置在请求转发前执行,后置在响应返回后执行
  4. FilteringWebHandler 按顺序执行:GlobalFilter + 当前路由的 GatewayFilter
  5. 通过代理转发到目标服务
  6. 响应逆向通过过滤器链返回
yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
            - Method=GET,POST
            - Header=X-Request-Id, \d+
          filters:
            - StripPrefix=2          # 去掉前缀 /api/user
            - AddRequestHeader=X-Gateway, scg
            - name: RequestRateLimiter  # 限流
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20

自定义 GlobalFilter(鉴权示例):

java
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        // 解析 token,注入用户信息
        exchange.getRequest().mutate().header("X-User-Id", userId).build();
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() { return -100; }   // 越小越先执行
}

过滤器顺序Ordered 接口或 @Order 注解决定执行顺序,数值小的先执行。pre 阶段按 order 升序,post 阶段按 order 降序。


6.8 ★★★★Sentinel 的限流、熔断、降级原理及规则有哪些?限流算法(滑动窗口、令牌桶、漏桶)的工作原理是什么?Sentinel 和 Hystrix 的区别?

📖 点击查看答案

Sentinel 核心概念

  • 资源(Resource):被保护的方法、接口、代码块
  • 规则(Rule):流控、熔断、热点、系统、授权规则
  • Slot Chain:责任链模式,每个 Slot 负责一个职责(统计、限流、熔断等)

Slot 责任链(核心):

  1. NodeSelectorSlot:构建资源调用树
  2. ClusterBuilderSlot:构建集群节点统计
  3. StatisticSlot:实时统计 QPS、RT、线程数等(基于滑动窗口)
  4. FlowSlot:流控规则判断
  5. DegradeSlot:熔断规则判断
  6. AuthoritySlot:授权规则
  7. SystemSlot:系统级规则(CPU、Load)

限流(Flow Control)规则

  • 阈值类型:QPS、并发线程数
  • 流控行为:直接拒绝、Warm Up(预热)、排队等待
  • 流控模式:直接、关联(关联资源达到阈值时限流自身)、链路(只限流从某资源来的调用)

熔断(Circuit Breaking)规则

  • 慢调用比例:RT > 阈值的请求比例超阈值时熔断
  • 异常比例:异常比例超阈值时熔断
  • 异常数:异常数超阈值时熔断
  • 熔断状态:CLOSED → OPEN(熔断) → HALF_OPEN(半开试探) → CLOSED

降级:通过 @SentinelResourceblockHandler / fallback 实现

java
@SentinelResource(value = "queryUser",
    blockHandler = "queryUserBlockHandler",     // 限流熔断时调用
    fallback = "queryUserFallback")              // 业务异常时调用
public User queryUser(Long id) { /* ... */ }

public User queryUserBlockHandler(Long id, BlockException ex) {
    return new User(-1L, "降级用户");
}
public User queryUserFallback(Long id, Throwable e) {
    return new User(-1L, "异常降级");
}

限流算法

1. 滑动窗口(Sentinel 默认)

  • 将时间分为样本窗口(如 1s 分 2 个 500ms 窗口)
  • 统计当前窗口 + 前一个窗口的请求数
  • 窗口滑动时,旧窗口数据过期,新窗口创建
  • Sentinel 用 LeapArray 实现,每个窗口是 WindowWrap,包含统计值

2. 令牌桶(Guava RateLimiter)

  • 以固定速率向桶中放令牌,桶满则丢弃
  • 请求到达时取令牌,有则通过,无则拒绝/等待
  • 允许突发流量(桶中积累的令牌可瞬间消耗)

3. 漏桶

  • 请求进入漏桶,漏桶以固定速率漏出(处理)
  • 桶满则拒绝新请求
  • 平滑流量,不允许突发

Sentinel vs Hystrix

特性SentinelHystrix
隔离策略信号量隔离线程池/信号量隔离
熔断策略慢调用比例/异常比例/异常数异常比例
实时统计滑动窗口(LeapArray)滑动窗口(RxJava)
动态规则支持(Nacos/控制台)配置文件
限流支持(QPS/线程数)不支持(仅熔断)
系统自适应支持不支持
控制台Sentinel DashboardHystrix Dashboard(监控)
维护状态活跃停止维护

6.9 ★★Sentinel 的流量整形和热点参数限流是如何实现的?

📖 点击查看答案

流量整形(Traffic Shaping)

1. 匀速排队(RateLimiterController)

  • 基于漏桶算法思想
  • 记录上次请求通过的时间,计算当前请求应等待的时间
  • 若等待时间 < 超时时间,则排队等待;否则拒绝
  • 实现 QPS 匀速通过,削峰填谷
java
// 伪代码
long expectedTime = passTime + 1000 / count;   // 预期通过时间
long currentTime = System.currentTimeMillis();
if (currentTime < expectedTime) {
    long waitTime = expectedTime - currentTime;
    if (waitTime > maxQueueingTimeMs) {
        return false;  // 拒绝
    }
    Thread.sleep(waitTime);  // 等待
}
passTime = currentTime;
return true;

2. 预热 Warm Up(WarmUpController)

  • 基于令牌桶 + 冷启动因子
  • 系统冷启动时阈值低,随时间逐渐升高到设定阈值
  • 防止系统启动瞬间被大流量打垮
  • 公式:coldThreshold = threshold * coldFactor(默认 coldFactor=3)

热点参数限流(ParamFlowChecker)

  • 针对方法参数值进行限流,如 getUser(id) 对不同 id 设置不同 QPS
  • 底层基于 LeapArray 滑动窗口 + LRU Cache 统计各参数值的 QPS
java
@SentinelResource(value = "getUser", blockHandler = "blockHandler")
public User getUser(@RequestParam Long id) { /* ... */ }
java
// 规则配置:对参数 0(id),值为 1 时 QPS 限制为 10,其他为 100
ParamFlowRule rule = new ParamFlowRule("getUser")
    .setParamIdx(0)
    .setCount(100);
ParamFlowItem item = new ParamFlowItem();
item.setObject("1");
item.setClassType(Long.class.getName());
item.setCount(10);
rule.setParamFlowItemList(Collections.singletonList(item));

实现原理

  1. ParamFlowSlot 检查热点参数规则
  2. ParamFlowChecker#passCheck() 通过参数值索引获取统计的 QPS
  3. 统计值通过 RollingParamEvent 在滑动窗口中累加
  4. 特定参数值超过阈值则拒绝

典型场景:秒杀商品的 itemId 限流、热点新闻的 articleId 限流、防止恶意刷接口。


6.10 ★★★什么是服务熔断、服务降级、服务雪崩?三者关系是什么?如何通过 Sentinel 实现熔断降级?

📖 点击查看答案

服务雪崩

  • 在微服务调用链中,下游服务故障导致上游服务请求堆积、线程耗尽,进而导致上游也故障
  • 故障沿调用链向上传播,最终导致整个系统不可用
  • 根本原因:同步调用 + 线程阻塞 + 无超时 + 无限流

服务熔断

  • 类似电路保险丝,当下游服务故障率达到阈值时,熔断器"跳闸",直接拒绝请求
  • 避免持续调用故障服务,快速失败,保护上游资源
  • 状态机:CLOSED(正常)→ OPEN(熔断,直接拒绝)→ HALF_OPEN(半开,放少量请求试探)→ CLOSED

服务降级

  • 主动放弃部分非核心功能,保证核心业务可用
  • 触发场景:熔断后返回兜底数据、限流后返回提示、资源不足时关闭非核心服务
  • 降级方式:返回默认值、返回缓存、返回简化结果、异步化处理

三者关系

  • 雪崩是问题,熔断和降级是解决方案
  • 熔断是被动触发(故障达到阈值),降级可以是主动或被动
  • 熔断的后果通常是降级(返回兜底数据)
  • 限流可以预防雪崩(控制请求量)
下游故障 → 调用失败率高 → 触发熔断(OPEN) → 直接拒绝/降级返回

       上游线程不阻塞 → 避免雪崩

半开阶段放少量请求 → 成功则恢复 CLOSED,失败则继续 OPEN

Sentinel 实现熔断降级

熔断规则

java
// 慢调用比例熔断:RT > 200ms 视为慢调用,比例 > 50% 且请求数 >= 5 时熔断 10s
DegradeRule rule = new DegradeRule("queryOrder")
    .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
    .setCount(200)            // 最大 RT
    .setSlowRatioThreshold(0.5)
    .setMinRequestAmount(5)
    .setStatIntervalMs(10000)
    .setTimeWindow(10);       // 熔断时长 10s

降级 fallback

java
@SentinelResource(value = "queryOrder",
    blockHandler = "handleBlock",     // Sentinel 规则触发(限流/熔断)
    fallback = "handleFallback")      // 业务异常触发
public Order queryOrder(Long id) {
    return orderClient.getOrder(id);  // Feign 调用
}

public Order handleBlock(Long id, BlockException ex) {
    return Order.defaultOrder();   // 返回兜底数据
}
public Order handleFallback(Long id, Throwable e) {
    log.error("查询订单异常", e);
    return Order.defaultOrder();
}

Feign + Sentinel 降级

yaml
feign:
  sentinel:
    enabled: true
java
@FeignClient(name = "order-service", fallback = OrderFallback.class)
public interface OrderClient {
    @GetMapping("/orders/{id}")
    Order getOrder(@PathVariable Long id);
}

@Component
public class OrderFallback implements OrderClient {
    @Override
    public Order getOrder(Long id) {
        return Order.defaultOrder();
    }
}

6.11 ★★★★分布式链路追踪的原理是什么?Trace ID 和 Span ID 是如何传递的?SkyWalking 的核心概念和工作原理是什么?

📖 点击查看答案

分布式链路追踪原理(基于 Google Dapper 论文):

  • Trace:一次完整请求链路,由唯一 TraceId 标识
  • Span:一次操作(如一次 RPC 调用),有 SpanIdParentSpanId,构成树形结构
  • Annotation:Span 中的事件标记(CS=Client Send、SR=Server Receive、SS=Server Send、CR=Client Receive)

Trace ID / Span ID 传递

  1. 请求入口(网关)生成 TraceId 和根 SpanId
  2. 通过 HTTP Header(X-B3-TraceIdX-B3-SpanIdX-B3-ParentSpanId)或 RPC 隐式参数透传
  3. 下游服务从 Header 中解析 TraceId,生成子 Span,记录 ParentSpanId
  4. 异步场景通过 ThreadLocal 或 MDC 传递,跨线程用 InheritableThreadLocal 或显式包装

SkyWalking 核心概念

  • Agent:Java Agent(无侵入),通过字节码增强(ByteBuddy)拦截方法
  • Trace Segment:一个服务实例内的 Span 集合
  • Service / Instance / Endpoint:服务、实例、接口
  • OAP Server:Observability Analysis Platform,聚合分析数据
  • Storage:Elasticsearch / H2 / MySQL 存储数据
  • UI:可视化界面

SkyWalking 工作原理

1. 数据采集(无侵入)

  • 通过 -javaagent:skywalking-agent.jar 启动时挂载
  • ByteBuddy 在类加载时拦截指定方法(如 Tomcat、SpringMVC、Feign、MySQL 驱动)
  • 拦截方法前后生成 Span,记录耗时、标签、异常

2. 数据上报

  • Agent 通过 gRPC / HTTP 将 Trace Segment 异步上报到 OAP Server
  • 异步队列缓冲,避免阻塞业务线程
  • 支持采样率配置

3. 数据处理

  • OAP Server 接收 Segment,合并跨服务的 Trace
  • 聚合生成 Service / Instance / Endpoint 维度的指标(QPS、RT、错误率)
  • 存储到 ES / H2

4. 数据展示

  • UI 展示拓扑图、Trace 详情、慢查询、告警

Trace 上下文传递

java
// SkyWalking 自动注入 HTTP Header
// sw8: 1-TraceId-SegmentId-SpanId-ParentService-ParentInstance-ParentEndpoint-TargetAddress
// 跨线程传递
ContextManager.capture();   // 捕获快照
ContextManager.continue(snapshot);  // 恢复

与其他方案对比

  • Zipkin:侵入式(需引入 SDK),轻量级
  • Jaeger:CNCF 项目,支持多语言
  • SkyWalking:Apache 顶级项目,无侵入,国内生态好

6.12 ★★微服务架构下的灰度发布如何实现?网关层面如何做到流量染色和路由?

📖 点击查看答案

灰度发布方案

1. 基于版本号的路由

  • 服务多版本注册,不同实例打 version 标签
  • 网关根据请求特征路由到不同版本

2. 基于权重的灰度

  • 按比例(如 10% 流量到新版本)路由

3. 基于规则的灰度

  • 特定用户(如白名单用户)路由到新版本
  • 基于 Header、Cookie、用户 ID 取模等

网关层流量染色与路由

Nacos 元数据 + Spring Cloud Gateway

  1. 服务注册时打标签:
yaml
spring:
  cloud:
    nacos:
      discovery:
        metadata:
          version: gray    # 灰度版本
          weight: 10
  1. 网关自定义 Predicate 和 Filter:
java
@Component
public class GrayPredicate extends AbstractRoutePredicateFactory<GrayPredicate.Config> {
    public GrayPredicate() {
        super(Config.class);
    }
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            String version = exchange.getRequest().getHeaders().getFirst("X-Gray-Version");
            return config.getVersion().equals(version);
        };
    }
}
  1. 自定义负载均衡器(Spring Cloud LoadBalancer):
java
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        String version = request.getContext().get("X-Gray-Version");
        List<ServiceInstance> instances = serviceInstances();
        List<ServiceInstance> matched = instances.stream()
            .filter(i -> version.equals(i.getMetadata().get("version")))
            .collect(Collectors.toList());
        if (!matched.isEmpty()) {
            return Mono.just(new DefaultResponse(matched.get(0)));
        }
        // 无灰度实例则走正式
        return Mono.just(new DefaultResponse(instances.get(0)));
    }
}
  1. 网关 Filter 注入染色:
java
@Component
public class GrayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        if (isGrayUser(userId)) {   // 灰度名单或取模
            exchange.getRequest().mutate().header("X-Gray-Version", "gray").build();
        }
        return chain.filter(exchange);
    }
}

全链路灰度

  • 灰度 Header 通过 Feign 拦截器透传到下游服务
  • 各服务负载均衡器根据 Header 路由到对应版本实例
  • 配合 Sentinel 的热点参数限流控制灰度流量

基于 4 份主流面试题库整合精选,共 200 题