Skip to content

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

生成时间:2026-05-24 20:05


【面试题】1. Nacos作为注册中心时,如何保证服务下线感知的实时性?生产环境有哪些优化手段? ​

【标准答案】 Nacos 1.x 采用心跳机制,默认5秒发送心跳,15秒未收到心跳标记不健康,30秒剔除。但这在生产环境下有延迟。

实际优化手段: 第一,调整心跳参数:将心跳间隔从5秒缩短到3秒,超时时间从15秒调整到6秒,这样最快6秒就能感知到服务异常。配置在application.yml中:spring.cloud.nacos.discovery.heartbeat-interval=3000。

第二,开启UDP推送:Nacos支持服务变更时主动UDP推送,客户端收到推送立即更新本地缓存,比定时拉取快得多。默认开启,确保防火墙没屏蔽UDP端口。

第三,服务优雅下线:在服务停机前主动调用Nacos API注销实例,curl -X DELETE "nacos:8848/nacos/v1/ns/instance?serviceName=xxx&ip=xxx&port=xxx",配合Spring的@PreDestroy或DisposableBean实现。

第四,双保险机制:注册中心剔除只是第一步,调用方还要配置Ribbon或LoadBalancer的重试和超时策略,即使Nacos感知慢,调用方也能快速切换到健康实例。

我之前在某电商项目,通过缩短心跳+UDP推送+优雅下线,把服务下线感知时间从30秒压到5秒以内,基本实现"秒级感知"。


【面试题】2. Sentinel和Hystrix在熔断降级机制上的核心区别是什么?实际选型怎么考虑? ​

【标准答案】 两者核心区别在四个维度:

架构模式不同:Hystrix采用线程池隔离,每个依赖一个独立线程池,请求先入线程池再执行,隔离性强但线程开销大;Sentinel支持线程池和信号量两种,默认信号量模式,性能更高,QPS上万没问题。

熔断策略不同:Hystrix基于错误比例和请求数量,比如10秒内20次请求失败率超50%就熔断;Sentinel更灵活,支持慢调用比例、异常比例、异常数三种策略,还能按响应时间熔断,更贴合实际场景。

规则持久化:Hystrix规则写死在代码里,改个阈值要重新发布;Sentinel有可视化控制台,支持动态规则配置和持久化到Nacos/ZooKeeper,改配置秒级生效。

生态和社区:Hystrix已停止维护,Netflix宣布进入维护模式;Sentinel是阿里巴巴开源,Spring Cloud Alibaba主力组件,持续迭代中。

选型建议:新项目无脑Sentinel,性能好、功能强、社区活跃。老项目如果已经深度集成Hystrix且稳定运行,没必要强迁;但遇到性能瓶颈或需要动态配置,就果断切Sentinel。我之前负责的项目从Hystrix迁移到Sentinel,QPS提升30%,CPU占用下降15%。


【面试题】3. Spring Cloud Gateway如何实现统一鉴权?Token验证应该放在网关还是各微服务? ​

【标准答案】 统一鉴权推荐放在网关层,但要有合理分层:

网关层职责:验证Token有效性、解析用户身份、提取关键信息放入Header透传。比如解析JWT后,把userId、roles放到X-User-Id、X-User-Roles请求头,下游服务直接使用。

微服务层职责:精细权限校验。网关判断"你是谁",微服务判断"你能做什么"。比如订单服务,网关验证登录后,订单服务再校验这个用户能否操作这笔订单。

具体实现:Gateway通过GatewayFilter或GlobalFilter实现鉴权:

java
@Component
public class AuthGlobalFilter implements GlobalFilter {
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        // 验证token,解析用户信息
        String userId = jwtUtil.parse(token);
        // 放入header透传
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header("X-User-Id", userId)
            .build();
        return chain.filter(exchange.mutate().request(request).build());
    }
}

安全加固:微服务要校验请求是否来自网关,防止绕过网关直接调用。可以用IP白名单或网关和微服务间的共享密钥签名验证。

这种架构的好处:微服务不需要关心认证逻辑,专注业务;网关统一管控,安全策略一处配置全局生效。但要警惕Token泄露风险,敏感操作建议二次验证。


【面试题】4. 分布式事务Seata的AT模式原理是什么?什么场景不适合用AT模式? ​

【标准答案】 AT模式是Seata的核心,核心原理是"自动补偿":

一阶段:执行业务SQL的同时,Seata自动解析SQL,记录数据前后镜像到undo_log表。比如更新账户余额从100到80,前后镜像分别是100和80。

二阶段提交:成功就删除undo_log,失败就根据undo_log反向生成SQL回滚,把余额从80恢复到100。

全局锁机制:AT模式用全局锁解决脏写问题。事务提交前先抢全局锁,拿到锁才能提交。如果其他分布式事务也改这条数据,必须等全局锁释放,避免并发冲突。

不适合AT模式的场景:

  1. 跨数据库厂商:undo_log依赖数据库方言,MySQL和Oracle混用时可能出问题。
  2. 高并发写场景:全局锁会阻塞并发事务,秒杀、抢红包这类高并发场景,AT模式性能扛不住,建议用TCC或可靠消息。
  3. 复杂SQL:存储过程、触发器、多表关联更新,Seata解析不出来,无法生成正确的undo_log。
  4. 非关系型数据库:AT模式依赖关系型数据库的事务机制,Redis、MongoDB不支持。
  5. 对性能要求极高:AT模式有额外的SQL解析和undo_log写入开销,TPS要求过万的场景要评估性能影响。

我之前在某支付系统,账户核心交易用TCC模式,保障强一致性和高并发;非核心业务比如积分发放用AT模式,开发效率高。


【面试题】5. OpenFeign的负载均衡是如何实现的?如何自定义负载均衡策略? ​

【标准答案】 OpenFeign本身不做负载均衡,它通过集成Spring Cloud LoadBalancer或Ribbon实现。

工作机制:Feign发起请求时,会把服务名解析成具体实例。这个过程委托给LoadBalancerClient,LoadBalancerClient调用负载均衡策略选择一个实例,然后Feign向该实例发起HTTP请求。

默认策略:Spring Cloud LoadBalancer默认轮询(RoundRobin),依次选择实例。Ribbon默认也是轮询。

自定义策略方式:

方式一:配置类方式

java
@Configuration
public class LoadBalancerConfig {
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment env, LoadBalancerClientFactory factory) {
        String serviceId = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
            factory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class), 
            serviceId);
    }
}

然后用@LoadBalancerClient(value = "user-service", configuration = LoadBalancerConfig.class)指定服务使用这个配置。

方式二:配置文件方式

yaml
spring:
  cloud:
    loadbalancer:
      ribbon:
        enabled: false  # 禁用Ribbon,使用Spring Cloud LoadBalancer

方式三:权重策略 如果有实例配置了权重元数据,可以实现加权随机或加权轮询。比如某个实例配置instance-weight: 3,它被选中的概率就是其他实例的3倍。

实际项目中,我建议根据业务特点选择:无状态服务用轮询就行,有状态服务或硬件配置差异大的,用权重策略合理分配流量。


【面试题】6. 微服务架构下如何实现配置的热更新?Nacos Config的原理是什么? ​

【标准答案】 配置热更新的核心是"配置中心推送+客户端动态刷新":

Nacos Config原理:

  1. 客户端长轮询:微服务启动时向Nacos发起长轮询请求,默认30秒超时。这期间如果配置有变更,Nacos立即返回;没变更则30秒后返回,客户端再发起新请求。
  2. 配置缓存:客户端本地缓存配置,同时Nacos集群也缓存,即使Nacos短暂宕机,服务仍能读取本地缓存继续运行。
  3. 动态刷新:配置变更推送后,Nacos客户端触发RefreshEvent,Spring Cloud的@RefreshScope注解的Bean会重新创建,注入新配置值。

实现热更新两种方式:

方式一:@RefreshScope注解

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

配置更新后,这个Controller的Bean会重建,configValue自动刷新。

方式二:@NacosValue自动刷新

java
@NacosValue(value = "${app.config.value}", autoRefreshed = true)
private String configValue;

生产注意事项:

  1. 配置回滚:Nacos支持配置历史版本,出问题一键回滚,建议每次变更都写备注。
  2. 灰度发布:Nacos支持按实例IP或标签灰度推送配置,先在1台实例验证,再全量发布。
  3. 配置加密:敏感配置用Nacos的配置加密功能,Jasypt或KMS方案,明文配置是安全隐患。
  4. 多环境隔离:用namespace隔离开发、测试、生产环境,用group隔离不同业务线,避免配置串环境。

我之前某项目把数据库连接池参数放Nacos,压测时动态调整maxPoolSize,实现运行时调优而不重启服务。


【面试题】7. Redis分布式锁用SET NX EX有什么坑?Redisson是如何解决的? ​

【标准答案】 SET key value NX EX seconds是最简单的分布式锁实现,但有几个经典坑:

坑一:锁误删 场景:A线程拿到锁,执行业务超时,锁自动过期;B线程获取锁后,A线程执行完,执行DEL把B的锁删了。 解决:锁的value设为唯一标识(如UUID),释放锁时校验value是不是自己的,Lua脚本保证原子性:

lua
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
end
return 0

坑二:锁续期 场景:业务执行时间超过锁过期时间,锁自动释放,其他线程获取锁,并发安全问题。 解决:Redisson的WatchDog机制,默认锁30秒过期,后台线程每10秒检查,如果持有锁的线程还在运行,就续期到30秒。

坑三:主从切换丢锁 场景:主节点写入锁成功但还没同步到从节点,主节点宕机,从节点升为主,锁丢失。 解决:Redisson的RedLock红锁,向多个独立Redis节点同时获取锁,超过半数成功才算获取成功。

坑四:死锁 场景:获取锁后程序crash,没执行释放锁,锁永远不释放。 解决:设置合理的过期时间EX参数,确保锁最终会释放。

Redisson的优势:

  1. 封装了上面所有坑,开箱即用
  2. 提供可重入锁、读写锁、公平锁等多种类型
  3. WatchDog自动续期,业务无感知
  4. 分布式锁+条件变量(Condition)支持复杂并发场景

生产建议:单机场景用Redisson普通锁即可;对可用性要求极高,用RedLock或迁移到Zookeeper分布式锁。


【面试题】8. 链路追踪Sleuth+Zipkin的核心原理是什么?TraceId如何跨服务传递? ​

【标准答案】 链路追踪核心概念是Trace和Span:

  • Trace:一次完整请求的轨迹,贯穿所有服务,全局唯一的TraceId标识
  • Span:单个服务的处理过程,有自己的SpanId,记录开始时间、结束时间、异常信息

TraceId传递机制:

  1. 请求入口:网关或第一个服务生成TraceId,放入HTTP Header X-B3-TraceId
  2. 跨服务调用:OpenFeign/RestTemplate拦截器自动读取Header中的TraceId,透传到下游服务
  3. 异步场景:线程池或消息队列场景,Sleuth通过TracingExecutorService包装线程池,或消息Header传递,保证TraceId不丢失

Sleuth核心原理:

  • 通过Brave库实现,对常用组件做无侵入式埋点
  • 自动拦截HTTP请求、RPC调用、消息队列,生成Span并上报
  • 采样策略可配置,比如采样率10%,降低存储压力

Zipkin架构:

  • Collector:接收各服务上报的Span数据
  • Storage:存储数据,支持MySQL、Elasticsearch、Cassandra等
  • API:查询接口
  • UI:可视化展示调用链、耗时分析

生产优化建议:

  1. 采样策略:高并发场景设为1%~10%,问题排查时临时调高
  2. 异步上报:Span数据用异步方式发送,避免阻塞业务线程
  3. 存储选型:数据量小用MySQL,量大用Elasticsearch,支持复杂查询和高吞吐
  4. 敏感信息:Span中的请求参数、响应体可能含敏感数据,配置脱敏规则

我之前用SkyWalking做链路追踪,相比Zipkin,SkyWalking是字节码增强,无需改动代码,还支持服务拓扑图、告警功能,更全面。


【面试题】9. Spring Cloud Stream如何保证消息的可靠性投递?消息积压怎么处理? ​

【标准答案】 消息可靠性涉及三个环节:生产端、Broker、消费端。

生产端可靠性:

  1. 消息确认机制:Spring Cloud Stream通过@EnableBinding和@StreamListener处理消息,生产者配置spring.cloud.stream.bindings.output.producer.required-groups确保消息持久化。
  2. 事务消息:RocketMQ支持事务消息,本地事务和消息发送原子性保证。先发半消息,本地事务成功再提交消息,失败则回滚。
  3. 失败重试:配置重试策略,spring.cloud.stream.bindings.output.producer.retry-template,网络抖动时自动重试。

Broker可靠性:

  1. 持久化:消息写入磁盘再返回ACK,避免Broker宕机丢失消息
  2. 主从同步:RocketMQ的同步双写,Kafka的acks=all,确保消息被多个副本确认
  3. 消息确认:消费者处理成功才发送ACK,避免消息丢失

消费端可靠性:

  1. 手动ACK:配置spring.cloud.stream.bindings.input.consumer.acknowledge-mode=manual,业务处理成功手动确认
  2. 死信队列:处理失败的消息进入DLQ,后续人工处理或重试
  3. 幂等性保证:消费端去重,比如用Redis记录已处理的消息ID,防止重复消费

消息积压处理:

  1. 临时扩容:增加消费者实例数,加快消费速度
  2. 批量消费:配置spring.cloud.stream.bindings.input.consumer.max-attempts,一次拉多条并行处理
  3. 降级策略:积压严重时,非核心业务丢弃或延迟处理,保障核心业务
  4. 异步处理:消费者接收到消息后,异步写入本地队列或数据库,快速ACK释放Broker压力

我之前某大促场景,消息积压几百万条,通过临时扩容消费者从10台到50台,配合批量消费,2小时内消化完毕。


【面试题】10. 微服务安全架构中,OAuth2和JWT各自扮演什么角色?如何实现Token的无感刷新? ​

【标准答案】 OAuth2和JWT的定位:

  • OAuth2:授权框架,解决"第三方应用如何获取用户资源访问权限"。核心是授权码、访问令牌、刷新令牌的交互流程。
  • JWT:令牌格式,自包含用户身份信息,无状态,服务端不存储Session。

组合使用场景: OAuth2负责授权流程,JWT作为Access Token的载体。用户登录后,授权服务器颁发JWT格式的Access Token,各微服务验证JWT签名就能获取用户身份,无需每次查库。

Token无感刷新实现:

方案一:双Token机制(Access Token + Refresh Token)

  • Access Token有效期短(如2小时),Refresh Token有效期长(如7天)
  • Access Token过期后,用Refresh Token换取新Token
  • 前端拦截401响应,静默调用刷新接口,刷新成功重发原请求

方案二:滑动过期

  • 每次请求都检查Token剩余有效期,若不足一定时间(如30分钟),主动返回新Token
  • 客户端收到响应后,检查Header中的新Token,替换本地存储

代码示例(Refresh Token方案):

java
@PostMapping("/refresh")
public TokenResponse refreshToken(@RequestParam String refreshToken) {
    // 验证refreshToken有效性
    if (!jwtUtil.validateRefreshToken(refreshToken)) {
        throw new InvalidTokenException("Invalid refresh token");
    }
    String userId = jwtUtil.extractUserId(refreshToken);
    // 检查是否被吊销(黑名单)
    if (tokenBlacklistService.isBlacklisted(refreshToken)) {
        throw new TokenRevokedException("Token revoked");
    }
    // 生成新access token
    String newAccessToken = jwtUtil.generateAccessToken(userId);
    // 可选:生成新refresh token,旧refresh token失效
    String newRefreshToken = jwtUtil.generateRefreshToken(userId);
    return new TokenResponse(newAccessToken, newRefreshToken);
}

安全加固要点:

  1. Refresh Token存储在HttpOnly Cookie,防止XSS攻击
  2. Refresh Token有使用次数限制或单次使用,用后即失效
  3. 用户修改密码或登出时,吊销所有Refresh Token
  4. JWT不存敏感信息,只放userId、roles等必要数据

我之前的项目采用双Token+滑动过期组合,前端无感知,用户体验好,同时Token安全可控。