Skip to content

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

生成时间:2026-05-21 22:28 题目数量:10道


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

【面试题】1. Nacos作为注册中心时,如何实现服务的优雅上下线? ​

【标准答案】优雅上线:服务启动后先注册到Nacos,但标记为"未就绪"状态,等应用完全初始化完成后再改为"健康"状态。可以通过配置spring.cloud.nacos.discovery.enabled=true和健康检查机制实现。

优雅下线:服务停止前,先从Nacos注销(调用注销接口或发送shutdown事件),让Nacos及时推送服务下线通知给其他消费者,消费者收到后从本地缓存移除该实例。同时服务端设置一个缓冲期(比如30秒),期间不再接收新请求,等现有请求处理完再真正停机。

实际项目中,我们通过实现ApplicationListener<ContextClosedEvent>监听容器关闭事件,在关闭前先调用Nacos API注销服务,等待请求处理完毕后再退出,避免了硬杀导致请求失败。


【面试题】2. Spring Cloud Gateway如何实现动态路由?有什么实际应用场景? ​

【标准答案】动态路由核心是利用Gateway的RouteDefinitionLocator接口,通过自定义实现类从数据库或配置中心加载路由规则,并支持实时刷新。

实现方式:定义一个DynamicRouteService类,注入RouteDefinitionWriter,从数据库查询路由配置(包含路径、断言、过滤器等),封装成RouteDefinition对象后调用save()方法写入。配合Nacos Config或数据库变更监听,路由规则变化时触发刷新,网关无需重启即可生效。

实际应用:我们项目用动态路由做灰度发布,根据请求头中的版本标识路由到不同版本的服务实例;还有多租户场景,根据tenantId路由到不同租户的专属服务集群。运维后台可以实时配置上线新服务,不需要改配置文件重启网关。


【面试题】3. Sentinel的熔断降级和Hystrix有什么区别?生产环境如何选择? ​

【标准答案】核心区别:

  1. 资源隔离方式:Hystrix默认线程池隔离,Sentinel支持线程数和信号量两种隔离方式,更灵活。
  2. 熔断策略:Hystrix主要基于异常比例和请求失败率;Sentinel支持慢调用比例、异常比例、异常数三种策略,还支持热点参数限流。
  3. 限流能力:Sentinel原生支持QPS限流、线程数限流、关联限流、链路限流,Hystrix本身不提供限流功能。
  4. 监控生态:Sentinel有Dashboard控制台,支持实时监控、规则配置、集群限流;Hystrix已停止维护,监控依赖Turbine。
  5. 性能开销:Sentinel基于滑动窗口统计,性能更好;Hystrix线程池隔离有额外线程开销。

生产选择:新项目首选Sentinel,特别是Spring Cloud Alibaba全家桶项目。老项目如果已经在用Hystrix且稳定运行,可以继续用,但建议制定迁移计划。我们团队2024年从Hystrix迁移到Sentinel,主要看中Dashboard和热点限流能力,迁移过程用Resilience4j做过渡方案。


【面试题】4. 分布式事务Seata的AT模式原理是什么?有什么优缺点? ​

【标准答案】AT模式原理:

  1. 一阶段:业务SQL执行时,Seata拦截SQL,解析语义,记录"前镜像"(修改前数据),执行业务SQL,再记录"后镜像"(修改后数据),生成行锁。整个过程在本地事务中完成,保证原子性。
  2. 二阶段提交:所有分支事务成功,TC(Transaction Coordinator)通知各RM(Resource Manager)异步删除undo_log。如果一阶段有失败,TC通知各RM根据undo_log反向生成SQL回滚。
  3. 全局锁:为了防止脏写,Seata在TC维护全局锁,一阶段本地事务提交前先申请全局锁,保证同一行数据同一时刻只有一个分布式事务能修改。

优点:

  • 代码零侵入,只需加@GlobalTransactional注解
  • 性能较好,一阶段本地事务直接提交,不阻塞
  • 适合大多数业务场景,学习成本低

缺点:

  • 存在脏回滚风险,如果回滚时数据已被其他本地事务修改,可能导致数据不一致
  • 全局锁竞争激烈时影响并发性能
  • 不支持复杂SQL(如多表关联更新)

实际项目:我们订单服务用AT模式,核心逻辑是扣库存、创建订单、扣余额,一阶段提交本地事务,二阶段全局提交。性能要求高的场景考虑TCC模式,业务侵入性强但性能更好。


【面试题】5. 如何保证消息队列在微服务架构中的可靠性投递?有哪些关键环节? ​

【标准答案】可靠性投递要保证"消息不丢、不重复、不丢失顺序":

生产端可靠性:

  1. 发送确认机制:RocketMQ的SendResult返回SEND_OK,Kafka的acks=all,RabbitMQ的Publisher Confirm
  2. 消息持久化:发送前先持久化到本地消息表(定时任务扫描未确认消息重发)
  3. 失败重试:配置重试次数和间隔,记录失败日志告警

Broker端可靠性:

  1. 多副本同步:Kafka配置min.insync.replicas≥2,RocketMQ配置同步刷盘
  2. 持久化存储:消息写入CommitLog后再返回确认
  3. 集群部署:避免单点故障

消费端可靠性:

  1. 手动ACK:业务处理成功再提交offset/ack
  2. 幂等性设计:消费逻辑支持重复消费(基于业务主键去重或Redis去重表)
  3. 死信队列:重试多次失败的消息转移到死信队列人工处理

实际项目:我们支付通知服务用RocketMQ,生产者发送前写入本地消息表(状态为发送中),定时任务扫描重发;消费者先查Redis去重表,处理成功写入Redis并返回CONSUME_SUCCESS,失败返回RECONSUME_LATER触发重试,重试16次进入死信队列告警处理。


【面试题】6. 微服务之间如何实现安全认证?Token如何在服务间传递? ​

【标准答案】微服务安全认证主流方案:

网关统一认证:

  1. 外部请求先到Gateway,Gateway集成OAuth2.0 Server或JWT校验
  2. 校验通过后,Gateway解析Token提取用户信息,放入请求头(如X-User-Id、X-User-Roles)
  3. 内部服务间调用通过请求头传递用户上下文,不再重复校验Token

服务间安全:

  1. 内部服务调用使用内部Token(如Service Token),Gateway签发短期Token给服务
  2. 每个服务配置白名单IP或mTLS双向认证
  3. 敏感操作二次校验(如支付、修改密码)

Token传递实现:

java
// 在OpenFeign拦截器中传递Token
public class AuthRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader("Authorization");
            if (StringUtils.isNotBlank(token)) {
                template.header("Authorization", token);
            }
        }
    }
}

Refresh Token机制:

  • Access Token有效期短(如2小时),Refresh Token有效期长(如7天)
  • Token快过期时,前端用Refresh Token换取新Access Token
  • Refresh Token只能使用一次,用后失效,防止重放攻击

实际项目:我们用Gateway + JWT做网关认证,内部服务通过Feign拦截器自动传递Token,敏感服务(支付服务)额外验证IP白名单和签名。


【面试题】7. Ribbon和Spring Cloud LoadBalancer有什么区别?为什么Ribbon被替换? ​

【标准答案】主要区别:

  1. 维护状态:Ribbon已进入维护模式(Maintenance Mode),Netflix不再积极开发;Spring Cloud LoadBalancer是Spring官方维护,持续更新。
  2. 依赖体系:Ribbon依赖Netflix OSS生态,与Spring Boot 2.4+版本兼容性变差;LoadBalancer是Spring Cloud原生组件,与Spring生态无缝集成。
  3. 配置方式:Ribbon使用@LoadBalanced注解,配置项以ribbon.开头;LoadBalancer同样用@LoadBalanced,但配置项以spring.cloud.loadbalancer.开头,更统一。
  4. 负载均衡策略:Ribbon提供7种策略(轮询、随机、权重、响应时间权重等);LoadBalancer默认提供轮询和随机,但可通过ServiceInstanceListSupplier自定义扩展,更灵活。
  5. 缓存机制:Ribbon默认缓存服务实例列表,可能导致服务下线后仍被访问;LoadBalancer支持配置缓存过期时间,更可控。

为什么被替换:

  • Netflix OSS组件大部分停止维护(Eureka、Hystrix、Ribbon、Zuul)
  • Spring Boot 2.4+移除了Ribbon的自动配置支持
  • Spring Cloud 2020.x版本开始,LoadBalancer成为默认负载均衡器

迁移建议:

yaml
# Ribbon配置(旧)
ribbon:
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

# LoadBalancer配置(新)
spring:
  cloud:
    loadbalancer:
      ribbon:
        enabled: false  # 禁用Ribbon
      configurations: random  # 使用随机策略

实际项目:我们2023年完成迁移,主要改动点是配置项迁移和自定义策略重写,业务代码零改动。


【面试题】8. 链路追踪中TraceId如何跨服务传递?异步调用时如何保证TraceId不丢失? ​

【标准答案】TraceId传递机制:

同步调用传递:

  1. Sleuth/Micrometer在HTTP请求头中注入traceId和spanId(请求头:X-B3-TraceId、X-B3-SpanId)
  2. 下游服务收到请求后,过滤器解析请求头,提取TraceId加入当前上下文(MDC)
  3. OpenFeign/RestTemplate拦截器自动将TraceId透传到下游请求头
  4. 日志框架通过MDC自动打印TraceId(%X{traceId})

异步调用传递:

  1. 线程池传递:使用TraceableExecutorService包装线程池,提交任务时自动复制父线程的TraceId到子线程
  2. @Async注解:配置TaskDecorator,在任务执行前复制TraceId
  3. 手动传递:异步任务提交时,从当前上下文获取TraceId,作为参数传递给异步方法,在异步方法内手动设置到MDC
java
// 线程池包装示例
@Bean
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setTaskDecorator(new ContextCopyingDecorator());  // 复制上下文
    return executor;
}

// 手动传递示例
String traceId = MDC.get("traceId");
CompletableFuture.runAsync(() -> {
    MDC.put("traceId", traceId);
    try {
        // 业务逻辑
    } finally {
        MDC.remove("traceId");
    }
});

消息队列传递: RocketMQ/Kafka消费者在消费消息时,从消息头提取TraceId设置到当前上下文,保证消息链路追踪连续。

实际项目:我们用Sleuth + Zipkin,所有日志自动带TraceId,异步任务用TraceableExecutorService包装,消息队列在生产者端将TraceId写入消息头,消费者端解析后设置到MDC,全链路追踪完整覆盖。


【面试题】9. Redis分布式锁如何解决锁续期和锁释放问题?Redisson是如何实现的? ​

【标准答案】锁续期问题:

  • 场景:业务执行时间超过锁过期时间,锁自动释放,其他线程获取锁导致并发问题
  • 解决:看门狗机制(Watchdog),后台线程定时(每10秒)检测锁是否还被持有,如果持有则续期(重置过期时间为30秒)

Redisson实现:

java
RLock lock = redisson.getLock("myLock");
lock.lock();  // 默认30秒过期,启动Watchdog自动续期
try {
    // 业务逻辑
} finally {
    lock.unlock();  // 释放锁
}

锁释放问题:

  • 误删问题:线程A的锁过期释放,线程B获取锁后,线程A执行完释放了线程B的锁
  • 解决:释放锁时验证锁的标识(value为UUID+线程ID),只释放自己持有的锁
  • Lua脚本保证原子性:Redisson用Lua脚本执行get + compare + del,保证原子操作
lua
-- Redisson释放锁Lua脚本
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

Redisson核心特性:

  1. 可重入锁:基于Redis Hash结构存储,field为线程标识,value为重入次数
  2. 公平锁:按请求顺序获取锁,基于Redis List和ZSet实现
  3. 读写锁:支持读写分离,读锁共享,写锁排他
  4. 红锁(RedLock):多Redis实例加锁,解决单点故障问题

实际项目:我们支付服务用Redisson分布式锁防止重复支付,默认配置看门狗续期,业务超时自动释放,释放时Lua脚本验证锁标识,避免误删。集群环境用RedLock,5个Redis实例,至少3个加锁成功才算获取锁成功。


【面试题】10. Spring Cloud Gateway如何实现统一鉴权?鉴权失败如何返回自定义响应? ​

【标准答案】统一鉴权实现方式:

方式一:GatewayFilter全局过滤器

java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        
        // 1. 验证Token有效性(JWT解析或调用认证服务)
        if (StringUtils.isBlank(token) || !validateToken(token)) {
            return authError(exchange, "Token无效或已过期", 401);
        }
        
        // 2. 解析用户信息,放入请求头传递给下游服务
        UserInfo user = parseToken(token);
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header("X-User-Id", user.getUserId())
            .header("X-User-Roles", user.getRoles())
            .build();
        
        return chain.filter(exchange.mutate().request(request).build());
    }
    
    private Mono<Void> authError(ServerWebExchange exchange, String msg, int code) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        
        String body = JSON.toJSONString(Result.error(code, msg));
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
    
    @Override
    public int getOrder() {
        return -100;  // 优先级高,先执行
    }
}

方式二:集成OAuth2.0资源服务器

yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://auth-server/.well-known/jwks.json

鉴权失败自定义响应:

  1. 实现ServerAuthenticationEntryPoint,返回自定义JSON
  2. 异常处理器捕获AuthenticationException,统一格式返回
  3. 白名单路径(如/login、/public/**)通过WebSecurityConfigurer放行

实际项目:我们Gateway用全局过滤器做JWT校验,Token无效返回{"code":401,"message":"请重新登录"},同时根据路径和角色做RBAC鉴权,权限不足返回{"code":403,"message":"无权限访问"},所有下游服务从请求头获取用户信息,不再重复鉴权。


说明:本批次题目覆盖Nacos优雅上下线、动态路由、Sentinel vs Hystrix、Seata AT模式、消息可靠性投递、微服务安全认证、Ribbon vs LoadBalancer、链路追踪TraceId传递、Redisson分布式锁、Gateway统一鉴权等核心考点。