本轮 SpringCloud微服务架构面试题库(带答案)
生成时间:2026-05-22 22:26 | 共10题
【面试题】1. Eureka的自我保护机制是什么?什么场景下会触发?生产环境建议开启还是关闭?
【标准答案】Eureka自我保护机制是:当Eureka Server在短时间内丢失超过一定比例的心跳(默认85%),会认为不是服务本身挂了,而是网络出了问题,于是不再剔除过期服务实例。
触发条件:Eureka Server每分钟期望收到的心跳数 × 0.85 > 实际收到的心跳数,持续一段时间后触发。
核心原理:Eureka遵循AP模型,宁可保留可能过期的数据,也不随意删除,因为网络抖动比服务宕机更常见。进入自我保护后,Server不再从注册表中移除任何实例,即使心跳超时也不踢。
生产环境建议:一般建议开启。因为生产环境网络抖动很常见,如果关闭自我保护,一旦网络波动,大量服务会被误判下线,导致流量全部涌向存活服务,引发雪崩。但如果你的环境网络非常稳定,且对服务状态的实时性要求极高(比如金融交易场景),可以考虑关闭,配合健康检查做精准摘除。
【面试题】2. Ribbon和Spring Cloud LoadBalancer的核心区别是什么?为什么Spring Cloud要替换Ribbon?
【标准答案】核心区别有三点:
技术栈不同:Ribbon基于Netflix开源,依赖较多,维护已停止(进入维护模式);Spring Cloud LoadBalancer是Spring官方出品,轻量级,只依赖spring-cloud-commons。
缓存机制不同:Ribbon有独立的ServerList缓存,默认30秒刷新一次,服务上下线感知有延迟;LoadBalancer直接基于ServiceInstanceListSupplier获取实例,默认35秒缓存,但可以自定义更灵活的刷新策略。
扩展方式不同:Ribbon通过ILoadBalancer接口+IRule策略扩展,配置项多且分散(ribbon.xxx);LoadBalancer通过Reactive编程模型+ServiceInstanceListSupplier扩展,配置简洁,和Spring Cloud Gateway天然集成。
替换原因:Ribbon停更,社区不再修复Bug和安全漏洞;Ribbon的缓存和重试逻辑复杂,容易踩坑;Spring Cloud要统一技术栈走向Reactive响应式,LoadBalancer天生支持WebFlux。
迁移成本很低——大部分场景只需把ribbon.xxx配置换成spring.cloud.loadbalancer.xxx,自定义策略实现ReactorServiceInstanceLoadBalancer接口即可。
【面试题】3. OpenFeign的超时重试配置怎么设?有什么踩坑点?
【标准答案】OpenFeign超时分两层:连接超时(connectTimeout)和读取超时(readTimeout)。
配置方式:
feign:
client:
config:
default: # 全局默认
connectTimeout: 5000 # 连接超时5秒
readTimeout: 10000 # 读取超时10秒
order-service: # 针对特定服务
connectTimeout: 3000
readTimeout: 5000踩坑点:
Ribbon和Feign超时谁生效? 如果同时配了Ribbon超时,实际生效的是两者中更小的那个。LoadBalancer模式下只有Feign超时生效。
重试默认关闭:Feign默认不重试。要开启需配置Retryer,但要注意和Ribbon重试叠加——如果两者都开了重试,最坏情况下重试次数会相乘。建议只在一层开重试。
读超时不能设太长:读超时过长会导致线程长时间占用,高并发下拖垮连接池。一般读超时设为P99响应时间的2-3倍。
非幂等接口不要重试:POST等非幂等请求重试可能导致数据重复提交。可以配合
feign.httpclient.disableSslValidation和自定义重试策略,对POST请求跳过重试。超时和熔断联动:如果Sentinel/Hystrix的超时比Feign短,熔断会先触发,Feign超时配置形同虚设。确保熔断超时 > Feign超时。
【面试题】4. Sentinel的熔断状态机是怎么运转的?三个状态之间的转换条件是什么?
【标准答案】Sentinel熔断状态机有三个状态:CLOSED(关闭)→ OPEN(打开)→ HALF-OPEN(半开)。
CLOSED(关闭状态):正常放行所有请求。Sentinel持续统计指标(慢调用比例、异常比例、异常数),当指标超过阈值,切换到OPEN。
OPEN(打开状态):所有请求直接拒绝,走降级逻辑。维持一段时间(熔断时长,默认10秒),时间到后自动切换到HALF-OPEN。
HALF-OPEN(半开状态):允许少量请求(默认1个)通过,探测下游是否恢复:
- 如果探测请求成功 → 切回CLOSED,恢复正常流量
- 如果探测请求仍然失败 → 切回OPEN,继续熔断
触发熔断的三种策略:
- 慢调用比例:响应时间超过最大RT的请求算慢调用,慢调用比例超过阈值且请求量 ≥ 最小请求数时触发
- 异常比例:异常数/总请求数 > 阈值,且请求量 ≥ 最小请求数时触发
- 异常数:单位时间内异常数 > 阈值时触发
面试加分点:注意"最小请求数"这个条件——如果10秒内只有2个请求全失败了,异常比例100%,但没到最小请求数,不会熔断。这是防止低流量误触发的保护机制。
【面试题】5. Spring Cloud Gateway的过滤器链执行顺序怎么控制?GlobalFilter和GatewayFilter有什么区别?
【标准答案】过滤器执行顺序由getOrder()返回值决定,数值越小优先级越高(和Spring的Ordered机制一致)。
GlobalFilter vs GatewayFilter:
| 维度 | GlobalFilter | GatewayFilter |
|---|---|---|
| 作用范围 | 所有路由 | 特定路由 |
| 配置方式 | 实现GlobalFilter+Ordered接口,自动生效 | 在路由定义中配置(yaml/Java API) |
| 典型场景 | 鉴权、日志、限流、跨域 | 请求参数转换、路径重写、特定路由的降级 |
执行流程:请求进来 → Pre阶段(按order从小到大执行所有filter的pre逻辑) → 转发到下游 → Post阶段(按order从大到小执行所有filter的post逻辑)。
踩坑点:
Pre和Post顺序相反:Pre阶段order=1的先执行,Post阶段order=1的后执行。这符合栈的LIFO特性——最先进去的最后出来。
filter链中断:在Pre阶段调用
exchange.getResponse().setComplete()可以直接返回响应,后续filter不再执行。常用于鉴权失败直接拒绝。自定义order值建议:官方内置filter的order一般是0、1、1000等,自定义filter建议用-1到100之间的值,确保在关键位置插入。不要用Integer.MIN_VALUE这种极端值,容易和未来版本冲突。
【面试题】6. Nacos Config配置热更新的原理是什么?和Spring Cloud Config的热更新有什么区别?
【标准答案】Nacos Config热更新原理:
长轮询机制:客户端启动时向Nacos Server发起长轮询请求(默认超时30秒),Server端hold住连接。当配置变更时,Server立即响应返回新配置;如果30秒内没变更,返回304,客户端重新发起长轮询。
配置变更监听:客户端收到新配置后,通过
Listener回调触发Spring Environment刷新,@RefreshScope标注的Bean会被销毁重建,@Value注解的字段会重新注入。DataId+Group定位:配置通过DataId(一般是服务名+后缀)和Group唯一定位,支持多环境隔离(namespace)。
和Spring Cloud Config的区别:
| 维度 | Nacos Config | Spring Cloud Config |
|---|---|---|
| 配置存储 | 内置数据库(Derby/MySQL) | Git/SVN/Vault |
| 热更新方式 | 长轮询,秒级感知 | 依赖Spring Cloud Bus+MQ广播,或手动发/actuator/refresh |
| 运维复杂度 | 开箱即用,自带管理界面 | 需要额外部署Bus、MQ、Git仓库 |
| 一致性模型 | CP(Raft协议选举Leader) | 依赖Git的一致性 |
| 配置回滚 | 支持历史版本一键回滚 | 依赖Git回滚 |
| 灰度发布 | 原生支持Beta发布 | 不支持,需自研 |
面试关键点:Nacos的配置变更是推拉结合——长轮询本质是"长连接的拉",不是纯粹的推模式,但效果等同于推送。这样既保证实时性,又不需要维护WebSocket等长连接的高开销。
【面试题】7. Seata的AT模式两阶段提交的具体过程是什么?和TCC模式相比各有什么优劣?
【标准答案】AT模式(自动事务):
一阶段(业务+回滚日志):
- 拦截业务SQL,解析SQL语义
- 查询修改前的数据,生成前镜像(beforeImage)
- 执行业务SQL
- 查询修改后的数据,生成后镜像(afterImage)
- 将前后镜像组织成undo_log记录,和业务SQL在同一个本地事务中提交
- 向TC(事务协调者)注册分支事务,上报分支状态
二阶段-提交:
- TC通知各分支提交 → 异步清理undo_log即可(因为一阶段已经提交了本地事务)
二阶段-回滚:
- TC通知各分支回滚 → 读取undo_log → 校验后镜像(脏写检查)→ 生成反向SQL执行回滚 → 删除undo_log
TCC模式(手动事务):
- Try:预留资源(冻结金额等)
- Confirm:确认提交(扣减冻结金额)
- Cancel:取消回滚(解冻金额)
AT vs TCC:
| 维度 | AT | TCC |
|---|---|---|
| 侵入性 | 零侵入,只需加@GlobalTransactional | 高侵入,需写Try/Confirm/Cancel三段逻辑 |
| 性能 | 一阶段直接提交本地事务,锁持有时间短 | 一阶段资源锁定,持有到二阶段 |
| 一致性 | 依赖undo_log回滚,存在脏写风险 | 业务自行保证一致性,更可靠 |
| 适用场景 | 大多数常规业务 | 资金类、库存扣减等强一致性场景 |
面试加分:AT模式的脏写检查——回滚时对比afterImage和当前数据,如果一致说明没被其他人修改过,安全回滚;如果不一致说明被脏写了,需要人工介入。这是AT模式最大的风险点,也是TCC在金融场景更受青睐的原因。
【面试题】8. Redis分布式锁用Redisson实现时,看门狗机制是怎么工作的?为什么不建议自己用SET NX EX实现?
【标准答案】Redisson看门狗机制:
- 加锁时如果不指定leaseTime,默认锁30秒(lockWatchdogTimeout=30秒)
- 加锁成功后,Redisson启动一个后台线程(看门狗),每隔lockWatchdogTimeout/3(即10秒)检查一次锁是否还被持有
- 如果持有锁的客户端仍存活且锁key还在,看门狗自动续期到30秒
- 客户端宕机后,看门狗线程随之消亡,锁30秒后自动过期释放
为什么不建议自研SET NX EX:
续期问题:自研锁设置了过期时间,如果业务执行超过过期时间,锁自动释放,其他线程拿到锁,导致并发安全问题。Redisson的看门狗自动续期解决了这个问题。
释放安全:自研锁直接DEL释放,可能误删别人的锁。标准做法是LUA脚本判断value再删,但手写LUA容易出错。Redisson内部用LUA脚本保证了原子性。
可重入:自研锁不支持同一线程重入。Redisson用Hash结构存储(key → field:threadId → value:重入次数),天然支持可重入。
集群高可用:自研锁在主从切换时可能丢失锁。Redisson提供RedLock算法(多节点加锁,过半成功才算获取锁),虽然有争议,但至少提供了选项。
面试一句话:自研SET NX EX能解决80%的场景,但续期、可重入、安全释放这三道坎,每道都容易踩坑。Redisson帮你全部处理好了,生产环境没理由重复造轮子。
【面试题】9. Spring Cloud Stream如何保证消息的可靠性投递?消息积压了怎么处理?
【标准答案】可靠性投递保障:
生产端确认:配置
spring.cloud.stream.bindings.output.producer.required-groups确保消息至少被一个消费者组接收。配合RabbitMQ的publisher confirms或Kafka的acks=all,确保消息到达Broker。消费端手动ACK:配置
spring.cloud.stream.bindings.input.consumer.acknowledge-mode=manual,业务处理完成后手动调用Acknowledgment.acknowledge()。如果处理失败不ACK,消息会重新投递。死信队列:消费重试达到最大次数后,消息进入死信队列(DLQ),配置
enableDlq: true。死信队列的消息可以人工审查后重新投递或补偿。分区有序:配置partitionKeyExpression确保同一业务ID的消息路由到同一分区,保证局部有序。
消息积压处理:
- 紧急扩容:增加消费者实例数(注意不能超过分区数,多了也是闲置)
- 临时降级:非核心业务的消息暂停消费,腾出资源给核心业务
- 批量消费:将消费模式从逐条处理改为批量处理,提高吞吐
- 跳过+补偿:积压太久的数据如果已无实时价值,先跳过消费最新数据,历史数据走离线批处理补偿
- 根因排查:消费端有没有慢查询?有没有外部API超时?下游服务有没有瓶颈?
面试加分:Kafka分区数是消费并行度的上限。如果你有10个分区但只有3个消费者,最多只能3路并行。要扩容消费者先得扩分区数,但Kafka分区只能增不能减。
【面试题】10. 微服务之间调用如何安全地传递Token?网关鉴权和服务间内部调用怎么设计?
【标准答案】Token传递机制:
网关统一鉴权:外部请求到网关,Gateway的GlobalFilter校验JWT,验签通过后将用户信息(userId、roles等)放入HTTP Header(如
X-User-Id、X-User-Roles)转发给下游服务。内部服务间调用:OpenFeign通过RequestInterceptor拦截器,将上游请求中的Header透传到下游。核心代码:
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attrs = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
if (attrs != null) {
HttpServletRequest request = attrs.getRequest();
template.header("X-User-Id", request.getHeader("X-User-Id"));
template.header("X-User-Roles", request.getHeader("X-User-Roles"));
}
}
}- 内部信任边界:服务间调用不再校验JWT,而是信任网关传过来的Header。服务内部通过拦截器从Header提取用户信息放入ThreadLocal或SecurityContext。
安全设计要点:
网关是唯一入口:内部服务不对外暴露端口,所有外部流量必须经过网关。K8s中可用NetworkPolicy限制只有Gateway能访问内部Service。
防伪造:内部服务间信任Header的前提是这些Header只能由网关设置。可以在网关加一个内部签名Header(如
X-Gateway-Signature),下游服务验证签名,防止伪造。服务间内部调用:如果是定时任务或系统间调用(无用户上下文),使用Service Account Token,走OAuth2 Client Credentials模式获取专用Token。
Token刷新:网关处处理Refresh Token逻辑,下游服务无感知。JWT快过期时,网关自动用Refresh Token换新Access Token,对下游透明。
面试关键:核心原则是"边界鉴权,内部信任"——在系统边界(网关)做严格鉴权,内部服务间减少重复校验,靠网络隔离和签名信任来保证安全。