Skip to content

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

【面试题】1. Nacos既支持AP模式又支持CP模式,请说明两者的适用场景以及Nacos是如何实现模式切换的? ​

【标准答案】Nacos中AP模式适用于临时实例(ephemeral=true),采用Distro协议,类似Gossip协议做数据同步,强调可用性,适合服务注册发现场景——服务上下线频繁,短暂不一致可以接受。CP模式适用于持久实例(ephemeral=false),采用Raft协议做一致性同步,强调数据强一致,适合配置中心场景——配置数据不能丢失不能不一致。切换方式是在实例注册时通过ephemeral字段决定,临时实例走AP,持久实例走CP,不需要手动切换模式,Nacos 2.x后默认临时实例走AP。面试时强调一点:注册中心用AP是行业共识,因为网络分区时CP会拒绝注册,反而导致服务不可用;而配置中心必须CP,配置不一致比短暂不可用更危险。

【面试题】2. Sentinel的滑动窗口统计和LeapArray数据结构是如何工作的?相比固定窗口和漏桶算法有什么优势? ​

【标准答案】Sentinel的滑动窗口核心是LeapArray,它把一个时间周期(默认1秒)分成多个样本窗口(默认500ms一个,共2个),用环形数组存储。每个样本窗口是一个WindowWrap对象,里面维护了一个MetricBucket统计QPS、RT、阻塞数等。当请求进来,先根据当前时间戳定位到哪个WindowWrap,然后在对应bucket里累加计数。统计时取当前窗口+前面所有有效窗口的数据求和,天然实现滑动窗口效果。优势对比:固定窗口有临界突刺问题(窗口边界两倍流量),滑动窗口解决了这个问题;相比漏桶的匀速输出,滑动窗口统计更灵活,允许一定程度的突发流量。Sentinel还用了双滑动窗口(秒级+分钟级)来支持更精细的限流判断。面试重点说:LeapArray是时间换空间的设计,环形数组复用WindowWrap对象,避免GC压力。

【面试题】3. Spring Cloud Gateway的过滤器链执行顺序是怎样的?如何实现一个全局鉴权过滤器并确保它在路由过滤器之前执行? ​

【标准答案】Gateway过滤器分三类:GlobalFilter(全局)、GatewayFilter(路由级别)、前置/后置由filter链顺序决定。执行顺序通过Ordered接口的getOrder()值控制,值越小越先执行pre阶段。执行流程是:请求进入 → 按order值从小到大执行所有filter的pre逻辑 → 路由转发 → 按order值从大到小执行post逻辑(类似洋葱模型)。实现全局鉴权过滤器:实现GlobalFilter和Ordered接口,在filter方法里校验Token,无效则设置response状态码并return chain.filter(exchange).then()的Mono.empty()短路返回;有效则继续chain.filter(exchange)。关键点:order值设为-1或更小,确保在路由过滤器(NettyRoutingFilter的order是Integer.MAX_VALUE)之前执行。实际项目中一般用-1到-10之间,比ReactiveLoadBalancerClientFilter(order=10150)早执行即可。

【面试题】4. Seata的AT模式在第二阶段回滚时,是如何保证undo_log不被误删或脏写的?遇到全局锁冲突怎么处理? ​

【标准答案】AT模式的一阶段在业务SQL执行前后保存before-image和after-image到undo_log表,同时获取全局锁(向TC注册分支锁)。二阶段回滚时:先校验after-image和当前数据库数据是否一致,一致则用before-image反向补偿并删除undo_log;不一致说明发生了脏写,需要人工介入。全局锁冲突处理:一阶段提交本地事务前,如果某行数据的全局锁被其他全局事务持有,当前事务会自旋重试获取锁(默认重试30次,间隔10ms),超时则回滚本地事务并抛异常。这保证了全局锁的互斥性。面试加分点:undo_log表和业务表在同一个数据库,保证本地事务的原子性;Seata 1.5+引入了防悬挂机制,通过branch_table记录避免Rollback比Register先到达导致的悬挂问题。

【面试题】5. Redis分布式锁使用SET NX EX有什么风险?Redisson是如何解决锁续期和锁释放安全问题的? ​

【标准答案】SET NX EX的核心风险:①锁过期但业务未完成(锁续期问题);②客户端A的锁过期后客户端B获取锁,A执行完误删了B的锁(锁释放安全问题);③Redis主从切换导致锁丢失。Redisson的解决方案:①锁续期——看门狗机制(Watchdog),默认锁30秒,每10秒(1/3租约时间)检查持有锁的线程是否存活,存活则续期到30秒,避免了锁过期但业务还在跑的问题;②锁释放安全——每个客户端生成唯一UUID作为锁value,释放时用Lua脚本判断value一致才删除,保证不会误删别人的锁;③可重入——用Hash结构存储,field是线程ID+UUID,value是重入次数,每次加锁value+1,解锁-1,到0才真正释放。面试补充:RedLock解决多节点问题,但Martin Kleppmann批评过它,生产环境更推荐Redisson的联锁(MultiLock)或用ZooKeeper做强一致锁。

【面试题】6. OpenFeign的超时和重试配置与Ribbon/LoadBalancer的超时重试是什么关系?如何避免请求被重复执行? ​

【标准答案】OpenFeign本身没有重试机制(默认不重试),它的超时分ConnectTimeout和ReadTimeout。Feign在底层使用Ribbon/LoadBalancer做负载均衡,所以实际生效的超时和重试取决于两者谁更小。关键关系:Feign的ConnectTimeout和ReadTimeout会覆盖Ribbon的ConnectTimeout和ReadTimeout;Ribbon的重试配置(MaxAutoRetries、MaxAutoRetriesNextServer)仍然生效。避免重复执行的原则:①非幂等操作(POST等)不要开启重试,设置ribbon.MaxAutoRetries=0;②幂等操作(GET)可以开启重试,但控制重试次数不超过2;③最安全的方式是关闭Ribbon重试(MaxAutoRetries=0, MaxAutoRetriesNextServer=0),在业务层用Spring Retry对指定异常做重试,配合@Retryable注解精确控制。面试强调:生产环境建议关闭底层重试,用业务层重试替代,因为底层重试对非幂等接口非常危险。

【面试题】7. Spring Cloud Sleuth如何实现TraceId和SpanId在微服务调用链中的自动传递?异步线程场景下会丢失吗? ​

【标准答案】Sleuth通过Filter和Interceptor自动注入和提取Trace信息:HTTP调用时,在请求头中注入traceId(X-B3-TraceId)、spanId(X-B3-SpanId)、parentSpanId(X-B3-ParentSpanId),下游服务从请求头中解析出来继续传递。对于OpenFeign调用,Sleuth注册了FeignInterceptor自动注入header;对于RestTemplate,通过LazyTracingContextPropagator实现。异步线程场景确实会丢失,因为TraceId存在ThreadLocal中,子线程无法继承。解决方案:①使用Sleuth提供的@NewSpan注解标记新Span;②手动用Tracer继续Span——Span span = tracer.nextSpan().name("xxx").start();③用Sleuth提供的装饰器包装Runnable/Callable——tracingRunnable = springContextUtil.getBean(Tracing.class).currentTraceContext().wrap(runnable);④线程池用LazyTraceExecutor包装。面试要点:Sleuth 3.x+已迁移到Micrometer Tracing,底层用Brave或OTel做传播,原理一致但API变了。

【面试题】8. 配置中心Nacos Config实现配置热更新的原理是什么?@RefreshScope和@NacosValue哪个更适合生产环境? ​

【标准答案】Nacos Config热更新原理:客户端通过长轮询(Long Polling)监听配置变化。Nacos 2.x改用gRPC长连接替代长轮询,服务端配置变更时主动推送给客户端。客户端收到变更通知后,拉取最新配置值,发布RefreshEvent事件,Spring Cloud的ContextRefresher处理该事件,销毁并重建@RefreshScope标注的Bean,新Bean会读取最新配置值。@RefreshScope vs @NacosValue:@RefreshScope是Spring Cloud标准方案,整个Bean销毁重建,适合配置项多且集中管理的场景,缺点是Bean重建有短暂不可用风险且可能导致Bean依赖问题;@NacosValue是Nacos扩展注解,只更新标注的字段值,不影响Bean生命周期,更轻量。生产建议:大部分场景用@NacosValue或@Value+@RefreshScope的配合;如果配置项不多,@NacosValue更安全;如果Bean本身就是配置聚合类(如ConfigurationProperties),用@RefreshScope更统一。关键是理解:热更新的本质是事件驱动+Bean刷新,不是真的魔法。

【面试题】9. 在Kubernetes环境下部署SpringCloud微服务,服务发现用Nacos还是K8s Service更合适?两者的优缺点和混合方案是什么? ​

【标准答案】Nacos服务发现:优点是和SpringCloud生态无缝集成,支持权重路由、元数据、命名空间隔离,控制台可视化管理;缺点是Nacos自身需要运维,K8s内外网络打通有复杂度(Pod IP vs Service IP)。K8s Service发现:优点是零运维(K8s原生),自动健康检查和负载均衡,Service域名可直接解析;缺点是负载均衡策略有限(只有轮询和SessionAffinity),不支持权重路由和灰度发布,无法做服务级别的元数据管理。混合方案:①Spring Cloud Kubernetes——直接用K8s Service做发现,适合简单场景;②Nacos部署在K8s内,服务注册Pod IP,通过headless Service暴露,Nacos管理流量治理,K8s管理基础设施调度;③双注册——服务同时注册到Nacos和K8s Service,Nacos做应用层治理,K8s做基础设施层兜底。生产推荐方案②,Nacos在K8s内做服务治理,用Ingress/Gateway暴露对外流量,内部用Nacos做发现和配置,享受K8s的弹性伸缩和Nacos的流量治理能力。

【面试题】10. 微服务架构下如何实现灰度发布?请对比Gateway层路由灰度、Ribbon自定义负载均衡灰度、以及Istio Service Mesh灰度三种方案的优缺点。 ​

【标准答案】①Gateway层路由灰度:在Gateway通过请求头(如gray-tag=true)+自定义Predicate/Filter,将灰度流量路由到灰度版本服务实例。优点是集中管控、实现简单,适合入口层面的灰度;缺点是只能控制入口流量,服务间内部调用无法灰度,需要在每个调用链路手动传递灰度标识。②Ribbon/LoadBalancer自定义负载均衡灰度:在负载均衡层根据元数据(如version=gray)过滤实例,灰度请求只路由到灰度实例。优点是可以控制服务间调用的灰度,实现链路级灰度;缺点是需要通过Header或ThreadLocal传递灰度标识,每个服务都要实现,开发成本高,且跨线程容易丢失标识。③Istio Service Mesh灰度:通过VirtualService和DestinationRule配置流量比例(如v1:v2=90:10),基于权重或Header匹配路由,Sidecar代理自动完成流量分配。优点是业务代码零侵入,支持全链路灰度,可视化配置;缺点是引入Sidecar增加延迟(约2-5ms),运维复杂度高,学习曲线陡峭。生产建议:中小团队用方案①+②组合,Gateway做入口灰度+LoadBalancer做链路灰度;大团队/云原生成熟团队用方案③,享受零侵入的全链路流量治理。核心思路:灰度发布的本质是流量路由,方案选择取决于团队规模和技术成熟度。