Skip to content

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

【面试题】1. Nacos作为注册中心,它的临时实例和持久实例有什么区别?在实际项目中如何选择? ​

【标准答案】这是Nacos区别于Eureka的核心设计点。Nacos支持两种实例类型:临时实例(ephemeral)和持久实例(persistent)。临时实例采用客户端心跳检测模式,实例自己向Nacos发送心跳,如果心跳停止,Nacos直接从列表中剔除——这是AP模式,适合微服务场景,服务实例随时可能上下线。持久实例采用服务端主动健康检查模式,Nacos主动探测实例是否存活,即使实例下线也不会立刻移除,而是标记为不健康——这是CP模式,适合数据库、缓存等基础设务服务,这类服务相对稳定,需要强一致性。实际项目中的选择:微服务节点用临时实例(弹性伸缩、故障自动摘除),MySQL、Redis这类基础设施用持久实例(保证注册信息的强一致性)。配置方式很简单,在注册时设置ephemeral=true/false即可,也可以通过spring.cloud.nacos.discovery.ephemeral配置。

【面试题】2. Sentinel的滑动窗口统计和漏桶限流算法有什么本质区别?分别适合什么场景? ​

【标准答案】这两个是不同层面的东西。滑动窗口是Sentinel用来做指标统计的底层结构,不是限流算法。它把时间窗口分成多个格子(比如1秒分5个格子),每个格子独立计数,随时间滑动丢弃过期格子,这样可以精确统计任意时刻的QPS,避免固定窗口的边界突发问题。Sentinel的滑动窗口用LeapArray实现,支持两种:可滑动重置的滑动窗口和不可滑动重置的滚动窗口。漏桶算法才是限流策略,请求像水一样倒入漏桶,桶以固定速率漏水,多余请求直接拒绝或排队。它强行平滑流量,输出速率恒定。两者的适用场景:滑动窗口统计本身用于指标采集,是所有限流/熔断判断的基础数据来源;漏桶适合对下游有严格速率要求的场景,比如调用第三方API有QPS限制、往数据库写入有吞吐上限。注意Sentinel默认限流用的是滑动窗口统计+阈值判断(本质是计数器限流),不是漏桶。如果需要平滑限流,要配置RateLimiterController(匀速排队),那个才是漏桶思想。

【面试题】3. Spring Cloud Gateway的过滤器链执行顺序怎么控制?GlobalFilter和GatewayFilter有什么区别?如何实现鉴权拦截? ​

【标准答案】Gateway的过滤器分两类:GatewayFilter是路由级别的,只在特定路由上生效,配置在route定义里;GlobalFilter是全局的,所有路由都经过。两者都实现相同的接口逻辑,最终会被合并成一个过滤器链按order排序执行。顺序控制:通过实现Ordered接口或用@Order注解设置order值,值越小越先执行。pre阶段按order从小到大,post阶段按order从大到小——典型的洋葱模型。鉴权拦截实现:写一个GlobalFilter,order设小一点确保先执行,在filter方法里获取请求头中的Token,校验JWT的签名和过期时间,合法则chain.filter(exchange)放行,不合法则exchange.getResponse().setComplete()直接返回401。关键点:要把用户信息通过exchange.getAttributes()向下传递,后续过滤器或下游服务就能拿到了。另外注意,鉴权Filter要区分登录接口等白名单路由,不能一刀切。

【面试题】4. Seata的AT模式为什么会有全局锁?全局锁和本地锁分别解决什么问题?会不会成为性能瓶颈? ​

【标准答案】AT模式的核心是一阶段提交+二阶段补偿,这要求在所有分支事务提交前,不能有脏写。全局锁是Seata在TC端维护的锁表,记录哪个全局事务锁住了哪行数据。一阶段业务SQL执行后,本地事务提交前,RM会向TC注册分支锁,获取全局锁成功后才提交本地事务。这就保证了其他全局事务不能同时修改这行数据。本地锁是数据库自身的行锁,保证同一个数据库实例内的并发安全。两者的分工:全局锁解决跨服务分布式事务的隔离问题,本地锁解决单库内的并发问题。全局锁会带来性能影响:一是全局锁获取需要网络请求TC,增加RT;二是如果热点数据频繁被不同全局事务争抢,锁等待会导致超时回滚。缓解方式:避免大事务覆盖过多行、合理设计避免热点行、AT模式下for update可以用读已提交+全局锁替代。如果全局锁确实是瓶颈,可以考虑TCC模式,它在业务层面控制锁的粒度,更灵活但开发成本更高。

【面试题】5. Redis分布式锁用SET NX EX实现有什么问题?Redisson是怎么解决的?RedLock是不是更好的方案? ​

【标准答案】手写SET key value NX EX seconds有三个经典问题:①锁续期问题——如果业务执行时间超过锁的过期时间,锁自动释放,其他线程获取锁导致并发问题。Redisson用Watchdog机制解决,默认锁30秒,每10秒(1/3超时时间)自动续期,业务没执行完就不让锁过期。②锁误删问题——线程A的锁过期后线程B获取锁,线程A执行完误删了B的锁。解决方式是value存唯一标识(UUID+线程ID),删除时Lua脚本先比较再删除,保证原子性。③主从切换导致锁丢失——主节点加锁后未同步到从节点就宕机,从节点升为主后没有锁信息。RedLock就是解决这个问题的:向N个独立Redis节点(通常5个)加锁,超过半数加锁成功才算获取锁。但RedLock有争议:Martin Kleppmann指出它依赖时钟同步,在时钟跳变等场景下不安全;Redis作者antirez做了反驳。实际建议:大多数业务场景用Redisson单节点锁+Watchdog就够了;对锁可靠性要求极高(金融扣款)用Zookeeper分布式锁更稳;跨机房多节点才考虑RedLock。

【面试题】6. OpenFeign的超时重试配置有哪些坑?如何避免接口被重复调用导致数据不一致? ​

【标准答案】这是实际项目中经常踩的坑。超时配置有两个层级:feign.client.config.default.connectTimeout和readTimeout,还有Ribbon层的ribbon.ConnectTimeout和ribbon.ReadTimeout。如果两个都配了,取更小的值生效,容易搞混。重试配置更坑:Ribbon默认重试1次,而且ribbon.OkToRetryOnAllOperations默认false(只对GET重试)。如果你把它改成true,POST/PUT也会重试,但接口不是幂等的话就会出大问题——比如扣款接口重试导致重复扣款。避免重复调用的方案:第一,非幂等接口(POST/PUT)绝不开启重试,保持OkToRetryOnAllOperations=false;第二,读超时调大一点,重试次数设0或1,别贪多;第三,关键接口做幂等设计——请求带唯一requestId,服务端用Redis去重,相同requestId的请求直接返回之前的结果;第四,新版Spring Cloud LoadBalancer取代Ribbon后,默认不重试,需要显式配置spring.cloud.loadbalancer.retry.enabled=true。一句话总结:重试只给幂等接口用,非幂等接口老老实实做幂等设计。

【面试题】7. Spring Cloud Sleuth的TraceId是怎么跨服务传递的?如果中间经过了MQ,TraceId还能延续吗? ​

【标准答案】Sleuth的TraceId传递核心是把它塞进HTTP请求头里。具体来说,Sleuth在出口处通过Brave组件的injector把TraceId、SpanId、ParentSpanId写入请求头,Header名称是X-B3-TraceId、X-B3-SpanId、X-B3-ParentSpanId(B3协议)。下游服务收到请求后,TracingFilter拦截这些Header,extractor提取出来,把当前请求挂到同一个Trace链上。MQ场景:如果中间经过消息队列,HTTP Header的方式就不行了,因为MQ的消息头机制不同。Sleuth对Spring Cloud Stream做了集成,在生产者发送消息时,把B3 Header写入MQ消息的Header(如Kafka的Record Header、RabbitMQ的Message Property),消费者拉取消息时从MQ Header中提取。所以TraceId是可以跨MQ延续的,前提是使用Spring Cloud Stream集成,而不是自己手动操作MQ客户端。如果用了RocketMQ但没用Stream集成,需要手动在消息体或消息属性中传递TraceId。另外注意,如果消费者是异步批量消费,要确保每条消息的Span是独立的,不能共享同一个SpanId。

【面试题】8. Nacos Config的配置热更新原理是什么?长轮询和长连接有什么区别?配置变更的延迟有多大? ​

【标准答案】Nacos Config的配置热更新有两种机制:长轮询(1.x版本)和长连接(2.x版本)。1.x的长轮询:客户端向Nacos发送请求,带上配置的md5值,Nacos比对md5,有变化立刻返回,没变化则hold住请求最多29.5秒(不是30秒,留0.5秒防客户端超时),期间如果配置变更,Nacos通过changed事件唤醒挂起的请求立即返回。客户端收到响应后立刻发下一次长轮询请求。2.x的gRPC长连接:客户端和Nacos建立gRPC双向流,配置变更时服务端主动推送给客户端,不需要反复建立连接。延迟差异:长轮询模式最坏延迟29.5秒(刚好hold住时配置变了要等超时),平均1秒内;gRPC长连接几乎是实时推送,延迟在毫秒级。Spring Boot集成:配置变更后需要@RefreshScope或@NacosValue(autoRefreshed=true)才能热更新到Bean,否则只是Nacos Client内存里的值变了,应用Bean不会重新创建。这也是一个常见坑:改了Nacos配置但没生效,大概率是忘了加@RefreshScope。

【面试题】9. 在Kubernetes环境下部署Spring Cloud微服务,还需要Eureka/Nacos这类注册中心吗?Spring Cloud Kubernetes能替代吗? ​

【标准答案】这是云原生转型中的经典问题。传统注册中心(Eureka/Nacos)在K8s里能用,但不是最优解:一是每个服务都要和注册中心通信,多一层依赖;二是注册中心的健康检查和K8s自身的Probe是重复的;三是网络模型变了,K8s里Pod有Service做负载均衡,注册中心做的服务发现和K8s Service是重叠的。Spring Cloud Kubernetes可以替代注册中心,它直接读取K8s API获取Service和Endpoint信息作为服务发现数据,不需要额外的注册中心。配置方式是把spring-cloud-starter-kubernetes引入,discovery模式自动生效。但注意几个问题:①权限:Pod需要RBAC权限读取K8s API的Service/Endpoint资源;②跨命名空间:默认只发现同namespace的服务,跨namespace需要配置;③非K8s环境:开发环境可能不在K8s里跑,需要做profile切换。更现代的方案是用Service Mesh(Istio),服务发现、负载均衡、熔断全下沉到基础设施层,应用代码完全不感知,这才是云原生的终极形态。总结:简单场景用Spring Cloud Kubernetes替代注册中心没问题;复杂场景或混合云环境,保留Nacos更灵活;长期目标应该是Service Mesh。

【面试题】10. 微服务之间调用链路过长导致延迟高,如何排查和优化?请给出系统化的方法论。 ​

【标准答案】链路延迟排查要系统化,不能瞎猜。第一步:定位瓶颈——通过链路追踪(SkyWalking/Zipkin)看Trace瀑布图,一眼看出哪个Span耗时最长。如果是数据库Span慢,看慢SQL;如果是外部调用Span慢,看第三方接口RT;如果是内部服务间调用慢,逐层下钻。第二步:区分类型——是单次慢还是偶发慢?单次慢是架构/代码问题,偶发慢可能是GC、网络抖动、连接池耗尽。看P99和P50的差距,差距大说明有长尾问题。第三步:优化手段分层:①网络层——服务间调用走内网、启用HTTP/2或gRPC减少连接开销、开启GZIP压缩大body;②调用层——并行化串行调用(CompletableFuture)、加缓存减少下游调用、合并细粒度接口为粗粒度批量接口;③数据层——加索引、分库分表、读写分离、引入Redis缓存热点数据;④架构层——链路太长考虑合并服务、引入事件驱动解耦、用Sidecar做本地缓存。第四步:防御机制——全链路超时控制(每层设合理超时,最外层兜底)、熔断降级(Sentinel兜底避免雪崩)、异步化(MQ解耦非核心链路)。关键原则:先量化再优化,每一步优化都要有数据对比。