Skip to content

SpringCloud微服务架构面试题库 ​

生成时间:2026-05-21 21:29 | 题目数:10


【面试题】1. Nacos同时支持AP和CP模式,底层是怎么切换的?什么场景该用哪种模式? ​

【标准答案】Nacos区分临时实例和持久实例两种注册方式,临时实例走AP模式(Distro协议),持久实例走CP模式(Raft协议)。默认注册的都是临时实例,用的就是AP模式。

切换逻辑很简单——服务实例注册时通过ephemeral字段控制:ephemeral=true(默认)走AP,ephemeral=false走CP。代码层面就是注册请求带了不同标记,Nacos Server根据标记路由到不同的一致性协议处理器。

选型原则:

  • AP模式(临时实例):适用于大多数业务服务,服务实例是临时的、随时可能上下线,追求高可用和低延迟。哪怕短暂不一致,多返回几个已下线实例,客户端重试一次就行,影响可控。
  • CP模式(持久实例):适用于数据库、缓存等基础服务,实例相对固定,对数据一致性要求高,不能容忍读到旧数据。比如MySQL主从切换的场景,必须确保同一时刻只有一个Master被注册。

面试加分点:可以补充说Nacos 2.x版本引入了gRPC长连接替代HTTP短连接做心跳,服务上下线感知从原来15-30秒缩短到3秒以内,AP模式下的不一致窗口大幅缩小,这是Nacos相比Eureka的核心优势。


【面试题】2. Ribbon默认的轮询策略在生产环境够用吗?如何自定义一个基于权重的负载均衡策略? ​

【标准答案】默认轮询策略在生产环境大概率不够用。轮询假设所有服务实例性能一致,但实际场景中不同实例的CPU、内存、网络带宽差异很大,弱实例和强实例平均分配流量会导致弱实例过载、强实例闲置。

自定义权重策略的实现思路:

方式一:继承AbstractLoadBalancerRule

java
public class WeightedRule extends AbstractLoadBalancerRule {
    @Override
    public Server choose(Object key) {
        List<Server> servers = getLoadBalancer().getAllServers();
        // 根据Nacos实例的weight元数据计算权重
        // 加权随机算法:构建[0, 总权重)区间,随机落点
        int totalWeight = servers.stream()
            .mapToInt(s -> {
                NacosServer ns = (NacosServer) s;
                return (int) ns.getInstance().getWeight();
            }).sum();
        int random = ThreadLocalRandom.current().nextInt(totalWeight);
        int current = 0;
        for (Server s : servers) {
            NacosServer ns = (NacosServer) s;
            current += (int) ns.getInstance().getWeight();
            if (random < current) return s;
        }
        return servers.get(0);
    }
}

方式二:配置文件指定

yaml
service-provider:
  ribbon:
    NFLoadBalancerRuleClassName: com.xxx.WeightedRule

实际项目更推荐直接用Nacos自带的权重配置,在Nacos控制台给实例设置权重值(0.1-10),结合NacosRule就能实现加权轮询,不需要自己写。

面试加分:如果用的是Spring Cloud LoadBalancer(Ribbon已停更),可以通过实现ReactorServiceInstanceLoadBalancer接口自定义策略,思路一样,只是API不同。


【面试题】3. OpenFeign调用超时了怎么办?超时重试配置有哪些坑? ​

【标准答案】超时处理分三步走:确认超时层级→调优超时配置→合理设置重试。

超时层级确认:OpenFeign超时本质是底层HTTP客户端的超时。Feign默认用URLConnection,没有连接池,建议换成Apache HttpClient或OkHttp。超时有两个维度:connectTimeout(建立连接超时)和readTimeout(读取响应超时)。

配置方式:

yaml
feign:
  client:
    config:
      default:  # 全局
        connectTimeout: 3000
        readTimeout: 5000
      order-service:  # 针对特定服务
        connectTimeout: 2000
        readTimeout: 10000
  httpclient:
    enabled: true
    max-connections: 200
    max-connections-per-route: 50

重试的坑:

  1. 重复提交:重试默认只对GET请求安全,非幂等接口(POST/PUT)重试可能导致重复操作。必须配合Retryer自定义重试策略,只对GET或明确幂等的接口重试。
  2. 重试风暴:Ribbon也有重试机制(MaxAutoRetries),Feign重试+Ribbon重试会叠加。比如Ribbon重试1次+Feign重试1次,实际最多调用4次!建议关闭Feign的重试(Retryer.NEVER_RETRY),只保留Ribbon重试,或者反过来。
  3. 超时+重试叠加:每次重试都消耗完整的readTimeout时间,3次重试可能等15秒。高并发场景下线程池会被打满,触发雪崩。建议重试次数不超过2次,且总超时时间要小于上游熔断器的超时阈值。

最佳实践:配置超时→开Sentinel熔断兜底→写降级fallback→重试只对幂等接口开启且次数严格控制。


【面试题】4. Sentinel的热点参数限流是什么?能解决什么实际业务问题? ​

【标准答案】热点参数限流是Sentinel的特色功能,可以根据请求中的参数值做精细化限流,不同的参数值有不同的限流阈值。

核心区别:普通限流是对整个接口限流(比如/order/query每秒最多100次),热点参数限流是按参数值分别限流(比如/order/query?goodsId=100每秒最多50次,/order/query?goodsId=200每秒最多200次)。

典型业务场景:

  1. 秒杀商品防刷:热门商品ID的查询QPS暴涨,冷门商品流量正常。按goodsId限流,热门商品单独限制,不影响其他商品。
  2. 大V用户防过载:社交场景下大V发动态瞬间涌入大量点赞和评论,按userId限流,大V单独设高阈值,普通用户正常限制。
  3. 防止缓存穿透:某些热点Key被打爆,按参数限流保护下游DB。

配置示例:

java
@SentinelResource(value = "queryOrder", blockHandler = "queryBlock")
public Order queryOrder(Long orderId, String userId) { ... }

// 热点参数规则
ParamFlowRule rule = new ParamFlowRule("queryOrder")
    .setParamIdx(0)          // 对第1个参数orderId限流
    .setCount(100)           // 默认每秒100
    .setGrade(RuleConstant.FLOW_GRADE_QPS);
// 对特定orderId单独配置
rule.setParamFlowItemList(List.of(
    new ParamFlowItem().setObject("10086")
        .setClassType(long.class.getName())
        .setCount(500)       // orderId=10086 每秒500
));

面试加分:热点参数限流底层用LRU Cache统计每个参数值的QPS,默认容量2000,参数值太多会淘汰冷数据。如果业务参数值域特别大(比如上百万SKU),要调大PARAM_INTERVAL_BASE或考虑用普通限流替代。


【面试题】5. Spring Cloud Gateway如何实现统一鉴权?全局过滤器与局部过滤器怎么选? ​

【标准答案】统一鉴权是网关的核心职责,Spring Cloud Gateway通过过滤器链实现,鉴权通常用全局过滤器(GlobalFilter)。

全局过滤器实现鉴权:

java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        // 1. 白名单路径放行(登录、注册等)
        String path = exchange.getRequest().getPath().value();
        if (isWhitePath(path)) {
            return chain.filter(exchange);
        }
        // 2. Token校验
        if (StringUtils.isBlank(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        // 3. 解析JWT,提取用户信息放入请求头传递给下游
        Claims claims = JwtUtil.parseToken(token);
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header("X-User-Id", claims.getSubject())
            .header("X-User-Role", claims.get("role", String.class))
            .build();
        return chain.filter(exchange.mutate().request(request).build());
    }
    @Override
    public int getOrder() { return -1; }  // 优先级最高
}

全局过滤器 vs 局部过滤器选择:

  • 全局过滤器(GlobalFilter):鉴权、日志、跨域、限流——所有请求都要走的逻辑,用全局的。不需要配置,注册即生效。
  • 局部过滤器(GatewayFilter):只对特定路由生效,比如某个服务的请求需要加特殊Header、做参数转换。需要在route配置中声明。

鉴权架构关键点:

  1. Token只在网关验证一次,解析后把用户信息通过Header传递给下游服务,避免下游重复验签。
  2. 下游服务信任网关传入的Header,但要注意防伪造——内网部署时网关是唯一入口,Header不会从外部绕过;如果担心,可以加一个网关签名Header做二次校验。
  3. Refresh Token的处理:网关拦截过期Token返回特定状态码,前端拿到后走无感刷新流程,新Token重新请求。

【面试题】6. Nacos Config配置热更新的原理是什么?多环境隔离怎么实现? ​

【标准答案】配置热更新的核心原理是长轮询监听机制。

热更新原理:

  1. 客户端启动时拉取配置,同时用长轮询(Long Polling)向Nacos Server注册监听。Nacos 2.x升级为gRPC长连接,实时性更强。
  2. 当配置变更时,Nacos Server通过gRPC连接主动推送变更通知给客户端。
  3. 客户端收到通知后重新拉取最新配置,触发RefreshEvent,Spring Cloud通过@RefreshScope或@ConfigurationProperties刷新Bean中的属性值。

两种热更新方式:

  • @RefreshScope:作用在Bean上,配置变更时销毁旧Bean、创建新Bean,适合需要重新初始化的Bean。有性能开销,不建议大量使用。
  • @ConfigurationProperties:属性自动绑定,配置变更时只更新属性值,不重建Bean,性能更好,推荐优先使用。

多环境隔离实现:Nacos用三层隔离模型——Namespace → Group → Data ID。

层级用途示例
Namespace环境隔离dev / test / prod
Group项目/业务隔离DEFAULT_GROUP / TRADE_GROUP
Data ID配置文件order-service.yaml
yaml
spring:
  cloud:
    nacos:
      config:
        namespace: ${NACOS_NAMESPACE:dev}   # 环境隔离
        group: ${NACOS_GROUP:DEFAULT_GROUP} # 项目隔离
        file-extension: yaml
        shared-configs:                     # 共享配置
          - data-id: common-db.yaml
            refresh: true
          - data-id: common-redis.yaml
            refresh: true

面试加分:生产环境建议配置加密(Nacos支持内置加密,配置jasypt或Nacos 2.x的配置加密插件),数据库密码等敏感配置不能明文存储。同时配合Git版本管理,Nacos Config支持配置历史回滚,最多保留30天,误改配置可以一键回退。


【面试题】7. SkyWalking和Zipkin在链路追踪上有什么区别?链路追踪对性能的影响怎么控制? ​

【标准答案】两者核心区别在架构和功能定位:

对比维度ZipkinSkyWalking
数据采集SDK侵入(Brave/Sleuth)Java Agent无侵入
存储后端内存/MySQL/ElasticsearchElasticsearch/H2/MySQL
告警能力无内置告警规则
服务拓扑简单依赖图实时拓扑+指标聚合
性能损耗中等(SDK埋点)低(Agent字节码增强)
语言支持多语言SDKJava为主,其他语言用SDK
协议HTTP/KafkagRPC/HTTP

选型建议:简单微服务用Zipkin够用,轻量上手快;大规模生产环境(几百个服务以上)选SkyWalking,无侵入+告警+拓扑更实用。

性能影响控制:

  1. 采样率控制:不是每个请求都追踪。Zipkin通过spring.sleuth.sampler.probability控制,SkyWalking通过agent.sample_n_per_3_secs控制每3秒采样N条。生产环境建议采样率5%-10%,异常时临时调高。
  2. 异步上报:追踪数据不能阻塞业务线程。Zipkin用Kafka异步上报,SkyWalking用gRPC批量上报+本地缓冲区,网络抖动时先缓存本地。
  3. 控制Span数量:一个请求不是所有调用都要打Span。Redis缓存、本地方法调用等低价值Span可以忽略,SkyWalking支持trace.ignore_path过滤。
  4. 存储优化:Elasticsearch设置合理的index lifecycle policy,链路数据保留7-15天即可,超期自动删除。

面试关键点:链路追踪的性能损耗通常在3%-5%以内,但采样率调到100%时可能达到10%以上。生产环境核心链路采样率100%+非核心链路低采样的混合策略最实用。


【面试题】8. Seata AT模式的全局锁机制是怎么工作的?和XA模式比有什么优劣? ​

【标准答案】AT模式是Seata最常用的模式,核心是两阶段提交+全局锁+undo_log。

AT模式执行流程:

  1. 一阶段:业务SQL执行前,拦截SQL解析语义,查询修改前的数据保存到undo_log表,然后执行业务SQL并提交本地事务。此时本地事务已提交,其他非分布式事务可以读到未确认的数据(读未隔离)。
  2. 全局锁:一阶段本地事务提交后,向TC(事务协调器)注册全局锁,锁定该行数据的全局修改权。如果另一个分布式事务也要改同一行,会等全局锁释放,超时后回滚。
  3. 二阶段提交:TC通知各分支提交,异步清理undo_log。
  4. 二阶段回滚:TC通知各分支回滚,根据undo_log反向补偿,恢复数据到修改前。回滚前还要校验当前数据是否被脏写(和undo_log中的after镜像对比),如果被改了就要人工介入。

AT vs XA对比:

维度AT模式XA模式
本步事务提交一阶段就提交二阶段才提交
锁持有时间全局锁(短)数据库锁(长)
性能高,本地事务快速释放低,长事务持锁
侵入性需要undo_log表无侵入,数据库原生支持
隔离性写隔离(全局锁),读默认未隔离完全隔离
数据库要求无特殊要求支持XA协议

AT模式的优势:性能好,一阶段本地事务就提交了,不长时间持有数据库锁,并发度高。 AT模式的劣势:一阶段提交后数据对其他事务可见(脏读),虽然全局锁保护了写隔离,但读隔离需要加@GlobalLock+SELECT FOR UPDATE才能保证。

面试加分:AT模式最大的风险是脏写——一阶段已提交,回滚时发现数据被改了。生产环境建议核心业务表加@GlobalLock读隔离,或者改用TCC模式手动控制资源预留。


【面试题】9. Redisson的看门狗机制是什么?解决了什么问题? ​

【标准答案】看门狗(Watchdog)解决的是分布式锁过期但业务未执行完的问题。

问题场景:设置锁的过期时间30秒,但业务执行了40秒。第30秒锁自动释放,其他线程拿到锁,此时两个线程同时持有锁——分布式锁失效!

看门狗原理:

  1. Redisson加锁时不指定过期时间(leaseTime=-1),默认锁30秒。
  2. 加锁成功后,启动一个后台定时任务(Watchdog),每隔lockWatchdogTimeout/3(默认10秒)检查:如果锁还被当前线程持有,就自动续期到30秒。
  3. 业务执行完毕正常释放锁时,取消看门狗任务。
  4. 如果持有锁的客户端宕机,看门狗自然停止,锁30秒后自动过期释放。

源码关键逻辑:

java
// 加锁成功后
long ttl = lockWatchdogTimeout; // 默认30秒
// 定时续期
Timeout task = commandExecutor.getServiceManager().newTimeout(() -> {
    if (lockHolder.isHeldByCurrentThread()) {
        // 续期到30秒
        expiration.renewExpire();
        // 重新调度自己
        reschedule(task);
    }
}, ttl / 3, TimeUnit.MILLISECONDS); // 每10秒检查一次

使用注意事项:

  1. 必须不指定leaseTime:如果lock(30, TimeUnit.SECONDS)指定了过期时间,看门狗不会启动!这是最常见的坑。
  2. 看门狗超时时间可调:lockWatchdogTimeout默认30秒,可以配置更短或更长,但要大于业务最大执行时间。
  3. 单节点问题:看门狗只在单节点有效。Redis主从切换时,锁可能丢失。对安全性要求高的场景用RedLock(多节点加锁,半数以上成功才算获取锁),但RedLock有争议——Martin Kleppmann认为它不安全,Antirez反驳。实际项目大多数用单节点+看门狗就够用了。

【面试题】10. Spring Cloud Stream消费失败怎么处理?消息积压了怎么救? ​

【标准答案】消费失败和消息积压是两个相关但不同的问题,分开处理。

消费失败处理:

  1. 重试:Spring Cloud Stream内置重试,配置spring.cloud.stream.bindings.input.consumer.max-attempts=3,默认重试3次。重试有退避策略,避免立即重试打爆下游。
  2. 死信队列(DLQ):重试全部失败后,消息进入死信队列,不丢弃。RabbitMQ配置:
yaml
spring:
  cloud:
    stream:
      bindings:
        orderInput:
          consumer:
            max-attempts: 3
      rabbit:
        bindings:
          orderInput:
            consumer:
              auto-bind-dlq: true       # 自动创建死信队列
              republish-to-dlq: true    # 失败消息转发到DLQ
              dlq-ttl: 86400000         # 死信保留24小时
  1. 手动ACK+补偿:对于RocketMQ/Kafka,用手动确认模式,处理成功才commit,失败走重试或补偿逻辑。可以写@ServiceActivator监听死信队列,做人工干预或自动修复后重新投递。

消息积压救援:

  1. 紧急扩容消费者:增加消费者实例数,但要注意分区数(Kafka的partition数)是消费者并行度上限,partition不够要同步扩partition。
  2. 临时转发+并行消费:把积压的消息快速转发到一个新Topic,这个新Topic配更多partition,用临时消费者集群并行消费。
  3. 跳过非核心消息:如果是日志类消息,可以临时丢弃或只消费最新N条,先恢复实时业务。
  4. 根因排查:积压通常是消费端瓶颈。看消费者耗时(是DB慢查询还是第三方接口超时?),针对性优化。RocketMQ可以看消费TPS和积压量的监控曲线。

预防措施:

  • 生产端限流(Sentinel),不让流量暴涨直接打穿MQ
  • 消费端做好幂等,积压恢复时重复消费不出错
  • 设置合理的队列容量告警,积压量超过阈值立即通知
  • 核心业务和非核心业务分Topic,避免非核心消息挤占核心消息的消费资源