格式知识点原理 → 面试表达模板 → 追问应对一、注册中心原理Q1. Eureka、Nacos、Zookeeper 的 CAP 选择及原因知识点讲解CAP 定理一致性©、可用性(A)、分区容错性§ 三者只能同时满足两个。网络分区§在分布式系统中必须容忍所以只能选 CP 或 AP。Eureka → AP可用性优先 设计理念宁可返回旧数据也不拒绝服务 自我保护机制心跳数低于85%阈值时停止剔除任何实例 适合场景对注册信息短暂不一致可接受的微服务 Zookeeper → CP一致性优先 原理基于 Raft/ZAB 协议选主写操作必须经 Leader 多数派确认 代价Leader 选举期间约30秒无法提供服务 适合场景需要强一致的分布式协调分布式锁、配置 Nacos → AP CP 可切换 临时实例默认Distro 协议AP客户端心跳适合微服务 永久实例Raft 协议CP服务端主动探测适合基础设施面试表达模板三者的 CAP 选择反映了不同的设计权衡Eureka 选 AP认为注册中心短暂数据不一致比不可用危害更小Zookeeper 选 CP牺牲可用性保证一致性但选举期间有服务中断风险Nacos 最灵活临时实例用 AP 的 Distro 协议永久实例支持 CP 的 Raft 协议这也是 Nacos 在国内成为主流的重要原因。追问为什么注册中心推荐 AP 不推荐 CP即使消费者拿到了稍旧的服务列表某个实例已下线最多导致一次调用失败并触发重试代价可控。但如果注册中心不可用CP 选举期间消费者完全无法获取服务列表整个调用链断开危害远大于前者。Q2. Nacos 服务注册后消费者如何感知服务变化知识点讲解Nacos 2.x 实例变化感知流程 提供者端 实例启动 → gRPC 长连接注册到 Nacos Server 心跳通过 gRPC 连接维持默认5秒一次 宕机/断连Server 侧自动检测标记实例不健康 消费者端感知变化两种机制 1. 主动推送Nacos 2.x gRPC Server 检测到实例变化 → 通过 gRPC 长连接主动推送给所有订阅该服务的消费者 消费者收到推送 → 更新本地缓存 → 下次调用使用新列表 2. 定时拉取兜底30秒 防止推送丢失消费者每30秒主动拉取一次 本地缓存服务降级 最后一次成功的服务列表写入磁盘 ~/.nacos/naming/{namespace}/{group}{serviceName} Nacos 全部宕机时从磁盘读取快照保证服务仍可运行示例验证本地缓存文件# 查看 Nacos 客户端本地缓存ls~/.nacos/naming/# 例public/DEFAULT_GROUPuser-service 文件内容是最后一次拉取的实例列表 JSON面试表达模板Nacos 2.x 采用 gRPC 长连接实例变化时 Server 主动推送给订阅者毫秒级感知1.x 是客户端轮询延迟可达30秒。消费者本地维护缓存并写到磁盘快照即使 Nacos 全部宕机服务也能用快照继续运行。二、OpenFeign 原理Q3. OpenFeign 的底层调用原理知识点讲解Feign 代理生成过程 EnableFeignClients → FeignClientsRegistrar → 扫描 FeignClient → 注册 FeignClientFactoryBean → getObject() 时创建 JDK 动态代理 一次 Feign 调用流程 方法调用 ↓ InvocationHandler.invoke() ↓ Contract 解析注解GetMapping → HTTP GET ↓ Encoder 序列化请求参数Java 对象 → JSON/Query ↓ RequestInterceptor 链添加 Token、TraceId ↓ LoadBalancer 选择实例Nacos 缓存 → 轮询/随机 ↓ HTTP Client 发起请求默认 HttpURLConnection ↓ Decoder 反序列化响应JSON → Java 对象 ↓ ErrorDecoder 处理非2xx响应可触发 Fallback生产优化替换 HTTP 客户端!-- 默认 HttpURLConnection 无连接池高并发性能差 --!-- 替换为 Apache HttpClient 5有连接池--dependencygroupIdio.github.openfeign/groupIdartifactIdfeign-hc5/artifactId/dependencyspring:cloud:openfeign:httpclient:hc5:enabled:truemax-connections:200# 全局最大连接数max-connections-per-route:50# 每个目标服务最大连接数面试表达模板Feign 通过 JDK 动态代理为接口生成代理对象调用方法时将注解信息GetMapping、PathVariable 等转换为 HTTP 请求经过拦截器链附加 Token/TraceId→ LoadBalancer 选实例 → HTTP Client 发请求 → Decoder 反序列化。生产建议替换默认的 HttpURLConnection 为 Apache HC5原因是前者无连接池每次请求新建 TCP 连接高并发下性能极差。追问Feign 调用时 Token 丢失怎么处理// 通过 RequestInterceptor 透传当前请求的 HeaderComponentpublicclassFeignTokenInterceptorimplementsRequestInterceptor{Overridepublicvoidapply(RequestTemplatetemplate){ServletRequestAttributesattrs(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();if(attrs!null){Stringtokenattrs.getRequest().getHeader(Authorization);if(token!null){template.header(Authorization,token);}}}}原因Feign 调用在独立线程池中执行RequestContextHolder用 ThreadLocal 存储请求上下文不同线程无法共享。RequestInterceptor在主线程发起调用前执行此时 ThreadLocal 仍可访问所以能拿到 Token。三、Gateway 原理Q4. Spring Cloud Gateway 的请求处理流程知识点讲解Gateway 基于 WebFluxReactor Netty完整流程 HTTP RequestNetty 接收 ↓ DispatcherHandlerWebFlux 核心分发器 ↓ RoutePredicateHandlerMapping └── 遍历所有 Route依次评估 Predicate断言 Predicate 全部匹配 → 选中该 Route ↓ FilteringWebHandler └── 合并 GlobalFilter 当前 Route 的 GatewayFilter 按 Order 排序Order 越小越先执行 ↓ Filter Chain Pre 阶段鉴权、限流、日志记录开始时间 ↓ NettyRoutingFilter发起对后端服务的 HTTP 请求 ↓ Filter Chain Post 阶段修改响应、记录日志耗时 ↓ 响应给客户端Predicate 匹配顺序的坑# ❌ 错误范围大的路由放前面所有请求都匹配第一条后面的路由永远不生效routes:-id:catch-alluri:lb://default-servicepredicates:-Path/**# 匹配所有-id:user-routeuri:lb://user-servicepredicates:-Path/api/users/**# ✅ 正确精确路由放前面范围大的路由放后面routes:-id:user-routeuri:lb://user-servicepredicates:-Path/api/users/**# 先匹配精确的-id:catch-alluri:lb://default-servicepredicates:-Path/**# 兜底放最后面试表达模板Gateway 基于 WebFlux 非阻塞模型核心流程是Netty 接收请求 →RoutePredicateHandlerMapping按 Predicate 找到匹配路由 → 合并 GlobalFilter 和路由 GatewayFilter按 Order 排序→ Pre 阶段过滤鉴权/限流→ 转发后端 → Post 阶段过滤响应处理/记录耗时。重要细节路由匹配按定义顺序精确路由要放在范围大的路由前面GlobalFilter 对所有路由生效GatewayFilter 只对配置的路由生效。四、熔断限流Q5. Sentinel 的熔断状态机和恢复机制知识点讲解三种状态 CLOSED关闭正常放行 → 统计窗口内触发阈值慢调用比例/异常比例/异常数 → 进入 OPEN OPEN熔断直接返回 BlockException不调用后端 → 经过休眠窗口时间timeWindow 秒 → 进入 HALF-OPEN HALF-OPEN半开放行一个探测请求 → 探测请求成功 → 回到 CLOSED → 探测请求失败 → 重新进入 OPEN继续休眠三种熔断策略对比策略触发条件适用场景慢调用比例响应时间 阈值的请求比例超标下游接口变慢异常比例异常请求占比超标下游接口不稳定异常数统计窗口内异常总数超标低流量场景Sentinel Dashboard 配置示例熔断规则资源名: getUserById 熔断策略: 慢调用比例 最大 RT: 1000ms ← 超过1秒算慢调用 比例阈值: 0.5 ← 50%以上请求是慢调用才熔断 熔断时长: 10s ← 熔断10秒后进入半开 最小请求数: 5 ← 统计窗口内至少5个请求才触发 统计时长: 10000ms ← 10秒统计窗口规则持久化到 Nacos生产必备# 服务重启后规则不丢失spring:cloud:sentinel:datasource:flow-rules:nacos:server-addr:localhost:8848data-id:${spring.application.name}-flow-rulesgroup-id:SENTINEL_GROUPdata-type:jsonrule-type:flow面试表达模板Sentinel 熔断有三种状态CLOSED正常→ OPEN熔断快速失败→ HALF-OPEN半开放一个探测请求。触发条件支持慢调用比例、异常比例、异常数三种策略。生产中必须做规则持久化推送到 Nacos否则服务重启后规则丢失控制台配置的规则全部失效。Q6. Sentinel 和 Hystrix 的核心区别知识点讲解Hystrix已停止维护 隔离模型线程池隔离每个资源一个独立线程池 优点完全隔离一个资源的阻塞不影响其他 缺点线程切换开销大每次调用都需要线程切换资源消耗高 熔断滑动窗口统计粒度较粗 Sentinel阿里活跃维护 隔离模型信号量隔离限制并发线程数在调用线程执行 优点无线程切换开销性能更好 缺点调用线程被占用时无法立即超时需配合超时设置 熔断支持慢调用比例、异常比例、异常数三种策略 额外能力实时 Dashboard、热点参数限流、系统自适应保护面试表达模板核心区别在隔离模型Hystrix 用线程池隔离每个资源分配独立线程池代价是线程切换开销大Sentinel 用信号量隔离限制并发线程数无切换开销性能更好。另外 Sentinel 的熔断策略更丰富三种有实时 Dashboard规则动态推送这也是国内几乎全面切换 Sentinel 的原因。Hystrix 已停止维护新项目不建议使用。五、分布式事务Q7. Seata AT 模式原理和适用场景知识点讲解AT 模式核心两阶段提交的改进版 Phase 1执行阶段 RM 拦截业务 SQL → 生成 before image快照修改前数据 执行业务 SQL → 生成 after image快照修改后数据 将 before/after image 存入 undo_log 表 提交本地事务含 undo_log一起提交 向 TC 汇报分支事务结果 Phase 2提交/回滚 全部成功 → TC 通知各 RM 删除 undo_log异步不影响业务 任一失败 → TC 通知各 RM 用 undo_log 生成反向 SQL 执行回滚 与传统 2PC 对比 传统 2PCPhase 1 持有全局锁直到 Phase 2 完成锁持有时间长 Seata ATPhase 1 直接提交本地事务锁释放仅用 undo_log 保证可回滚 → 全局锁持有时间极短并发性能大幅提升必须要有 undo_log 表-- 每个参与事务的数据库都要建此表CREATETABLEundo_log(idBIGINTAUTO_INCREMENTPRIMARYKEY,branch_idBIGINTNOTNULL,xidVARCHAR(128)NOTNULL,contextVARCHAR(128)NOTNULL,rollback_infoLONGBLOBNOTNULL,log_statusINTNOTNULL,log_createdDATETIMENOTNULL,log_modifiedDATETIMENOTNULL,UNIQUEKEYux_undo_log(xid,branch_id))ENGINEInnoDB;使用示例ServiceRequiredArgsConstructorpublicclassOrderService{privatefinalOrderRepositoryorderRepository;privatefinalProductFeignClientproductClient;privatefinalAccountFeignClientaccountClient;// GlobalTransactional 开启 Seata 全局事务// 内部任何步骤包括 Feign 调用的远程服务失败 → 全部自动回滚GlobalTransactional(rollbackForException.class,timeoutMills30000)publicOrdercreateOrder(CreateOrderDTOdto){OrderorderorderRepository.save(buildOrder(dto));// 步骤1本地productClient.deductStock(dto.getProductId(),dto.getQty());// 步骤2远程accountClient.deductBalance(dto.getUserId(),order.getAmount());// 步骤3远程returnorder;// 任何步骤抛异常 → Seata 协调所有服务回滚}}面试表达模板Seata AT 基于改进的两阶段提交Phase 1 执行业务 SQL 的同时记录 undo_log前后镜像然后直接提交本地事务不持锁等待Phase 2 成功则删 undo_log失败则用 undo_log 生成反向 SQL 回滚。相比传统 2PC全局锁持有时间大幅缩短。适合大多数业务场景对性能有极致要求的资金场景用 TCC。Q8. 什么时候用 Seata什么时候用消息队列知识点讲解选型判断树 这个操作需要立即一致吗 是 → 用 Seata强一致/最终强一致 性能敏感 是 → TCC 模式 否 → AT 模式推荐无侵入 否允许短暂不一致→ 用消息队列最终一致 需要可靠投递 是 → 本地消息表 MQ保证消息不丢失 否 → 直接发 MQ 实际场景映射 Seata下单扣库存扣余额必须同时成功/失败 MQ下单成功后发优惠券、更新积分、发邮件允许延迟面试表达模板核心判断是是否需要实时强一致扣款/扣库存这类操作不允许任何不一致用 Seata发送通知/更新积分/刷新缓存这类操作允许短暂不一致用消息队列性能更好吞吐更高。原则是能用最终一致就不用强一致因为 Seata 引入了全局锁和网络开销会降低系统吞吐量。六、链路追踪Q9. 分布式链路追踪的 TraceId 如何跨服务传递知识点讲解Micrometer TracingSpring Boot 3.x 推荐工作原理 请求进入 Gateway 生成 TraceId全局唯一、SpanId当前节点 注入到 HTTP 请求头B3 协议格式 X-B3-TraceId: abc123def456 X-B3-SpanId: 111111 X-B3-ParentSpanId: (空因为是起点) X-B3-Sampled: 1 下游服务user-service接收请求 读取请求头中的 TraceId → 继承不生成新的 生成新的 SpanId自己的节点标识 将 TraceId/SpanId 注入 MDC可以在日志中打印 Feign 调用更下游时自动将 TraceId 放入下游请求头 日志中打印 TraceId %X{traceId} ← Logback MDC 占位符配置示例dependencygroupIdio.micrometer/groupIdartifactIdmicrometer-tracing-bridge-brave/artifactId/dependencydependencygroupIdio.zipkin.reporter2/groupIdartifactIdzipkin-reporter-brave/artifactId/dependencymanagement:tracing:sampling:probability:0.1# 生产环境采样10%全采样性能影响大zipkin:tracing:endpoint:http://zipkin:9411/api/v2/spanslogging:pattern:console:%d{HH:mm:ss} [%thread] %-5level [TraceId:%X{traceId}] %msg%n面试表达模板链路追踪通过在 HTTP 请求头中携带 TraceIdB3 协议实现跨服务传递。每个服务接收请求时读取 TraceId 并继承生成自己的 SpanId然后将 TraceId 注入 MDC 供日志打印调用下游时自动将 TraceId 放入请求头。这样所有服务的日志都有相同的 TraceId可以在日志系统ELK中用 TraceId 过滤出一次请求的完整日志链。Feign 客户端会自动传播 TraceId无需手动处理。七、专家级追问Q10. 微服务中如何实现灰度发布知识点讲解方案一Nacos 权重实例级别 新版本实例设置低权重如5旧版本高权重95 LoadBalancer 按权重分配流量 优点简单缺点不能按用户/Header 定向灰度 方案二Gateway Header 路由用户级别 灰度用户请求携带 HeaderX-Gray: true Gateway 识别 Header → 路由到灰度服务集群 适合VIP 用户先体验新功能 方案三Gateway Weight 断言流量比例 配置两个路由分别权重90%和10% 适合按比例切流不关心具体是哪个用户方案二完整实现// Gateway 灰度路由过滤器ComponentpublicclassGrayRouteFilterimplementsGlobalFilter,Ordered{OverridepublicMonoVoidfilter(ServerWebExchangeexchange,GatewayFilterChainchain){StringgrayHeaderexchange.getRequest().getHeaders().getFirst(X-Gray);if(true.equals(grayHeader)){// 将目标服务名改为灰度版本URIgrayUriUriComponentsBuilder.fromUri(exchange.getRequest().getURI()).host(user-service-gray)// 灰度服务名Nacos 中注册.build().toUri();ServerHttpRequestrequestexchange.getRequest().mutate().uri(grayUri).build();returnchain.filter(exchange.mutate().request(request).build());}returnchain.filter(exchange);}OverridepublicintgetOrder(){return-50;}}面试表达模板灰度发布有三个维度①实例级别Nacos 权重按流量比例②用户级别Gateway 识别 Header指定用户走新版本③流量比例级别Gateway Weight 断言按百分比切流。生产中常组合使用先用 Nacos 权重做5%流量灰度观察指标无异常后用 Weight 断言逐步扩大到100%最终下线旧版本实例。八、面试自测表题目能否讲清原理能否结合项目能否应对追问Eureka/Nacos/ZK CAP 选择Nacos 服务变化感知机制Feign 代理生成 调用流程Token 丢失问题及解决Gateway 请求处理流程Predicate 匹配顺序问题Sentinel 三状态机Seata AT 两阶段原理Seata vs MQ 选型判断TraceId 跨服务传递灰度发布三种方案