主题
六、SpringCloud 生态(12 题)
6.1 ★★★微服务架构的核心组件有哪些?请描述一个完整的微服务请求链路。微服务架构的优缺点是什么?
📖 点击查看答案
核心组件:
- 服务注册与发现:Nacos、Eureka、Consul、Zookeeper
- 服务调用:OpenFeign、Dubbo、RestTemplate + LoadBalancer
- 负载均衡:Spring Cloud LoadBalancer、Ribbon(已维护模式)
- API 网关:Spring Cloud Gateway、Zuul
- 配置中心:Nacos Config、Apollo、Spring Cloud Config
- 熔断降级:Sentinel、Hystrix(停止维护)、Resilience4j
- 分布式事务:Seata、RocketMQ 事务消息
- 链路追踪:SkyWalking、Zipkin、Jaeger
- 消息总线:Spring Cloud Bus
- 分布式调度:XXL-Job、ElasticJob
完整请求链路(以一次外部请求为例):
- 客户端请求 → 网关(Gateway,统一鉴权、限流、路由)
- 网关从注册中心获取服务实例列表
- 网关通过负载均衡选择一个实例,转发请求
- 服务 A 处理请求,若需调用服务 B:
- Feign 生成动态代理,从注册中心获取服务 B 实例
- 负载均衡选择实例发起 HTTP/RPC 调用
- 熔断器(Sentinel)拦截,判断是否降级
- 调用过程中,链路追踪生成 Trace ID 并透传(通过 HTTP Header / MDC)
- 服务从配置中心读取配置,配置变更通过消息总线推送刷新
- 异步场景通过消息队列(RocketMQ/Kafka)解耦
- 跨服务写操作通过分布式事务保证一致性(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(服务提供者/消费者):启动时注册自身,并拉取服务列表
服务注册流程(服务提供者):
- 服务启动时,Client 向 Server 发送注册请求,包含:服务名、IP、端口、元数据(权重、版本等)
- Server 将信息存储在注册表中(内存 Map)
- Server 返回注册成功,Client 开启定时任务发送心跳
服务发现流程(服务消费者):
- Client 启动时从 Server 拉取服务实例列表并缓存到本地
- Server 维护长连接(Nacos UDP 推送)或 Client 定时拉取(Eureka 30s)
- 服务列表变更时,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 配置中心原理:
- 客户端启动时从 Nacos 拉取配置文件(dataId + group)
- 客户端通过长轮询监听配置变更:默认每 30s 向 Nacos 发送请求,带配置的 MD5 值
- Nacos 比较客户端 MD5 和服务端 MD5,不一致则立即返回变更的 dataId;一致则 hold 住请求 30s
- 客户端收到变更通知后,拉取最新配置并触发
Listener回调 - Nacos 服务端内部通过
AsyncContext实现 long polling 的 hold
动态刷新机制:
方式一:@RefreshScope + /actuator/refresh:
- 配置变更时调用
/actuator/refresh或监听 Nacos 配置变更 - 触发
RefreshEvent,ContextRefresher销毁所有@RefreshScope标注的 Bean - 下次访问时重新创建 Bean,注入最新配置
方式二:@NacosConfigListener / @ConfigurationProperties:
- 配置变更时自动绑定到
@ConfigurationProperties类
@RefreshScope 原理:
@RefreshScope标记的 Bean 会被RefreshScope(作用域)管理- 真正的 Bean 被代理(CGLIB),实际实例缓存到
RefreshScope的ConcurrentHashMap - 配置刷新时,调用
RefreshScope#refreshAll()清空缓存 - 下次访问 Bean 时,重新走 Bean 创建流程,注入最新配置
java
@RestController
@RefreshScope
public class ConfigController {
@Value("${app.name}")
private String appName; // 配置刷新后,下次访问时为新值
}局限性:
- 只对
@RefreshScope标注的 Bean 生效,普通@Value不会刷新 @RefreshScopeBean 需要重新创建,有一定性能开销- 静态变量、
@PostConstruct初始化的字段不会自动刷新 - 数据库连接池、线程池等已初始化的资源不会因配置刷新而重建(需配合
@RefreshScope或自定义监听) - 刷新会触发 Bean 销毁重建,期间有短暂不可用
@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:
| 特性 | Nacos | Eureka |
|---|---|---|
| CAP 模式 | AP + CP 可切换 | 仅 AP |
| 配置中心 | 是(集成) | 否 |
| 长连接 | 长轮询 + UDP 推送 | 定时拉取(30s) |
| 实例类型 | 临时 + 持久 | 仅临时 |
| 健康检查 | 心跳 + 主动探测 | 心跳 |
| 集群协议 | Distro + Raft | P2P 复制 |
| 一致性 | 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 工作原理:
@LoadBalanced注解标记 RestTemplate,为其添加拦截器LoadBalancerInterceptor- 拦截器拦截请求,从 URL 提取服务名(如
http://user-service/users/1中的user-service) - 调用
LoadBalancerClient#choose(),通过ILoadBalancer选择实例 ILoadBalancer从注册中心获取实例列表,调用IRule#choose()根据策略选择实例- 用真实 IP:Port 替换服务名,发起请求
java
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
// 调用:restTemplate.getForObject("http://user-service/users/1", User.class);内置负载均衡策略(实现 IRule):
RoundRobinRule(默认):轮询,按顺序依次选择RandomRule:随机选择WeightedResponseTimeRule:根据响应时间加权,响应快的权重高BestAvailableRule:选择并发数最小的实例AvailabilityFilteringRule:过滤熔断器跳闸的实例,选择并发数小的ZoneAvoidanceRule(默认,Spring Cloud Alibaba 默认):综合区域可用性和实例可用性过滤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 工作原理:
@EnableFeignClients扫描@FeignClient接口- 通过
FeignClientFactoryBean创建代理对象(JDK 动态代理InvocationHandler) - 调用方法时,代理对象将方法转为
RequestTemplate(URL、参数、Header) - 通过负载均衡器选择实例,替换服务名为真实 IP:Port
- 通过
Client(默认HttpURLConnection)发起 HTTP 请求 Decoder解码响应,Encoder编码请求体- 拦截器链(
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: true2. 超时配置:
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: true4. 日志级别(生产环境用 NONE/BASIC,性能影响最小):
yaml
feign:
client:
config:
default:
logger-level: BASIC # NONE/BASIC/HEADERS/FULL5. 开启熔断降级:避免级联故障,配合 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,POSTHeader:请求头匹配,Header=X-Request-Id, \d+Query:查询参数匹配,Query=token, \d+Host:Host 匹配,Host=**.example.comAfter/Before/Between:时间匹配Cookie:Cookie 匹配RemoteAddr:IP 匹配
Filter(过滤器):对请求/响应进行处理:
- GatewayFilter(局部):作用于单个路由,如
AddRequestHeader、PrefixPath - GlobalFilter(全局):作用于所有路由,如鉴权、日志、限流
工作流程:
- 客户端请求 →
HandlerMapping匹配路由(基于 Predicate) - 匹配成功 → 进入过滤器链
- 过滤器分前置(pre)和后置(post),前置在请求转发前执行,后置在响应返回后执行
FilteringWebHandler按顺序执行:GlobalFilter+ 当前路由的GatewayFilter- 通过代理转发到目标服务
- 响应逆向通过过滤器链返回
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 责任链(核心):
NodeSelectorSlot:构建资源调用树ClusterBuilderSlot:构建集群节点统计StatisticSlot:实时统计 QPS、RT、线程数等(基于滑动窗口)FlowSlot:流控规则判断DegradeSlot:熔断规则判断AuthoritySlot:授权规则SystemSlot:系统级规则(CPU、Load)
限流(Flow Control)规则:
- 阈值类型:QPS、并发线程数
- 流控行为:直接拒绝、Warm Up(预热)、排队等待
- 流控模式:直接、关联(关联资源达到阈值时限流自身)、链路(只限流从某资源来的调用)
熔断(Circuit Breaking)规则:
- 慢调用比例:RT > 阈值的请求比例超阈值时熔断
- 异常比例:异常比例超阈值时熔断
- 异常数:异常数超阈值时熔断
- 熔断状态:CLOSED → OPEN(熔断) → HALF_OPEN(半开试探) → CLOSED
降级:通过 @SentinelResource 的 blockHandler / 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:
| 特性 | Sentinel | Hystrix |
|---|---|---|
| 隔离策略 | 信号量隔离 | 线程池/信号量隔离 |
| 熔断策略 | 慢调用比例/异常比例/异常数 | 异常比例 |
| 实时统计 | 滑动窗口(LeapArray) | 滑动窗口(RxJava) |
| 动态规则 | 支持(Nacos/控制台) | 配置文件 |
| 限流 | 支持(QPS/线程数) | 不支持(仅熔断) |
| 系统自适应 | 支持 | 不支持 |
| 控制台 | Sentinel Dashboard | Hystrix 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));实现原理:
ParamFlowSlot检查热点参数规则ParamFlowChecker#passCheck()通过参数值索引获取统计的 QPS- 统计值通过
RollingParamEvent在滑动窗口中累加 - 特定参数值超过阈值则拒绝
典型场景:秒杀商品的 itemId 限流、热点新闻的 articleId 限流、防止恶意刷接口。
6.10 ★★★什么是服务熔断、服务降级、服务雪崩?三者关系是什么?如何通过 Sentinel 实现熔断降级?
📖 点击查看答案
服务雪崩:
- 在微服务调用链中,下游服务故障导致上游服务请求堆积、线程耗尽,进而导致上游也故障
- 故障沿调用链向上传播,最终导致整个系统不可用
- 根本原因:同步调用 + 线程阻塞 + 无超时 + 无限流
服务熔断:
- 类似电路保险丝,当下游服务故障率达到阈值时,熔断器"跳闸",直接拒绝请求
- 避免持续调用故障服务,快速失败,保护上游资源
- 状态机:CLOSED(正常)→ OPEN(熔断,直接拒绝)→ HALF_OPEN(半开,放少量请求试探)→ CLOSED
服务降级:
- 主动放弃部分非核心功能,保证核心业务可用
- 触发场景:熔断后返回兜底数据、限流后返回提示、资源不足时关闭非核心服务
- 降级方式:返回默认值、返回缓存、返回简化结果、异步化处理
三者关系:
- 雪崩是问题,熔断和降级是解决方案
- 熔断是被动触发(故障达到阈值),降级可以是主动或被动
- 熔断的后果通常是降级(返回兜底数据)
- 限流可以预防雪崩(控制请求量)
下游故障 → 调用失败率高 → 触发熔断(OPEN) → 直接拒绝/降级返回
↓
上游线程不阻塞 → 避免雪崩
半开阶段放少量请求 → 成功则恢复 CLOSED,失败则继续 OPENSentinel 实现熔断降级:
熔断规则:
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: truejava
@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 调用),有
SpanId和ParentSpanId,构成树形结构 - Annotation:Span 中的事件标记(CS=Client Send、SR=Server Receive、SS=Server Send、CR=Client Receive)
Trace ID / Span ID 传递:
- 请求入口(网关)生成
TraceId和根SpanId - 通过 HTTP Header(
X-B3-TraceId、X-B3-SpanId、X-B3-ParentSpanId)或 RPC 隐式参数透传 - 下游服务从 Header 中解析 TraceId,生成子 Span,记录 ParentSpanId
- 异步场景通过 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:
- 服务注册时打标签:
yaml
spring:
cloud:
nacos:
discovery:
metadata:
version: gray # 灰度版本
weight: 10- 网关自定义 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);
};
}
}- 自定义负载均衡器(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)));
}
}- 网关 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 的热点参数限流控制灰度流量