本轮 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实现鉴权:
@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模式的场景:
- 跨数据库厂商:undo_log依赖数据库方言,MySQL和Oracle混用时可能出问题。
- 高并发写场景:全局锁会阻塞并发事务,秒杀、抢红包这类高并发场景,AT模式性能扛不住,建议用TCC或可靠消息。
- 复杂SQL:存储过程、触发器、多表关联更新,Seata解析不出来,无法生成正确的undo_log。
- 非关系型数据库:AT模式依赖关系型数据库的事务机制,Redis、MongoDB不支持。
- 对性能要求极高:AT模式有额外的SQL解析和undo_log写入开销,TPS要求过万的场景要评估性能影响。
我之前在某支付系统,账户核心交易用TCC模式,保障强一致性和高并发;非核心业务比如积分发放用AT模式,开发效率高。
【面试题】5. OpenFeign的负载均衡是如何实现的?如何自定义负载均衡策略?
【标准答案】 OpenFeign本身不做负载均衡,它通过集成Spring Cloud LoadBalancer或Ribbon实现。
工作机制:Feign发起请求时,会把服务名解析成具体实例。这个过程委托给LoadBalancerClient,LoadBalancerClient调用负载均衡策略选择一个实例,然后Feign向该实例发起HTTP请求。
默认策略:Spring Cloud LoadBalancer默认轮询(RoundRobin),依次选择实例。Ribbon默认也是轮询。
自定义策略方式:
方式一:配置类方式
@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)指定服务使用这个配置。
方式二:配置文件方式
spring:
cloud:
loadbalancer:
ribbon:
enabled: false # 禁用Ribbon,使用Spring Cloud LoadBalancer方式三:权重策略 如果有实例配置了权重元数据,可以实现加权随机或加权轮询。比如某个实例配置instance-weight: 3,它被选中的概率就是其他实例的3倍。
实际项目中,我建议根据业务特点选择:无状态服务用轮询就行,有状态服务或硬件配置差异大的,用权重策略合理分配流量。
【面试题】6. 微服务架构下如何实现配置的热更新?Nacos Config的原理是什么?
【标准答案】 配置热更新的核心是"配置中心推送+客户端动态刷新":
Nacos Config原理:
- 客户端长轮询:微服务启动时向Nacos发起长轮询请求,默认30秒超时。这期间如果配置有变更,Nacos立即返回;没变更则30秒后返回,客户端再发起新请求。
- 配置缓存:客户端本地缓存配置,同时Nacos集群也缓存,即使Nacos短暂宕机,服务仍能读取本地缓存继续运行。
- 动态刷新:配置变更推送后,Nacos客户端触发
RefreshEvent,Spring Cloud的@RefreshScope注解的Bean会重新创建,注入新配置值。
实现热更新两种方式:
方式一:@RefreshScope注解
@RestController
@RefreshScope
public class ConfigController {
@Value("${app.config.value}")
private String configValue;
@GetMapping("/config")
public String getConfig() {
return configValue;
}
}配置更新后,这个Controller的Bean会重建,configValue自动刷新。
方式二:@NacosValue自动刷新
@NacosValue(value = "${app.config.value}", autoRefreshed = true)
private String configValue;生产注意事项:
- 配置回滚:Nacos支持配置历史版本,出问题一键回滚,建议每次变更都写备注。
- 灰度发布:Nacos支持按实例IP或标签灰度推送配置,先在1台实例验证,再全量发布。
- 配置加密:敏感配置用Nacos的配置加密功能,Jasypt或KMS方案,明文配置是安全隐患。
- 多环境隔离:用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脚本保证原子性:
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的优势:
- 封装了上面所有坑,开箱即用
- 提供可重入锁、读写锁、公平锁等多种类型
- WatchDog自动续期,业务无感知
- 分布式锁+条件变量(Condition)支持复杂并发场景
生产建议:单机场景用Redisson普通锁即可;对可用性要求极高,用RedLock或迁移到Zookeeper分布式锁。
【面试题】8. 链路追踪Sleuth+Zipkin的核心原理是什么?TraceId如何跨服务传递?
【标准答案】 链路追踪核心概念是Trace和Span:
- Trace:一次完整请求的轨迹,贯穿所有服务,全局唯一的TraceId标识
- Span:单个服务的处理过程,有自己的SpanId,记录开始时间、结束时间、异常信息
TraceId传递机制:
- 请求入口:网关或第一个服务生成TraceId,放入HTTP Header
X-B3-TraceId - 跨服务调用:OpenFeign/RestTemplate拦截器自动读取Header中的TraceId,透传到下游服务
- 异步场景:线程池或消息队列场景,Sleuth通过
TracingExecutorService包装线程池,或消息Header传递,保证TraceId不丢失
Sleuth核心原理:
- 通过
Brave库实现,对常用组件做无侵入式埋点 - 自动拦截HTTP请求、RPC调用、消息队列,生成Span并上报
- 采样策略可配置,比如采样率10%,降低存储压力
Zipkin架构:
- Collector:接收各服务上报的Span数据
- Storage:存储数据,支持MySQL、Elasticsearch、Cassandra等
- API:查询接口
- UI:可视化展示调用链、耗时分析
生产优化建议:
- 采样策略:高并发场景设为1%~10%,问题排查时临时调高
- 异步上报:Span数据用异步方式发送,避免阻塞业务线程
- 存储选型:数据量小用MySQL,量大用Elasticsearch,支持复杂查询和高吞吐
- 敏感信息:Span中的请求参数、响应体可能含敏感数据,配置脱敏规则
我之前用SkyWalking做链路追踪,相比Zipkin,SkyWalking是字节码增强,无需改动代码,还支持服务拓扑图、告警功能,更全面。
【面试题】9. Spring Cloud Stream如何保证消息的可靠性投递?消息积压怎么处理?
【标准答案】 消息可靠性涉及三个环节:生产端、Broker、消费端。
生产端可靠性:
- 消息确认机制:Spring Cloud Stream通过
@EnableBinding和@StreamListener处理消息,生产者配置spring.cloud.stream.bindings.output.producer.required-groups确保消息持久化。 - 事务消息:RocketMQ支持事务消息,本地事务和消息发送原子性保证。先发半消息,本地事务成功再提交消息,失败则回滚。
- 失败重试:配置重试策略,
spring.cloud.stream.bindings.output.producer.retry-template,网络抖动时自动重试。
Broker可靠性:
- 持久化:消息写入磁盘再返回ACK,避免Broker宕机丢失消息
- 主从同步:RocketMQ的同步双写,Kafka的
acks=all,确保消息被多个副本确认 - 消息确认:消费者处理成功才发送ACK,避免消息丢失
消费端可靠性:
- 手动ACK:配置
spring.cloud.stream.bindings.input.consumer.acknowledge-mode=manual,业务处理成功手动确认 - 死信队列:处理失败的消息进入DLQ,后续人工处理或重试
- 幂等性保证:消费端去重,比如用Redis记录已处理的消息ID,防止重复消费
消息积压处理:
- 临时扩容:增加消费者实例数,加快消费速度
- 批量消费:配置
spring.cloud.stream.bindings.input.consumer.max-attempts,一次拉多条并行处理 - 降级策略:积压严重时,非核心业务丢弃或延迟处理,保障核心业务
- 异步处理:消费者接收到消息后,异步写入本地队列或数据库,快速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方案):
@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);
}安全加固要点:
- Refresh Token存储在HttpOnly Cookie,防止XSS攻击
- Refresh Token有使用次数限制或单次使用,用后即失效
- 用户修改密码或登出时,吊销所有Refresh Token
- JWT不存敏感信息,只放userId、roles等必要数据
我之前的项目采用双Token+滑动过期组合,前端无感知,用户体验好,同时Token安全可控。