Skip to content

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

生成时间:2026-05-21 23:59


【面试题】1. Nacos配置中心如何实现配置的热更新?项目中如何让配置变更实时生效? ​

【标准答案】 Nacos配置热更新有两种方式:一是通过@RefreshScope注解,二是通过@NacosValue注解结合配置监听。

最常用的是@RefreshScope,在需要动态刷新的Bean上加这个注解,Nacos配置变更时会触发Bean的重新创建,从而加载最新配置。底层原理是Nacos客户端通过长轮询机制监听配置变化,服务端有变更时立即推送,客户端收到后发布RefreshEvent事件,Spring Cloud的ContextRefresher处理这个事件,更新Environment中的配置源,然后销毁并重建标注了@RefreshScope的Bean。

实际项目中,配置类或Controller上加@RefreshScope,属性用@Value("${key}")注入即可。但要注意,@RefreshScope会导致Bean代理化,不要滥用,只在确实需要动态更新的Bean上加。另外,如果配置变更需要执行特定逻辑,可以监听RefreshScopeRefreshedEvent事件做额外处理。


【面试题】2. Sentinel的限流算法有哪些?令牌桶和漏桶算法的区别是什么? ​

【标准答案】 Sentinel支持多种限流算法:QPS限流、线程数限流,底层实现基于滑动窗口、令牌桶、漏桶三种算法。

滑动窗口算法:将时间窗口划分为多个小格子,每个格子记录请求数,滑动统计总请求数。Sentinel默认用滑动窗口实现QPS限流,精确度高,适合突发流量控制。

令牌桶算法:以固定速率往桶里放令牌,请求来了从桶里取令牌,有令牌就通过,没令牌就拒绝。特点是允许一定程度的突发流量——桶里有存量令牌时可以瞬间处理多个请求。Sentinel的RateLimiterController就是令牌桶实现。

漏桶算法:请求像水一样流入桶里,桶底以固定速率漏水(处理请求),桶满了就溢出(拒绝)。特点是强制平滑流量,输出速率恒定,无法应对突发。Sentinel的ThrottlingController类似漏桶思想。

核心区别:令牌桶允许突发(桶里有存量),漏桶强制匀速;令牌桶是"按需取令牌",漏桶是"排队等处理"。实际项目中,API网关限流常用令牌桶,因为前端流量本身有波动,允许小范围突发更合理;如果是调用外部API怕把对方打挂,用漏桶更稳。


【面试题】3. Seata的AT模式是如何实现分布式事务的?有什么优缺点? ​

【标准答案】 Seata AT模式是阿里开源的分布式事务解决方案,核心是"自动补偿"——框架自动生成回滚SQL,业务无侵入。

执行流程:一阶段,每个分支事务执行业务SQL时,Seata会解析SQL,记录数据修改前后的快照(before-image、after-image)到undo_log表,然后提交本地事务。二阶段如果全局事务提交,异步删除undo_log;如果回滚,根据before-image自动生成反向SQL执行补偿。

核心组件:

  • TC(Transaction Coordinator):事务协调者,维护全局事务状态
  • TM(Transaction Manager):事务管理器,发起全局事务
  • RM(Resource Manager):资源管理器,管理分支事务

优点:业务代码零侵入,只需加@GlobalTransactional注解;性能好,一阶段直接提交,不阻塞;学习成本低,基本不用改代码。

缺点:依赖数据库本地事务,不支持跨库关联查询;全局锁可能导致死锁;高并发下undo_log表有性能瓶颈;隔离级别默认读未提交,脏读问题需要注意。另外,AT模式对SQL有要求,复杂SQL可能解析失败。

适用场景:单体拆分的微服务、对性能要求高、业务逻辑相对简单的场景。如果是跨库复杂业务或对隔离性要求高,建议用TCC模式。


【面试题】4. Redis分布式锁如何解决锁续期问题?Redisson的看门狗机制是什么? ​

【标准答案】 Redis分布式锁的续期问题是指:锁设置了过期时间,但业务执行时间超过了过期时间,导致锁自动释放,其他线程拿到锁后产生并发问题。

解决方案:Redisson的Watchdog(看门狗)机制。

原理:Redisson加锁时,如果不指定leaseTime,会启动一个后台线程(看门狗),默认每10秒检查一次锁是否还存在,如果存在就自动续期到30秒。只要客户端还活着且锁未显式释放,看门狗就会持续续期,直到业务执行完毕显式解锁。

代码层面:

java
RLock lock = redisson.getLock("myLock");
lock.lock(); // 启动看门狗,自动续期
try {
    // 业务逻辑,执行时间不确定
} finally {
    lock.unlock(); // 显式释放,看门狗停止
}

关键参数:

  • lockWatchdogTimeout:默认30秒,锁的过期时间
  • 续期间隔:默认过期时间的1/3,即10秒

注意事项:如果指定了leaseTime(lock.lock(10, TimeUnit.SECONDS)),看门狗不会启动,业务必须在指定时间内完成;看门狗只在Redisson客户端存活时工作,客户端宕机则锁自动过期。

生产建议:Redisson内部已实现看门狗,直接用lock.lock()即可,无需手动实现续期逻辑;但要做好超时监控,如果业务真的执行太久,需要考虑是否需要优化业务逻辑或换方案。


【面试题】5. Spring Cloud Gateway的过滤器链是如何工作的?如何实现统一鉴权? ​

【标准答案】 Spring Cloud Gateway的过滤器链基于WebFlux的响应式编程模型,核心是GatewayFilterChain,每个过滤器可以对请求进行预处理和后处理。

过滤器类型:

  • GatewayFilter:网关过滤器,作用在单个路由或路由组上
  • GlobalFilter:全局过滤器,作用在所有路由上

执行流程: 请求进来 → DispatcherHandler → RoutePredicateHandlerMapping匹配路由 → FilteringWebHandler加载过滤器链 → 按order排序依次执行 → 每个过滤器的filter()方法处理请求 → 调用chain.filter(exchange).then()传递给下一个过滤器 → 最后转发到后端服务 → 响应返回时按相反顺序执行后处理逻辑。

统一鉴权实现:

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.isEmpty(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        // 验证token,解析用户信息
        Claims claims = JwtUtil.parseToken(token);
        if (claims == null) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        // 将用户信息透传给下游服务
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header("X-User-Id", claims.get("userId").toString())
            .build();
        return chain.filter(exchange.mutate().request(request).build());
    }
    
    @Override
    public int getOrder() {
        return -100; // 优先级高,先执行
    }
}

生产要点:过滤器顺序很重要,鉴权要靠前;响应式编程不要阻塞;白名单路径要跳过鉴权;Token解析失败要返回标准错误码;用户信息透传给下游服务避免重复解析。


【面试题】6. OpenFeign的超时重试如何配置?有哪些坑需要注意? ​

【标准答案】 OpenFeign的超时分两层:connectTimeout(连接超时)和readTimeout(读取超时)。重试通过Retryer实现。

配置方式:

yaml
feign:
  client:
    config:
      default:
        connectTimeout: 5000  # 连接超时5秒
        readTimeout: 10000    # 读取超时10秒
      specific-service:
        connectTimeout: 3000
        readTimeout: 5000

重试配置:

java
@Configuration
public class FeignConfig {
    @Bean
    public Retryer retryer() {
        // period: 重试间隔,maxAttempts: 最大重试次数(包含首次调用)
        return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 3);
    }
}

常见坑:

  1. 默认重试:Feign默认有重试机制,Retryer.Default会重试5次,生产环境需要自定义Retryer或用Retryer.NEVER_RETRY关闭。

  2. 超时时间叠加:如果设置了重试3次,每次超时10秒,最坏情况下会等待30秒,要配合接口的超时设置。

  3. Hystrix/Sentinel冲突:如果同时用了熔断器,熔断的超时要小于Feign的超时,否则熔断不生效。比如Feign读超时10秒,熔断超时要设8秒。

  4. GET幂等性问题:Feign默认只对GET请求重试,POST请求不会重试。如果POST需要重试,要自定义Retryer,但要确保接口幂等。

  5. 超时穿透:Feign超时后,后端服务可能还在执行,要考虑是否需要取消请求或做资源清理。

最佳实践:生产环境一般关闭Feign重试,交给上游(如网关或服务调用方)统一处理;如果开重试,必须确保接口幂等;超时时间根据P99响应时间设置,宁可快速失败也不要长时间等待。


【面试题】7. SkyWalking链路追踪的采样策略有哪些?对性能有什么影响? ​

【标准答案】 SkyWalking采样策略决定哪些请求会被追踪,主要三种:

1. 全量采样(默认)

  • 配置:agent.sample_n_per_3_secs = -1 或不配置
  • 所有请求都采集,链路完整,便于问题排查
  • 缺点:数据量大,存储压力大,对性能有影响(每次请求产生约0.5-1ms开销)

2. 固定比例采样

  • 配置:agent.sample_n_per_3_secs = 9,表示每3秒采样9个请求
  • 适合高并发场景,控制数据量
  • 缺点:可能漏掉关键链路,问题复现难

3. 动态采样(推荐)

  • 通过SkyWalking后台配置采样率,支持动态调整
  • 可按服务、接口、时间维度设置不同采样率
  • 核心接口100%采样,非核心接口10%采样

性能影响:

  • CPU开销:每个请求生成TraceId、SpanId,收集调用链信息,约占请求处理时间的1-3%
  • 内存开销:Agent本地缓存调用链数据,批量上报,约占用10-50MB堆内存
  • 网络开销:调用链数据上报到OAP Server,带宽占用约为请求量的5-10%
  • 存储开销:Elasticsearch存储,数据量大时需要考虑分片和保留策略

优化建议:

  1. 生产环境用动态采样,核心服务100%,非核心服务10-30%
  2. 调整上报频率:agent.span_limit_per_segment = 300,限制单个链路Span数量
  3. 忽略不需要追踪的接口:trace.ignore_path = /health,/metrics
  4. 使用异步上报:is_plugin_apm_toolkit_trace_active = true
  5. 合理设置数据保留周期:一般保留7-15天,问题排查时临时调大

监控策略:SkyWalking本身也会产生性能开销,建议单独部署Agent,不要和业务服务抢占资源;同时监控SkyWalking自身的性能指标(OAP Server的吞吐量、ES的写入延迟)。


【面试题】8. Eureka的自我保护机制是什么?生产环境应该如何配置? ​

【标准答案】 Eureka自我保护机制是为了防止网络分区故障导致的"误杀"——当Eureka Server短时间内丢失大量客户端心跳,不会立即剔除这些服务,而是进入自我保护模式,保留所有注册信息。

触发条件:当每分钟期望续约数(numberOfRenewsPerMinThreshold)低于实际续约数的85%时触发。计算公式:

  • 期望续约数 = 服务数 × 2(每个服务每分钟发2次心跳)× 续约百分比阈值(默认0.85)

自我保护模式行为:

  • 不再剔除注册表中任何服务实例
  • 继续接受新服务注册
  • 继续提供服务查询
  • 控制台显示红色警告

生产配置建议:

开发/测试环境:关闭自我保护,方便调试

yaml
eureka:
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 5000  # 5秒清理一次失效实例

生产环境:开启自我保护,但要调整参数

yaml
eureka:
  server:
    enable-self-preservation: true
    renewal-percent-threshold: 0.75  # 降低阈值,减少误触发
    eviction-interval-timer-in-ms: 60000  # 60秒清理一次
    peer-eureka-nodes-update-interval-ms: 300000  # 集群节点同步间隔
  instance:
    lease-renewal-interval-in-seconds: 30  # 心跳间隔30秒
    lease-expiration-duration-in-seconds: 90  # 过期时间90秒

最佳实践:

  1. 生产必须开启自我保护,但建议阈值设为0.6-0.75,避免频繁进入保护模式
  2. 配合健康检查:eureka.client.healthcheck.enabled=true,让Eureka感知服务真实状态
  3. 部署Eureka集群(至少2个节点),避免单点故障
  4. 监控自我保护状态,频繁触发说明网络或服务有问题
  5. 考虑迁移到Nacos:Eureka已停止维护,Nacos支持健康检查更灵活,自我保护机制更完善

【面试题】9. RocketMQ如何实现顺序消息?有哪些使用场景和注意事项? ​

【标准答案】 RocketMQ顺序消息分两种:全局有序和分区有序。

全局有序:整个Topic下所有消息严格按FIFO顺序消费。实现方式是Topic只建一个Queue,所有消息发到同一个Queue。缺点是并发度低,吞吐量受限,极少使用。

分区有序(常用):同一业务ID的消息发送到同一个Queue,由同一个消费者处理,保证局部有序。

实现方式:

java
// 生产者:发送顺序消息,指定MessageQueueSelector
Message msg = new Message("TopicTest", "TagA", "OrderID001", body);
SendResult result = producer.send(msg, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        Long orderId = (Long) arg;  // 根据订单ID选择队列
        long index = orderId % mqs.size();
        return mqs.get((int)index);
    }
}, orderId);  // orderId作为选择key

消费者顺序消费:

java
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
        // 顺序消费,同一个Queue的消息会被加锁
        for (MessageExt msg : msgs) {
            // 处理消息
        }
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

典型场景:

  • 订单状态流转:创建→支付→发货→完成,必须按顺序
  • 数据库binlog同步:保证SQL执行顺序
  • 充值回调:同一用户的充值请求需顺序处理

注意事项:

  1. 选择Key要合理:用业务唯一标识(订单ID、用户ID)作为分区Key,避免数据倾斜
  2. 消费失败处理:顺序消费失败会阻塞后续消息,必须设置合理的重试次数和死信处理
  3. 消费者扩容:分区有序场景下,消费者数量不能超过Queue数量,否则部分消费者空闲
  4. 性能权衡:顺序消息牺牲并发性,Queue越多并发越高,但同一Key的顺序保证越弱
  5. 异常处理:消费者宕机时,Broker会重新分配Queue,期间可能短暂乱序

最佳实践:业务层面尽量做到幂等,即使短暂乱序也能通过状态机校验兜底;消费失败时快速失败,不要无限重试阻塞队列。


【面试题】10. Spring Cloud Kubernetes与Spring Cloud传统组件有什么区别?什么场景下选择Kubernetes方案? ​

【标准答案】 Spring Cloud Kubernetes是用Kubernetes原生能力替代Spring Cloud传统组件的方案。

核心组件对比:

功能Spring Cloud传统组件Spring Cloud Kubernetes
服务注册发现Eureka/NacosK8s Service + DNS
配置中心Spring Cloud Config/NacosK8s ConfigMap/Secret
负载均衡Ribbon/LoadBalancerK8s Service负载均衡
熔断限流Hystrix/Sentinel可选,或用Istio
API网关Spring Cloud GatewayIngress/Istio Gateway

Spring Cloud Kubernetes优势:

  1. 原生集成:直接用K8s的Service发现,无需额外部署注册中心
  2. 配置热更新:ConfigMap/Secret变更自动注入Pod,无需重启
  3. 运维简化:减少中间件,降低运维复杂度
  4. 资源效率:无需Eureka、Config Server等组件,节省资源
  5. 云原生标准:符合K8s生态,便于接入Istio等Service Mesh

Spring Cloud Kubernetes劣势:

  1. 强绑定K8s:无法在非K8s环境运行,开发调试麻烦
  2. 功能受限:K8s Service只支持轮询,无法实现加权、一致性哈希等高级策略
  3. 无熔断限流:K8s本身不提供熔断,需要配合Istio或保留Sentinel
  4. 配置管理弱:ConfigMap缺乏Nacos那样的分组、命名空间管理能力
  5. 链路追踪缺失:需要单独集成SkyWalking或Istio

选择建议:

选Spring Cloud Kubernetes:

  • 已全面上K8s,且不打算用其他编排平台
  • 团队熟悉K8s生态,想减少中间件运维
  • 对负载均衡策略要求不高,简单的轮询即可
  • 准备引入Service Mesh(Istio),逐步替换Spring Cloud组件

选Spring Cloud传统组件:

  • 混合部署(部分K8s,部分物理机/虚拟机)
  • 需要复杂的流量治理(灰度发布、流量镜像、AB测试)
  • 配置管理需求复杂(多环境、多租户、动态推送)
  • 团队对Spring Cloud生态更熟悉,K8s能力不足

混合方案(推荐):

  • 服务发现用K8s Service
  • 配置中心用Nacos(功能更强)
  • 熔断限流用Sentinel
  • API网关用Spring Cloud Gateway

这样既利用了K8s原生能力,又保留了Spring Cloud的灵活性。核心原则:K8s负责调度和基础服务发现,Spring Cloud负责业务层治理。