Skip to content

Java分布式架构面试题库 ​

生成时间:2026-05-24 20:16


【面试题】1. CAP定理中为什么说分区容错性P是必须保证的?在什么场景下可以选择CP或AP? ​

【标准答案】这是一个很好的切入点问题。CAP定理的核心是分布式系统在网络分区发生时,必须在一致性和可用性之间做选择。

分区容错性P必须保证,是因为分布式系统天生就存在网络不可靠的问题——网络延迟、节点故障、消息丢失都是常态,分区是不可避免的现实约束。如果放弃P,就意味着不允许网络分区,这在真实分布式环境中是不现实的。

选择CP的场景:金融账户系统、库存扣减、订单状态管理。比如银行转账,宁可系统暂时不可用,也不能出现账户余额不一致,否则会产生严重后果。Zookeeper就是典型的CP系统,Leader选举期间集群不可用。

选择AP的场景:社交媒体、电商商品展示、评论系统。用户刷新商品列表,短暂看到旧数据可以接受,但页面打不开会严重影响用户体验。Eureka就是AP设计,节点间数据同步失败时仍能提供服务注册查询。

实际项目中,我们往往在细分场景做选择——核心交易链路走CP,边缘业务走AP,做到整体平衡。


【面试题】2. Redisson的看门狗机制解决了什么问题?如果看门狗失效会发生什么? ​

【标准答案】Redisson的看门狗机制解决的是分布式锁自动续期问题。

传统Redis分布式锁用SET key value NX EX 30设置30秒过期,但如果业务执行超过30秒,锁就会自动释放,其他线程拿到锁后,原线程还在执行,导致并发安全问题。如果设置过期时间太长,又会出现持有者宕机后锁迟迟不释放的问题。

Redisson的看门狗机制是:默认每10秒检查一次锁是否还被当前线程持有,如果持有就续期到30秒。这样只要业务线程还在运行,锁就不会过期;业务结束主动释放,看门狗自动停止。

如果看门狗失效,比如Redisson客户端宕机、Redis连接断开、或者业务线程被kill,锁会在最后一次续期后30秒自动过期释放。这个设计保证了即使发生异常,锁也不会永久持有。

实际使用中,我们会根据业务平均执行时间调整lockWatchdogTimeout参数,确保看门狗续期间隔合理。极端情况还要考虑业务超时主动降级,防止死循环导致锁一直被持有。


【面试题】3. TCC模式和Saga模式在分布式事务中如何选择?各有什么适用场景? ​

【标准答案】这两个都是最终一致性的分布式事务方案,但设计思路完全不同。

TCC是Try-Confirm-Cancel,核心是预留资源、确认执行、取消回滚三个阶段。Try阶段冻结库存,Confirm扣减库存,Cancel解冻库存。优点是资源锁定粒度细,失败时能精确补偿。缺点是业务侵入性强,每个接口都要写三套逻辑,还要处理幂等、空回滚、悬挂等边界问题。

Saga是长事务拆分成多个本地事务,每个本地事务有对应的补偿动作。执行顺序是T1→T2→T3,失败时反向补偿C3→C2→C1。优点是对业务侵入小,适合跨系统的长流程事务。缺点是没有资源预留,可能出现脏读,补偿逻辑可能失败。

选择建议:

  • 金融核心交易、库存扣减等资源竞争激烈的场景,选TCC。因为能锁定资源,防止超卖。
  • 跨多个系统的长流程业务,如订单创建→库存扣减→积分增加→消息通知,选Saga。因为协调成本低,接入简单。
  • 如果系统已经有成熟的补偿机制,用Saga增量改造更快。如果从零建设且业务简单,TCC更可控。

实际项目中,我们结合使用:核心交易链路用Seata的TCC模式,边缘业务用Saga模式,配置中心统一管理事务状态。


【面试题】4. Snowflake雪花算法的时间回拨问题如何解决?WorkerId冲突怎么办? ​

【标准答案】Snowflake依赖机器时钟,时间回拨是最大的痛点,可能生成重复ID。

时间回拨解决方案:

  1. 轻微回拨(<5ms):等待时钟追平,直接sleep后继续生成。这是最常见的处理方式。
  2. 中等回拨(5ms~1s):使用备用workerId,开启新的ID序列空间。不同workerId生成的ID不会冲突。
  3. 严重回拨(>1s):直接报错或降级到数据库生成,让运维介入处理时钟问题。

百度UidGenerator的方案是:维护最近一秒内每毫秒的最大序列号,回拨时复用历史序列号空间,前提是单机QPS不超上限。

WorkerId冲突解决方案:

  1. Zookeeper注册:每个实例启动时向ZK注册临时节点,路径包含workerId,节点存在则换下一个。Leaf-segment就是这么干的。
  2. 数据库号段分配:在数据库维护workerId分配表,实例启动时申请租约,定期续约。
  3. 本地配置文件:小规模集群可以手工配置,适合机器数量固定的场景。

生产环境推荐用Zookeeper或数据库集中分配,配合心跳续约检测僵尸实例。同时做好监控告警,一旦workerId池耗尽或时钟回拨超阈值立即通知。


【面试题】5. 缓存穿透、缓存击穿、缓存雪崩的区别是什么?各用什么方案解决? ​

【标准答案】这三个是缓存三大经典问题,本质都是缓存失效导致请求直接打到数据库。

缓存穿透:查询不存在的数据,缓存无数据可存,每次请求都穿透到数据库。恶意攻击常用这种手段。 解决方案:

  • 布隆过滤器:启动时加载所有有效key,查询前先判断key是否可能存在,不存在直接返回。
  • 缓存空值:查不到数据时缓存null或特定标记,设置较短过期时间(如5分钟)。
  • 接口层校验:对参数做合法性校验,过滤明显非法请求。

缓存击穿:热点key在某一时刻过期,瞬间大量请求同时击穿缓存直达数据库。 解决方案:

  • 热点数据永不过期:不设置过期时间,后台异步更新。
  • 互斥锁:第一个请求去查数据库,其他请求等待结果。可以用Redis的SETNX实现。
  • 逻辑过期:数据本身不过期,但记录逻辑过期时间,查询时异步刷新。

缓存雪崩:大量key在同一时间集中过期,或Redis宕机导致全面缓存失效。 解决方案:

  • 过期时间加随机值:避免同时过期,如基础过期时间+随机1~5分钟。
  • 多级缓存:本地缓存+Redis缓存,Redis挂了本地缓存还能扛一阵。
  • 熔断降级:数据库压力过大时熔断,返回降级数据。
  • Redis高可用:主从+哨兵+Cluster,避免单点故障。

实际项目中,这三者往往结合使用。我们系统就是:布隆过滤器防穿透 + 热点key互斥锁防击穿 + 过期时间随机化+多级缓存防雪崩。


【面试题】6. Raft算法的Leader选举过程是怎样的?为什么能保证同一时刻只有一个Leader? ​

【标准答案】Raft是强一致性的共识算法,Leader选举是它的核心机制。

选举过程:

  1. 初始所有节点都是Follower状态,每个节点有随机的选举超时时间(150~300ms)。
  2. 如果Follower在超时时间内没有收到Leader心跳,转为Candidate状态,任期term+1,投自己一票。
  3. Candidate向其他节点发送RequestVote请求,请求参数包含term、candidateId、lastLogIndex、lastLogTerm。
  4. 其他节点收到请求后,检查:term是否>=自己的term、candidate的日志是否比自己新。都满足才投票。
  5. Candidate收到多数派投票后成为新Leader,立即发送心跳确认地位。

为什么保证单一Leader:

  1. 多数派原则:必须获得超过半数投票才能成为Leader,不可能有两个节点同时获得多数派。
  2. 随机超时:每个节点的选举超时随机,大概率只有一个节点先触发选举。
  3. Term递增:每个term最多一个Leader,新term的Leader会让旧term的Leader自动降级。
  4. 日志完整性校验:投票时检查候选者日志是否比自己新,保证选出的Leader拥有最新数据。

实际使用中,比如Etcd和Consul都是基于Raft。我们项目中用Etcd做配置中心,观察到Leader故障时,新Leader选举通常在1秒内完成,客户端几乎无感知。


【面试题】7. XXL-Job的任务分片机制如何实现海量数据处理?分片失败如何处理? ​

【标准答案】XXL-Job的分片广播模式能把一个大任务拆分成多个子任务并行处理,是处理海量数据的利器。

分片实现原理:

  1. 配置任务的路由策略为分片广播(ShardingUtil.ShardingVO)。
  2. 调度中心根据执行器数量动态生成分片参数:分片序号(0,1,2...)和总分片数。
  3. 每个执行器收到任务时,从ShardingUtil获取自己的分片序号。
  4. 业务代码按分片序号处理对应数据,如:WHERE MOD(id, 总分片数) = 分片序号。

代码示例:

java
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
int shardIndex = shardingVO.getIndex();  // 当前分片序号
int shardTotal = shardingVO.getTotal();   // 总分片数
// 查询本分片数据
List<Order> orders = orderMapper.selectByShard(shardIndex, shardTotal);

分片失败处理:

  1. 执行器宕机:其他执行器的分片任务正常完成,数据部分延迟。下次调度时自动重新分片。
  2. 新执行器加入:下次调度时自动加入分片,无需人工干预。
  3. 所有执行器宕机:任务执行失败,触发告警,按配置重试或跳过。

生产环境建议:

  • 分片数量=执行器数量或其倍数,便于负载均衡。
  • 数据源做好分库分表,避免单库成为瓶颈。
  • 任务失败时记录断点,支持从断点续跑。
  • 监控每个分片的执行时间,发现慢分片及时优化。

我们用XXL-Job处理每日千万级订单对账,10个执行器分片,单任务处理时间从2小时降到15分钟。


【面试题】8. Sentinel的限流策略有哪些?如何选择固定窗口、滑动窗口、令牌桶? ​

【标准答案】Sentinel提供了多种限流策略,核心是通过QPS或线程数控制流量入口。

主要限流策略:

  1. 固定窗口计数器:将时间划分为固定大小的窗口,如每秒一个窗口,统计窗口内请求数。 优点:实现简单,内存占用小。 缺点:边界问题,窗口切换瞬间可能承受2倍流量(前一窗口末尾+后一窗口开头)。 适用:对突发流量不敏感的场景,如后台任务限流。

  2. 滑动窗口:将大窗口划分为多个小格子,如1秒划分10个100ms格子,统计最近1秒内的请求。 优点:更平滑,避免固定窗口的边界问题。 缺点:内存占用略高,需要维护多个格子。 适用:需要精确控制QPS的API接口。

  3. 令牌桶:以固定速率往桶中放令牌,请求到达时取令牌,有令牌放行,无令牌拒绝。 优点:允许一定突发流量,桶内有令牌时可以快速响应。 适用:需要应对突发流量的场景,如秒杀、促销活动。

  4. 漏桶:请求像水一样流入桶中,以固定速率流出处理。 优点:输出流量绝对平稳。 缺点:无法应对突发流量,多余请求直接丢弃。 适用:需要严格保护下游系统的场景,如调用第三方API。

选择建议:

  • 内部服务调用:滑动窗口,精确控制QPS。
  • 面向用户的API:令牌桶,允许正常突发。
  • 调用外部API:漏桶,保护下游不超载。
  • 后台任务:固定窗口,简单够用。

Sentinel中通过FlowRule配置,grade字段指定策略,count指定阈值。我们生产环境80%接口用滑动窗口,核心交易接口用令牌桶+预热模式。


【面试题】9. 分布式链路追踪的TraceId和SpanId如何传递?异步调用时如何保证链路完整? ​

【标准答案】TraceId标识一次完整请求链路,SpanId标识链路中的每个服务节点。传递机制是分布式追踪的基础。

同步调用传递:

  1. 请求入口生成TraceId(如UUID或Snowflake ID),初始SpanId=1。
  2. 调用下游时,通过HTTP Header传递:X-Trace-Id、X-Span-Id、X-Parent-Span-Id。
  3. 下游服务收到请求,从Header提取TraceId,生成新的SpanId(如1.1、1.2),记录父SpanId。
  4. 依次类推,形成完整的调用树。

Spring Cloud Sleuth自动完成Header传递,无需业务代码干预。核心组件是TracingFilter,拦截请求自动创建Span。

异步调用传递(难点): 异步场景中,上下文会丢失,需要手动传递:

  1. 线程池场景:使用装饰过的线程池
java
ExecutorService executor = new TraceExecutorWrapper(
    Executors.newFixedThreadPool(10), 
    tracing
);

或使用TaskDecorator:

java
@Bean
public TaskDecorator taskDecorator() {
    return runnable -> {
        Span span = tracer.currentSpan();
        return () -> {
            try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
                runnable.run();
            }
        };
    };
}
  1. 消息队列场景:发送消息时将TraceId写入消息Header,消费时提取并恢复上下文。 Kafka示例:Producer拦截器注入Header,Consumer拦截器提取Header。

  2. 定时任务场景:任务触发时生成新TraceId,或从任务元数据中获取。

生产环境踩坑:

  • 记得配置采样率,全量采集对存储压力大。
  • 异步方法的@Async注解要配合TaskDecorator。
  • 跨线程调用用TransmittableThreadLocal替代InheritableThreadLocal。

我们用SkyWalking,配置10%采样率,链路数据存ES,保留7天。


【面试题】10. Nacos配置中心如何实现配置热更新?客户端配置变更监听的原理是什么? ​

【标准答案】Nacos配置热更新依赖长轮询机制,实现毫秒级配置变更通知。

热更新原理:

  1. 客户端启动时向Nacos Server注册监听,发送长轮询请求(默认30秒超时)。
  2. Nacos Server收到请求后,检查配置是否有变更:
    • 无变更:挂起请求,等待配置变更或超时返回。
    • 有变更:立即返回新配置。
  3. 客户端收到响应后:
    • 配置变更:回调Listener通知业务代码,触发@RefreshScope更新Bean。
    • 超时无变更:立即发起新的长轮询,保持持续监听。

长轮询 vs 轮询:

  • 传统轮询:客户端每N秒主动拉取,延迟=N秒,QPS高时服务端压力大。
  • 长轮询:请求挂起等待变更,实时性好,无变更时几乎没有无效请求。

客户端监听实现:

java
configService.addListener("app.yml", "DEFAULT_GROUP", new Listener() {
    @Override
    public void receiveConfigInfo(String configInfo) {
        // 配置变更回调,业务处理
        log.info("配置变更: {}", configInfo);
    }
});

Spring Cloud集成:

  • 添加@RefreshScope注解的Bean,配置变更时自动重新创建。
  • 使用@Value("${xxx}")注入的配置,变更后自动更新。
  • 自定义配置类配合@ConfigurationProperties,实现复杂配置的热更新。

多环境管理:

  • 通过Namespace隔离环境(dev/test/prod)。
  • 通过Group隔离项目或模块。
  • 配置优先级:Profile > Namespace > Group > DataId。

生产环境配置:

  • Nacos集群部署,至少3节点,保证高可用。
  • 数据库持久化配置,Nacos宕机后配置不丢失。
  • 配置变更触发灰度发布,逐步推送到实例。
  • 敏感配置加密存储,使用Nacos的加密配置功能。

我们项目用Nacos管理200+配置项,数据库连接池、限流阈值、功能开关都能实时调整,避免了改配置重新部署的繁琐流程。


共 10 道面试题,覆盖:CAP理论、Redisson锁、TCC/Saga、Snowflake、缓存问题、Raft算法、XXL-Job分片、Sentinel限流、链路追踪、Nacos配置中心