Skip to content

本轮 SpringCloud微服务架构面试题库(带答案) ​

生成时间:2026-05-23 15:51


【面试题】1. Nacos作为注册中心,如何保证服务发现的实时性?心跳机制和健康检查是怎么配合的? ​

【标准答案】

这个问题要分三层来答。

第一层,心跳机制。 Nacos客户端默认每5秒发送一次心跳,服务端收到心跳后更新实例的最后心跳时间。如果15秒没收到心跳,标记为不健康;30秒还没收到,直接剔除。这个时间窗口是可以配置的,根据业务容忍度调整。

第二层,健康检查。 Nacos有两种模式:临时实例用客户端心跳,永久实例用服务端主动探测。临时实例适合微服务场景——服务挂了,自动剔除;永久实例适合数据库这种需要人工介入的基础设施。

第三层,实时性保障。 客户端本地有服务列表缓存,同时订阅服务变更事件。当服务端有实例上下线,会主动推送给订阅的客户端。这是推拉结合:心跳保活是拉,变更推送是推。所以正常情况下,服务发现是毫秒级的。

面试加分点:提到Nacos 2.x之后用gRPC长连接替代了HTTP短轮询,实时性更好了。


【面试题】2. Ribbon和Spring Cloud LoadBalancer有什么区别?生产环境怎么选? ​

【标准答案】

核心区别就一句话:Ribbon已死,LoadBalancer是官方接班人。

Ribbon是Netflix家的,2018年进入维护模式,不再新增功能。Spring Cloud 2020版本之后,官方用LoadBalancer替换了Ribbon作为默认负载均衡组件。

功能上对比:

  • Ribbon内置7种策略:轮询、随机、加权、最小并发、一致性哈希等,功能完善但代码老旧
  • LoadBalancer初期功能简单,现在也支持轮询、随机、加权,但一致性哈希这类高级策略需要自己扩展

生产选型建议:

  • 新项目直接用LoadBalancer,别用Ribbon,跟官方走不吃亏
  • 老项目如果用了Ribbon的高级策略,迁移成本要评估——可以先共存,逐步替换
  • 如果需要自定义负载均衡策略,LoadBalancer的Reactive API更现代,写起来更顺手

代码迁移点: @LoadBalanced注解不用改,但Ribbon的IRule要换成LoadBalancer的ReactorServiceInstanceLoadBalancer接口实现。


【面试题】3. OpenFeign调用超时了怎么排查?超时配置的优先级是什么? ​

【标准答案】

排查思路三步走:

第一步,确认超时来源。 看日志是connect timeout还是read timeout。connect timeout是连不上服务端,可能是服务挂了或网络问题;read timeout是连上了但响应慢,可能是业务逻辑慢或下游依赖慢。

第二步,检查配置链。 OpenFeign的超时配置分两层:Feign层和Ribbon/LoadBalancer层。优先级是:Feign配置 > Ribbon配置 > 默认值。

优先级具体规则:

yaml
# Feign层配置(优先级最高)
feign.client.config.default.connectTimeout: 5000
feign.client.config.default.readTimeout: 10000

# Ribbon层配置(Feign没配时生效)
ribbon.ConnectTimeout: 3000
ribbon.ReadTimeout: 6000

第三步,分布式场景还要看网关。 如果经过Spring Cloud Gateway,还要检查Gateway的超时配置,它也有自己的connect-timeout和response-timeout。

实际案例: 我之前遇到一个坑,Feign和Ribbon都配了超时,但有个服务老超时。排查发现是Gateway配置了30秒超时,但某个慢接口需要50秒。解决方案是针对特定接口单独配置超时,而不是全局放大。


【面试题】4. Sentinel和Hystrix的熔断机制有什么区别?生产环境怎么选? ​

【标准答案】

先说结论:新项目用Sentinel,老项目逐步迁移,Hystrix已停止维护。

核心区别对比:

维度HystrixSentinel
维护状态2018年停止维护阿里持续维护
熔断策略错误比例、错误数量错误比例、错误数量、慢调用比例
限流能力仅有信号量隔离QPS限流、线程池隔离、热点参数限流
控制台Hystrix Dashboard(简陋)Sentinel Dashboard(功能丰富)
动态规则需要自己实现开箱即用,支持多种数据源

熔断状态机,两者一致: 关闭→开启→半开→关闭。半开状态放一个请求试探,成功就关闭,失败继续开启。

生产选型建议:

  • 新项目无脑Sentinel,生态完善,控制台好用,规则动态下发
  • 老项目如果Hystrix用得稳,迁移收益要评估——但长期看,技术债要还
  • 如果只需要熔断,不需要限流,Resilience4j也是个轻量选择

Sentinel的杀手锏: 热点参数限流——比如限制"查询某个热门商品"的QPS,而不是限制整个接口。这个Hystrix做不到。


【面试题】5. Spring Cloud Gateway如何实现统一的JWT鉴权?Token校验失败怎么返回友好的错误信息? ​

【标准答案】

实现方案分三块:

第一块,过滤器链鉴权。 写一个AuthGlobalFilter实现GlobalFilter和Ordered接口,在filter方法里解析JWT。

java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !validateToken(token)) {
            return onError(exchange, "未授权访问", HttpStatus.UNAUTHORIZED);
        }
        // 解析成功,将用户信息放入header透传给下游
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header("X-User-Id", getUserIdFromToken(token))
            .build();
        return chain.filter(exchange.mutate().request(request).build());
    }
    
    @Override
    public int getOrder() { return -100; } // 优先级要高
}

第二块,友好错误返回。 不要返回默认的401页面,要返回JSON格式的错误信息:

java
private Mono<Void> onError(ServerWebExchange exchange, String msg, HttpStatus status) {
    ServerHttpResponse response = exchange.getResponse();
    response.setStatusCode(status);
    response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
    String body = "{\"code\":401,\"message\":\"" + msg + "\"}";
    return response.writeWith(Mono.just(response.bufferFactory().wrap(body.getBytes())));
}

第三块,白名单配置。 登录接口、健康检查接口不需要鉴权,在配置中心或数据库维护白名单路径,过滤器里判断跳过。

面试加分点: 提到Token刷新机制——Access Token短期(如30分钟),Refresh Token长期(如7天),过期前用Refresh Token换取新的Access Token,无感刷新。


【面试题】6. Nacos Config配置中心如何实现配置的热更新?应用收到配置变更后怎么自动刷新? ​

【标准答案】

热更新原理分三步:

第一步,长轮询监听。 Nacos客户端通过长轮询监听配置变更,服务端有变更立即推送。2.x版本用gRPC长连接,实时性比HTTP长轮询更好。

第二步,配置变更推送。 服务端配置变更后,推送给所有订阅的客户端。客户端收到新配置,更新本地缓存,然后触发变更事件。

第三步,自动刷新机制。 这是面试关键点,两种方式:

方式一:@RefreshScope注解

java
@RestController
@RefreshScope  // 关键注解
public class ConfigController {
    @Value("${app.config}")
    private String config;
    
    @GetMapping("/config")
    public String getConfig() {
        return config;
    }
}

配置变更后,Spring Cloud会销毁并重建这个Bean,重新注入最新配置。

方式二:监听配置变更事件

java
@Component
public class ConfigChangeListener {
    @NacosConfigListener(dataId = "app.yml", groupId = "DEFAULT_GROUP")
    public void onConfigChange(String newConfig) {
        // 自定义处理逻辑
        log.info("配置已更新: {}", newConfig);
    }
}

生产注意事项:

  • @RefreshScope有性能开销,Bean会重建,别滥用
  • 复杂配置变更可能需要重启应用才能完全生效,比如数据库连接池参数
  • 配置回滚:Nacos支持配置历史版本,可以一键回滚

【面试题】7. 分布式链路追踪中,TraceId和SpanId是怎么传递的?跨线程调用时如何保证链路不断? ​

【标准答案】

先说概念:

  • TraceId:整个请求链路的唯一标识,从入口生成,贯穿所有服务
  • SpanId:单个处理单元的标识,每经过一个服务/组件就生成一个新的Span

传递机制:HTTP调用场景

当服务A调用服务B时,Spring Cloud Sleuth会自动把TraceId和SpanId放入HTTP Header:

  • traceparent(W3C标准):00-{trace-id}-{parent-span-id}-01
  • 或者老版本用:X-B3-TraceId、X-B3-SpanId

服务B收到请求后,从Header提取TraceId继续使用,生成新的SpanId,并记录ParentSpanId为A的SpanId。

跨线程场景:链路断裂问题

问题场景:主线程收到请求后,丢到线程池异步处理,新线程里TraceId丢失了。

解决方案:

java
// 方案一:手动传递
@Async
public void asyncProcess(String traceId) {
    MDC.put("traceId", traceId);  // 新线程里手动设置
    try {
        // 业务逻辑
    } finally {
        MDC.clear();
    }
}

// 方案二:Sleuth自动包装(推荐)
@Autowired
private Tracer tracer;

public void asyncProcess() {
    Span span = tracer.nextSpan().name("async-process");
    try (Tracer.SpanInScope ws = tracer.withSpan(span.start())) {
        // Sleuth自动在新线程维护链路上下文
        executorService.submit(() -> {
            // 这里的TraceId自动继承
        });
    }
}

方案三:使用Sleuth的LazyTraceExecutor包装线程池

java
@Bean
public Executor executor() {
    return new LazyTraceExecutor(tracer, Executors.newFixedThreadPool(10));
}

这样丢到线程池的任务会自动继承链路上下文。


【面试题】8. Seata的AT模式和TCC模式有什么区别?各适合什么场景? ​

【标准答案】

AT模式:自动化,零侵入,适合大多数场景

AT模式的核心是"自动补偿":

  1. 一阶段:执行业务SQL,自动记录前后镜像到undo_log表
  2. 二阶段提交:删除undo_log
  3. 二阶段回滚:根据undo_log反向生成补偿SQL

优点: 业务代码无侵入,像写本地事务一样写分布式事务 缺点: 有全局锁,并发性能有瓶颈;只支持SQL操作,不支持非DB资源

TCC模式:手动补偿,灵活但侵入大

TCC要求业务实现三个方法:

  • Try:资源预留,比如冻结账户余额
  • Confirm:确认提交,比如扣减冻结金额
  • Cancel:回滚补偿,比如解冻余额

优点: 性能高(无全局锁),支持非DB资源(如Redis、MQ) 缺点: 业务侵入大,三个方法都要自己写,还要处理幂等、空回滚、悬挂等问题

选型建议:

场景推荐模式
纯数据库操作、并发不高AT模式
高并发、性能敏感TCC模式
涉及非DB资源(Redis、MQ等)TCC模式
快速上线、开发成本敏感AT模式
对一致性要求极高、有专业团队TCC模式

面试加分点: 提到AT模式的"写隔离"问题——全局锁保证隔离性,但可能导致死锁,需要合理设计超时时间。


【面试题】9. Redis分布式锁用Redisson比直接用SET NX有什么优势?看门狗机制是什么? ​

【标准答案】

SET NX的问题:

java
// 简单实现
jedis.set("lock:key", "value", "NX", "EX", 30);

看似没问题,但有几个坑:

  1. 锁续期问题:业务执行超过30秒,锁自动过期,其他线程拿到锁,你的业务还在跑——并发安全问题
  2. 锁释放问题:线程A释放了线程B的锁(value没校验,或者校验了但不是原子操作)
  3. 不可重入:同一个线程多次获取同一把锁会死锁
  4. 集群问题:主从切换时锁可能丢失

Redisson的优势:

1. 看门狗机制(Watchdog) 默认锁过期时间30秒,Redisson后台有个定时任务(每10秒检查),如果线程还持有锁,自动续期。业务执行多久都不怕锁过期。

java
RLock lock = redisson.getLock("myLock");
lock.lock();  // 看门狗自动生效
try {
    // 业务执行超过30秒也没事
    Thread.sleep(60000);
} finally {
    lock.unlock();
}

2. 自动校验+Lua脚本原子释放

java
// Redisson内部用Lua保证原子性
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

3. 可重入锁支持 同一个线程可以多次获取同一把锁,内部用计数器记录。

4. 红锁(RedLock)解决集群问题 多节点同时获取锁,超过半数成功才算成功,避免单节点故障导致的锁丢失。

生产建议: 无脑用Redisson,别自己造轮子。看门狗+可重入+Lua脚本,这些坑Redisson都帮你踩过了。


【面试题】10. Spring Cloud Stream集成RocketMQ,如何保证消息不丢失?消费失败如何处理? ​

【标准答案】

消息不丢失要盯三个环节:

环节一:生产端

yaml
spring:
  cloud:
    stream:
      rocketmq:
        binder:
          name-server: localhost:9876
        bindings:
          output:
            producer:
              sync: true  # 同步发送,等待Broker确认
              retry-times-when-send-failed: 3  # 失败重试3次

关键配置:sync: true同步发送,配合重试机制。异步发送虽然快,但丢失了生产端不知道。

环节二:Broker端

  • 开启同步刷盘:flushDiskType=SYNC_FLUSH
  • 开启主从同步复制:brokerRole=SYNC_MASTER
  • 这样即使Broker宕机,消息也不丢

环节三:消费端

yaml
spring:
  cloud:
    stream:
      bindings:
        input:
          consumer:
            ack-mode: manual  # 手动确认
java
@StreamListener(MySink.INPUT)
public void handleMessage(Message<String> message, Acknowledgment ack) {
    try {
        // 业务处理
        process(message.getPayload());
        ack.acknowledge();  // 处理成功才确认
    } catch (Exception e) {
        // 不确认,消息会重新投递
        log.error("处理失败", e);
    }
}

消费失败处理策略:

  1. 重试队列:RocketMQ自动重试16次,间隔递增(10秒→1分钟→10分钟)
  2. 死信队列(DLQ):重试16次后进入死信队列,人工介入处理
  3. 业务补偿:关键业务可以记录失败消息到数据库,定时任务扫描重试
yaml
spring:
  cloud:
    stream:
      rocketmq:
        bindings:
          input:
            consumer:
              delay-level-when-next-consume: 3  # 重试间隔级别

生产最佳实践:

  • 重要消息用同步发送
  • 消费失败至少打日志,最好入库
  • 死信队列要有监控告警,别让消息悄悄消失