Skip to content

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

【面试题】1. Eureka的自我保护机制是什么?触发条件和处理策略分别是什么?生产环境中该不该关闭? ​

【标准答案】Eureka自我保护机制是:当Eureka Server在一段时间内(默认15分钟)丢失超过85%的心跳时,Server会认为不是服务本身挂了,而是网络出了问题,于是进入自我保护模式——不再剔除过期服务。触发条件就是心跳续约比例低于85%。处理策略:进入保护后,Server仍能接受新服务注册和查询请求,但不会删除任何服务实例。生产环境建议开启自我保护(默认就是开的),原因很简单——微服务环境下网络抖动比服务宕机更常见,如果关闭保护,一次网络闪断就会导致大量服务被误剔除,引发"雪崩"。但也有例外:如果你的环境网络极其稳定,且需要快速感知服务下线(比如CDN边缘节点),可以考虑关闭。面试时加一句"我实际项目中保持开启,同时在服务端配合手动evict和优雅停机来弥补感知延迟",就很加分。

【面试题】2. Sentinel的熔断状态机是怎么流转的?三个状态之间切换的阈值分别是什么? ​

【标准答案】Sentinel熔断有三态:CLOSED(关闭)→ OPEN(打开)→ HALF_OPEN(半开)。CLOSED状态下请求正常放行,同时统计异常比例/慢调用比例。当指标超过阈值(比如异常比例>50%,或慢调用比例>50%,持续到最小请求数以上),熔断器切换到OPEN状态,所有请求直接被拒绝走降级逻辑。OPEN持续一个时间窗口(statIntervalMs,默认10s)后,进入HALF_OPEN,此时允许一个请求通过试探:如果成功,回到CLOSED;如果仍然失败,重新回到OPEN。关键参数:慢调用比例阈值、最大RT、异常比例阈值、异常数阈值、最小请求数、熔断时长。面试要点:Sentinel比Hystrix多了"慢调用比例"这一维度的熔断,不只是一个异常比例,而且支持热点参数限流,这是Hystrix做不到的。

【面试题】3. Seata的AT模式原理是什么?为什么说它对业务代码零侵入?两阶段提交的具体流程是怎样的? ​

【标准答案】AT模式是Seata最常用的模式,核心是"自动补偿"——对业务SQL自动解析并生成回滚日志。一阶段:业务SQL执行前,Seata拦截SQL,解析语义得到修改前的前镜像(before image),执行业务SQL,再获取修改后的后镜像(after image),生成回滚日志(undo log)写入undo_log表,然后提交本地事务。二阶段提交:TC通知各分支提交,直接异步清理undo_log即可(因为一阶段已经提交了)。二阶段回滚:TC通知回滚,读取undo_log,校验后镜像(脏写检查),如果没被别人改过,根据前镜像生成反向SQL补偿回去,然后删除undo_log。零侵入是因为:业务代码只需要加@GlobalTransactional注解,Seata的DataSource代理自动拦截SQL做前/后镜像记录,不需要手动写补偿逻辑。面试补充:AT模式的脏写检查机制(对比after image和当前数据)是它和TCC的关键区别——TCC靠业务自己保证,AT靠框架。

【面试题】4. Spring Cloud Gateway的过滤器链执行顺序是怎样的?GlobalFilter和GatewayFilter的区别和优先级如何? ​

【标准答案】Gateway的过滤器链基于WebFlux的过滤器模式,请求进来后按"前置→路由→后置"的顺序执行,响应回来逆序执行后置逻辑。执行顺序由Order值决定,Order越小越先执行。GlobalFilter是全局过滤器,对所有路由生效,常用做鉴权、限流、日志等;GatewayFilter是路由级别过滤器,只对配置了该filter的特定路由生效,比如AddRequestHeader、Retry等。优先级:两者混合排序,都按Order值排,没有"谁一定先谁后"的说法。面试关键点:自定义GlobalFilter实现GlobalFilter和Ordered接口即可;如果要在路由配置里用GatewayFilter,可以通过filter工厂或自定义GatewayFilterFactory。实际项目中鉴权用GlobalFilter(所有路由都要鉴权),特定路由的请求转发改造用GatewayFilter。另外,Gateway基于Netty+WebFlux异步非阻塞,吞吐量远超Zuul 1.x的Servlet同步模型。

【面试题】5. Redis分布式锁用SET NX EX实现有什么问题?Redisson是怎么解决这些问题的?RedLock算法又是什么? ​

【标准答案】SET NX EX的问题主要有三个:一、锁续期问题——如果业务执行时间超过过期时间,锁自动释放,其他线程拿到锁导致并发问题;二、锁误删——线程A的锁过期后,线程B获取锁,线程A执行完删除了B的锁;三、单点故障——Redis主节点宕机,锁数据可能丢失。Redisson的解决方案:一、看门狗机制(Watchdog),默认30秒过期,每10秒自动续期,业务没执行完就一直续;二、锁的value用唯一ID(UUID+线程ID),释放时用Lua脚本判断"是不是自己的锁"才删除,原子操作避免误删;三、可重入锁支持,用Hash结构记录重入次数。RedLock算法:在多个(通常5个)独立Redis实例上同时加锁,超过半数(N/2+1)加锁成功且总耗时没超过锁过期时间,才算加锁成功,解决单点故障问题。面试加分:RedLock有争议(Martin Kleppmann认为不安全,Salvatore反驳),实际生产中主从+Sentinel/Cluster的方案用得更多。

【面试题】6. Nacos作为配置中心,配置热更新的原理是什么?长轮询机制是怎么工作的? ​

【标准答案】Nacos配置热更新核心是长轮询机制。客户端启动时向Nacos Server发起长轮询请求(默认超时30s),带上当前配置的md5值。Server端收到请求后,比较客户端md5和Server端的md5:如果一致,挂起请求(使用ScheduledDeferredResult延迟29.5s返回空响应),期间如果配置变更了,立刻触发响应返回新配置;如果不一致,直接返回新配置。客户端收到响应后,如果是新配置就更新本地缓存并通知业务Bean刷新,然后立即发起新一轮长轮询。这样就实现了"配置变更秒级推送"。对比Spring Cloud Config原生方案:Config需要配合Bus+MQ才能实现推送,而Nacos自带长轮询,不需要额外组件。面试补充:Nacos 2.x引入了gRPC长连接模式替代长轮询,配置变更推送更实时(毫秒级),同时兼容1.x客户端。另外,@RefreshScope注解或@ConfigurationProperties都能触发Bean刷新,前者是销毁重建,后者是直接属性注入。

【面试题】7. OpenFeign的超时和重试机制怎么配置?如何避免重试导致的接口幂等性问题? ​

【标准答案】OpenFeign的超时分两层:连接超时(connectTimeout,默认10s)和读取超时(readTimeout,默认60s),通过feign.client.config.default.connectTimeout/readTimeout或指定服务名配置。重试方面,OpenFeign默认的Retryer是NEVER_RETRY(不重试),如果要开启,自定义Retryer即可,配置maxAttempts(最大重试次数,含首次)、period(重试间隔)、maxPeriod(最大重试间隔)。幂等性问题的根源:POST等非幂等请求重试可能导致重复操作(比如重复扣款)。解决方案:一、只对GET等幂等请求开启重试,POST不重试;二、在请求头加唯一请求ID(Request-Id),服务端做幂等校验;三、使用Seata等分布式事务保证最终一致性。实际配置中推荐:重试只配在GET请求或明确幂等的接口上,非幂等接口走补偿机制而非重试。面试加分:Spring Cloud LoadBalancer也有重试配置(spring.cloud.loadbalancer.retry),注意和Feign重试的叠加效应,避免双重重试导致请求放大。

【面试题】8. Sleuth+Zipkin链路追踪中,TraceId和SpanId是怎么传递的?跨线程场景如何保证TraceId不丢? ​

【标准答案】Sleuth通过在HTTP请求头中注入追踪标识来传递:TraceId放在X-B3-TraceId头,SpanId放在X-B3-SpanId头,父SpanId放在X-B3-ParentSpanId头,采样标识放在X-B3-Sampled头。微服务间调用时,这些Header自动由RestTemplate/Feign的拦截器透传到下游。跨线程场景是链路追踪的经典坑——主线程的TraceId在线程池中默认丢失,因为ThreadLocal不会自动传递。解决方案:一、Sleuth自带LazyTraceExecutor,包装线程池后自动传递追踪上下文;二、使用@NewSpan注解手动创建新Span并关联父Span;三、对于@Async方法,Sleuth自动处理上下文传递。面试关键:新版Spring Cloud(2020+)已弃用Sleuth,改用Micrometer Tracing + Zipkin/Jaeger导出器,API从brave换成了micrometer,但原理一样——都是上下文传播。异步场景的万能方案:始终用Sleuth/Micrometer提供的线程池包装器,不要手动new Thread。

【面试题】9. Spring Cloud Stream如何实现消息的可靠投递?如果消费者处理失败,怎么保证消息不丢? ​

【标准答案】Spring Cloud Stream的消息可靠性分三端保障:一、生产端:通过spring.cloud.stream.bindings.output.producer.required-groups确保消息至少被一个消费者组接收,配合事务消息(如RocketMQ的事务消息)保证本地事务和消息发送的原子性;二、Broker端:消息持久化+集群副本(Kafka的replica、RocketMQ的同步刷盘);三、消费端:手动ACK机制,spring.cloud.stream.bindings.input.consumer.auto-commit-offset=false,业务处理成功后手动发送Ack,失败则不Ack让Broker重投。消费失败的处理策略:一、重试——配置max-attempts和retry的backoff;二、死信队列(DLQ)——重试耗尽后消息进入DLQ,人工介入处理;三、降级——@ServiceActivator指定fallback方法兜底。面试关键:RocketMQ的顺序消息场景下,重试不能换线程消费,否则乱序,需要确保同一MessageKey落在同一Queue上。

【面试题】10. 微服务场景下JWT Token如何在服务间安全传递?Refresh Token机制怎么设计? ​

【标准答案】JWT在微服务间传递的标准方案:用户登录后网关签发JWT,后续请求在HTTP Header的Authorization字段携带Bearer Token。网关负责验签和基础鉴权,Token通过请求头透传到下游服务。服务间内部调用(Feign)通过RequestInterceptor把当前请求的Token透传到下游。问题:JWT一旦签发无法撤回,所以需要Refresh Token机制——Access Token短过期(如30分钟),Refresh Token长过期(如7天),Access Token过期后用Refresh Token刷新获取新的Access Token。设计要点:一、Refresh Token只在认证服务验证,不暴露给其他服务;二、Refresh Token存Redis,支持主动注销;三、Token刷新时做"旋转"(Rotation)——每次刷新生成新的Refresh Token,旧的作废,防止Refresh Token被盗用后持续有效;四、网关层检查Token黑名单(用户登出时加入黑名单)。面试加分:如果服务间调用需要更高安全性,可以用"双Token"方案——外部请求用JWT,内部服务间调用用mTLS双向认证,不暴露用户Token。