本轮 SpringCloud微服务架构面试题库(带答案)
生成时间:2026-05-22 22:57 | 共10题
【面试题】1. Nacos既支持AP又支持CP,它是如何在这两种模式间切换的?什么场景下应该选择AP,什么场景下选择CP?
【标准答案】Nacos通过临时实例和持久化实例来区分AP与CP模式。临时实例(ephemeral=true,默认)采用AP模式,服务注册后靠客户端心跳维持,Nacos集群间用Distro协议(类似Gossip)做数据同步,不保证强一致,但可用性高;持久化实例(ephemeral=false)采用CP模式,服务注册后数据持久化到磁盘,集群间用Raft协议做Leader选举和日志复制,保证强一致性。
选择AP的场景:微服务实例是临时的、弹性伸缩频繁的(如K8s Pod),实例上下线很常见,此时可用性比一致性更重要——短暂的注册信息不一致可以接受,但注册中心不可用会导致大面积服务不可达。
选择CP的场景:服务实例是固定的、长生命周期的(如数据库、外部供应商接口),这类服务数量少且稳定,数据不一致比短暂不可用更危险——比如一个已经下线的数据库实例还出现在注册列表里,会导致请求打到已关闭的连接上。
实际工作中90%以上的微服务注册场景用AP就够了,只有配置中心和服务端发现场景才需要CP。
【面试题】2. Sentinel的滑动窗口限流算法是如何实现的?相比固定窗口和漏桶算法,它解决了什么问题?
【标准答案】Sentinel默认采用滑动窗口算法来统计QPS,核心实现是LeapArray数据结构。它把一个时间窗口(比如1秒)划分成多个小格子(默认2个,每格500ms),每个格子维护一个计数器。随着时间的推移,窗口向前滑动,淘汰最老的格子,加入新的格子。当前QPS = 当前窗口内所有格子的计数之和。
对比固定窗口:固定窗口在窗口边界处会出现"突刺"问题——比如限流100/s,在0.9s时来了100个请求,1.1s又来了100个请求,虽然两个窗口都没超限,但0.9s~1.1s这0.2s内就过了200个请求。滑动窗口因为窗口是连续滑动的,不存在这种边界突刺。
对比漏桶算法:漏桶以恒定速率出水,能保证绝对平滑的输出速率,但不允许任何突发流量。滑动窗口允许在窗口内的合理突发(只要窗口总量不超限),更贴合实际的业务流量特征——比如一个接口平时50 QPS,偶尔有个80的短脉冲,滑动窗口能放行,漏桶会拒绝很多请求。
实际项目中,一般接口限流用滑动窗口就够了,需要严格匀速出队的场景(如消息投递到下游限速系统)才用漏桶或令牌桶。
【面试题】3. Spring Cloud Gateway的过滤器链是如何工作的?GlobalFilter和GatewayFilter有什么区别?如何自定义一个鉴权过滤器?
【标准答案】Gateway的过滤器链基于WebFlux的Reactor模式,请求进入后依次经过前置过滤器(pre phase),路由到下游服务,响应返回时再逆序经过后置过滤器(post phase)。整个链路是异步非阻塞的。
GlobalFilter vs GatewayFilter:
- GlobalFilter:全局生效,对所有路由都起作用,无需在每个路由上单独配置。适合做鉴权、日志、跨域等全局逻辑。
- GatewayFilter:路由级别生效,只在配置了该Filter的路由上触发。适合做特定路由的请求重写、限流等。
自定义鉴权Filter示例:实现GlobalFilter和Ordered接口。在filter方法中,从ServerWebExchange提取请求头中的Token,校验Token合法性——合法则chain.filter(exchange)放行,不合法则exchange.getResponse().setComplete()设置401状态码直接返回。Ordered接口的getOrder()返回值越小优先级越高,鉴权Filter应该设高优先级(如-1)确保在业务Filter之前执行。
关键细节:响应式编程中不能阻塞,Token校验如果是远程调用(如调Redis/认证服务),必须用响应式API(如ReactiveRedisTemplate),不能同步阻塞,否则会卡死Netty线程。
【面试题】4. Seata的AT模式是如何保证分布式事务一致性的?undo_log表的作用是什么?遇到脏回滚怎么处理?
【标准答案】Seata AT模式是二阶段提交的自动化版本,对业务代码零侵入(只需加@GlobalTransactional注解)。
一阶段:拦截业务SQL,在执行前查询修改前的数据(before image),执行业务SQL后查询修改后的数据(after image),生成行锁和回滚日志(undo_log),将undo_log和业务SQL在同一个本地事务中一起提交。这样一阶段就释放了本地锁,其他事务可以继续操作,提高了并发度。
二阶段提交:协调者通知各分支提交,异步清理undo_log即可,非常快。
二阶段回滚:根据undo_log中的before image生成反向SQL(反向补偿),把数据恢复到修改前的状态,然后清理undo_log。
undo_log表的作用:存储before image和after image,是回滚的数据依据。它和业务表在同一个库中,保证本地事务的一致性。
脏回滚问题:当一个全局事务回滚时,如果数据已经被另一个本地事务修改了(before image对不上当前数据),直接用before image回滚就会覆盖别人的修改——这就是脏回滚。Seata的处理:回滚时先比较after image和当前数据,如果一致说明没人改过,安全回滚;如果不一致,说明数据已被其他事务修改,Seata会记录异常并且回滚失败,需要人工介入。所以AT模式适合"读多写少、冲突概率低"的场景,高冲突场景建议用TCC模式。
【面试题】5. Redis分布式锁用SET NX EX实现和用Redisson实现有什么本质区别?Redisson的看门狗机制是如何工作的?
【标准答案】SET NX EX的局限:基础Redis锁就是SET lock_key unique_value NX EX 30,它有三个致命问题:
- 锁续期问题:如果业务执行超过30秒,锁自动过期,其他线程获取到锁,导致并发安全问题。
- 释放别人的锁:线程A的锁过期后,线程B拿到锁,线程A执行完执行DEL把B的锁删了。需要用Lua脚本保证"判断value相等再删"的原子性。
- 不可重入:同一个线程无法对同一把锁多次加锁,递归调用场景无法使用。
Redisson的改进:
- 看门狗续期:默认锁30秒,Redisson在后台起一个定时任务(每10秒=30/3),检查持有锁的线程是否还活着,如果活着就自动续期到30秒。业务执行多久锁就续多久,不会提前过期。看门狗在锁被释放或客户端宕机时自动停止。
- 可重入锁:Redisson用Hash结构存储锁,field是线程ID,value是重入次数。同一线程可多次加锁,计数+1,解锁时-1,归零才真正释放。
- 释放安全:内部用Lua脚本保证判断+删除的原子性。
看门狗的核心:本质是一个Netty的HashedWheelTimer定时任务,每次成功加锁后启动,默认每10秒检查一次。如果业务线程还在运行,就执行pexpire命令续期。客户端挂掉后看门狗自然停止,锁30秒后自动释放,不会死锁。
【面试题】6. Spring Cloud OpenFeign的超时和重试机制如何配置?在生产环境中如何避免重试风暴?
【标准答案】超时配置:OpenFeign的超时分两层——连接超时(connectTimeout)和读取超时(readTimeout)。可以在配置文件中全局设置,也可以针对某个服务单独设置:
feign:
client:
config:
default: # 全局默认
connectTimeout: 5000
readTimeout: 10000
order-service: # 针对特定服务
connectTimeout: 3000
readTimeout: 5000连接超时设短一些(1-3秒),因为连不上通常是网络问题,等再久也没用;读取超时根据下游接口的P99响应时间设置,一般设为P99的2-3倍。
重试配置:OpenFeign默认不重试。如果需要开启,要自定义Retryer——Feign的Retryer.Default默认最多重试5次(最大重试间隔1秒),这个在生产环境很危险。建议自定义Retryer,设置最多重试1-2次,且只对IO异常(连接超时、网络断开)重试,不对业务异常重试。
避免重试风暴:
- 限制重试次数:最多1次,且重试间隔加随机抖动(避免所有实例同时重试)。
- 配合熔断器:Sentinel/Hystrix在错误率达到阈值时直接熔断,不再发起请求,从源头切断重试链路。
- 幂等性保障:只有GET或确认幂等的POST才重试,非幂等操作绝不重试。
- 超时时间要考虑重试:如果下游P99是3秒,readTimeout设5秒,允许1次重试的话,上游接口自身的超时至少要设10秒以上,否则重试还没完成就被上层超时打断了。
【面试题】7. Nacos配置中心如何实现配置热更新?@RefreshScope的原理是什么?配置变更后如何保证所有实例都及时感知?
【标准答案】热更新机制:Nacos配置中心通过长轮询(Long Polling)实现配置变更的实时感知。客户端发起一个带超时(默认30秒)的HTTP请求到Nacos Server,如果期间配置没变化,Server挂起请求直到超时返回;如果配置变了,Server立即返回变更的dataId和group,客户端再拉取最新配置值。这个机制比定时轮询实时性好、比长连接资源消耗低。
@RefreshScope原理:这是Spring Cloud的机制,配合Nacos使用。加了@RefreshScope的Bean会被CGLIB代理,放入一个专门的Scoped Proxy缓存中。当配置变更事件触发时,Spring Cloud Context会广播RefreshEvent,@RefreshScope监听到事件后清空缓存。下次有请求访问这个Bean时,因为缓存为空,Spring会重新创建Bean实例,此时会注入最新的配置值。所以本质是"销毁旧Bean+创建新Bean",不是原地修改字段值。
保证所有实例感知:
- Nacos的配置变更通过集群间同步(Distro/Raft协议)保证每个Nacos节点都有最新数据。
- 每个客户端实例独立与Nacos建立长轮询连接,配置变更后Nacos会通知所有连接的客户端。
- 如果长轮询偶发失败,Nacos还有兜底的定时拉取(默认每30秒拉一次)。
- 生产环境建议用Nacos Config的配置变更监听器(Listener),在onChange回调中做配置生效后的校验和日志记录,确保变更可追踪。
【面试题】8. 在Kubernetes环境中部署Spring Cloud微服务,服务发现用K8s Service还是Nacos?两者的适用场景和组合方案是什么?
【标准答案】K8s Service(CoreDNS):基于K8s原生的Service和Endpoint资源,Pod注册为Service后,通过CoreDNS做服务名解析。优点是与K8s深度集成,Pod上下线自动同步;缺点是只有简单的轮询负载均衡,没有权重路由、元数据过滤等高级功能。
Nacos:微服务主动注册到Nacos,有丰富的元数据(版本、权重、分组等),支持灰度路由、流量染色。缺点是在K8s环境中多了一套注册中心,Pod IP变化频繁(每次重建都换IP),需要处理实例下线的及时感知。
适用场景:
- 纯K8s内部调用、简单微服务架构 → K8s Service足够,少维护一个组件。
- 需要灰度发布、流量染色、多版本路由 → 必须用Nacos,K8s Service做不了这些。
- 混合环境(部分服务在K8s,部分在ECS/物理机)→ Nacos统一注册中心,跨环境发现。
组合方案(推荐):Nacos做服务注册发现,K8s Service仅用于Nacos集群自身和入口网关的暴露。微服务间调用走Nacos + OpenFeign/Sentinel,享受完整的微服务治理能力。同时利用K8s的readinessProbe确保Pod就绪后才注册到Nacos,利用preStop钩子在Pod终止前主动从Nacos下线,避免K8s杀Pod导致的请求丢失。
关键配置:Pod的preStop设sleep 15给K8s时间从Service摘除,再在应用层实现@PreDestroy主动调用Nacos注销接口。
【面试题】9. SkyWalking和Zipkin在链路追踪架构上的核心区别是什么?如何选择?
【标准答案】架构区别:
| 维度 | Zipkin | SkyWalking |
|---|---|---|
| 数据收集 | 应用内埋点(Sleuth/Brave) | Java Agent字节码增强(无侵入) |
| 传输方式 | HTTP/Kafka推送 | gRPC推送(高性能) |
| 存储引擎 | 内存/MySQL/Elasticsearch/Cassandra | Elasticsearch/H2/MySQL |
| 分析能力 | 基础调用链查看 | 端到端拓扑图、服务SLA、告警、慢接口分析 |
| 语言支持 | 多语言SDK | Java Agent为主,其他语言有探针 |
| 社区定位 | 轻量级、易上手 | 全栈APM平台 |
选择建议:
选Zipkin的场景:项目简单、只需基础调用链追踪、团队不想装Agent、已有Spring Cloud Sleuth集成。Zipkin跑个jar就能用,5分钟上手。
选SkyWalking的场景:生产环境需要完整的可观测性——不止看调用链,还要看服务拓扑、慢SQL、P99延迟趋势、告警。SkyWalking的Java Agent零侵入,不用改代码,不用加依赖,升级时只换Agent包。8个以上微服务的生产环境强烈推荐SkyWalking。
核心差异总结:Zipkin是"看到调用链",SkyWalking是"理解整个系统"。小项目用Zipkin够了,生产环境必须上SkyWalking。两者不是替代关系,SkyWalking是更高维度的APM方案。
【面试题】10. 微服务架构下,JWT Token在网关到下游服务的传递过程中如何保证安全?Refresh Token应该存储在哪里?如何防范Token泄露?
【标准答案】Token传递链路:客户端携带JWT请求网关 → 网关验证签名和过期时间 → 验证通过后,网关将Token原样传递给下游服务(通过请求头Authorization: Bearer xxx)→ 下游服务解析Token获取用户信息,不再重复验签(信任网关的校验结果)。
安全保证:
- 网关统一验签:所有入口流量必须经过网关,下游服务内网部署,外部不可直连。下游服务信任内网环境,只做业务鉴权(检查角色/权限),不做Token验签。
- 短有效期:Access Token有效期设15-30分钟,即使泄露影响窗口小。
- HTTPS全链路加密:网关入口和内部服务间通信都走HTTPS(或K8s内网mTLS),防止中间人窃取Token。
Refresh Token存储:
- Web端:存HttpOnly + Secure + SameSite=Strict的Cookie中,前端JS无法读取,防XSS窃取。
- 移动端:存安全存储(iOS Keychain / Android Keystore),不存SharedPreferences。
- 绝对不能存localStorage——XSS攻击一行代码就能读走。
防范Token泄露:
- Token绑定设备指纹:签发Token时把设备信息(UA+IP哈希)写入Payload,验证时比对,不一致则拒绝。
- Token黑名单:网关维护已注销Token的短期黑名单(Redis TTL = Token剩余有效期),用户主动登出或改密码后立即失效。
- Refresh Token轮换:每次用Refresh Token换新Access Token时,同时签发新的Refresh Token,旧的立即失效。一旦检测到已废弃的Refresh Token被使用,说明泄露,立即吊销该用户所有Token。
- 审计日志:记录每次Token使用的时间、IP、设备,异常异地登录及时告警。