Java分布式架构面试题库
生成时间:2026-05-24 21:57
【面试题】1. CAP定理中为什么只能三选二?在实际系统中如何权衡?
【标准答案】 CAP定理的核心是:一个分布式系统不可能同时满足一致性(C)、可用性(A)、分区容错性(P)。
为什么只能三选二?因为网络分区是客观存在的,P无法避免。当分区发生时,系统必须在C和A之间做选择——要么等待分区恢复保证一致性但牺牲可用性,要么继续响应请求保证可用性但可能返回旧数据。
实际权衡策略:
- CP系统(如Zookeeper、HBase):强一致性优先,网络分区时部分节点不可用。适合金融转账、库存扣减等对一致性要求极高的场景。
- AP系统(如Cassandra、Eureka):可用性优先,分区时继续服务但可能读到旧数据。适合社交动态、商品展示等容忍短暂不一致的场景。
- 注意:实际上很多系统在不同层面做取舍,比如Redis Cluster是AP但支持异步复制的一致性读,MySQL主从是AP但可以通过半同步提升一致性。
面试加分点:提到"实际上没有真正的CA系统,因为P是分布式系统固有的"。
【面试题】2. Redis分布式锁如何解决锁超时导致的安全问题?
【标准答案】 Redis分布式锁的核心问题是:如果持有锁的客户端执行时间超过锁的过期时间,锁会自动释放,其他客户端获得锁后可能导致并发问题。
解决方案:
锁续期(看门狗机制):
- Redisson实现的Watchdog,后台线程定期续期(默认每10秒续期到30秒)
- 业务没执行完锁不会过期,执行完自动停止续期
合理设置超时时间:
- 根据业务最长执行时间设置,留出buffer
- 比如业务最大执行5秒,锁设置10秒
Redlock算法(多节点高可用):
- 在多个Redis实例上加锁,大多数成功才算成功
- 防止单节点故障导致锁失效
锁释放校验:
- 释放锁时检查value是否是自己设置的(Lua脚本原子操作)
- 防止误删其他客户端的锁
实际落地:生产环境直接用Redisson,自带看门狗、可重入锁、读写锁,不用自己造轮子。
【面试题】3. TCC分布式事务模式下,Confirm阶段失败了怎么办?
【标准答案】 TCC(Try-Confirm-Cancel)是柔性事务方案,Confirm失败是常见问题。
失败处理机制:
重试机制:
- Confirm失败后,事务协调器不断重试(指数退避策略)
- 直到成功或达到最大重试次数
- 因为Confirm是确认操作,必须成功才能保证事务完整性
幂等性设计:
- Confirm操作必须幂等,多次执行结果相同
- 通过业务流水号去重,执行前先查状态
人工介入:
- 重试N次仍失败,转入人工处理队列
- 记录完整的事务日志,便于排查和补偿
定时任务扫描:
- 后台任务扫描长时间未完成的事务
- 触发重试或报警
关键点:Confirm没有Cancel,只能重试到底。这也是TCC的"刚性"体现——Try之后必须有个明确结果。
实际经验:Seata TCC模式下,Confirm失败会记录在事务日志表,后台有补偿线程自动重试。业务代码要确保Confirm幂等。
【面试题】4. Snowflake雪花算法时钟回拨问题怎么解决?
【标准答案】 Snowflake依赖服务器时间,时钟回拨会导致ID重复或时间倒退。
解决方案:
回拨检测+等待:
- 记录上次生成ID的时间戳
- 发现时间回拨,等待到时间追上
- 小回拨(比如几毫秒)可以等,大回拨需要其他策略
回拨拒绝:
- 回拨超过阈值(比如5ms),直接报错或返回备用方案
- 适合对连续性要求不高的场景
预留ID段:
- 百度UidGenerator方案:提前生成一批ID缓存
- 时钟回拨时使用缓存的ID
- 平时异步补充缓存
时钟回拨位:
- 美团Leaf方案:用部分位存储回拨次数
- 同一毫秒内回拨也能区分
NTP时钟同步优化:
- 禁用NTP主动调整,只做监控
- 发现时钟偏差报警,人工介入
生产建议:
- 物理机避免虚拟机时钟漂移
- 监控系统时间变化
- 使用UidGenerator或Leaf等成熟方案
【面试题】5. 缓存穿透、击穿、雪崩的区别和解决方案?
【标准答案】 这三个问题都是缓存失效导致的数据库压力问题,但场景不同。
缓存穿透:查询不存在的数据,缓存没命中,直接打到数据库。
- 解决方案:
- 布隆过滤器过滤不存在的key
- 空值也缓存,设置短过期时间(如5分钟)
- 参数校验拦截非法请求
缓存击穿:某个热点key过期瞬间,大量请求同时访问数据库。
- 解决方案:
- 热点key永不过期,异步更新
- 互斥锁:缓存失效时只允许一个线程查库重建缓存
- 提前续期:访问时延长过期时间
缓存雪崩:大量key同时过期,或Redis宕机,数据库压力骤增。
- 解决方案:
- 过期时间加随机值,避免同时失效
- 多级缓存:本地缓存+Redis
- Redis高可用:主从+哨兵+Cluster
- 限流降级:保护数据库
实际落地:
- 穿透:布隆过滤器+空值缓存
- 击穿:热点key预热+互斥锁
- 雪崩:随机过期+多级缓存+熔断
面试加分:提到"双十一热点商品预热到缓存,设置随机过期时间防止雪崩"。
【面试题】6. JWT无状态方案如何实现Token刷新和注销?
【标准答案】 JWT无状态的好处是不需要服务端存储,但也带来了刷新和注销难题。
Token刷新方案:
双Token机制:
- Access Token:有效期短(如2小时),用于访问
- Refresh Token:有效期长(如7天),用于刷新Access Token
- Refresh Token存储在Redis中,可主动失效
滑动刷新:
- 用户活跃时自动延长Token有效期
- 后台静默刷新,用户无感知
Token黑名单:
- 注销时将Token加入Redis黑名单
- 每次请求检查黑名单(牺牲部分性能)
注销方案:
Token黑名单:
- 注销时将未过期的Token加入黑名单
- 设置过期时间等于Token剩余有效期
- 访问时检查黑名单
版本号机制:
- Token中包含版本号字段
- 用户数据中维护当前版本号
- 注销时更新版本号,旧Token失效
Refresh Token撤销:
- 注销时删除Redis中的Refresh Token
- Access Token短时间后自然过期
实际选择:
- 安全要求高:黑名单+双Token
- 性能要求高:版本号机制+短过期时间
【面试题】7. 令牌桶和漏桶算法的区别?生产环境如何选择?
【标准答案】 两者都是限流算法,但特性和适用场景不同。
漏桶算法:
- 原理:请求先进队列,以固定速率流出处理
- 特点:流出速率恒定,能平滑突发流量
- 缺点:无法应对突发请求,队列满则丢弃
- 场景:保护数据库等处理能力固定的系统
令牌桶算法:
- 原理:以固定速率往桶里放令牌,请求拿令牌才能处理
- 特点:桶有容量,可以应对突发流量(桶内有令牌时)
- 优势:既有限流又有一定弹性
- 场景:API网关、业务限流
核心区别:
- 漏桶:强制恒定速率,突发流量直接丢弃
- 令牌桶:允许一定程度的突发,桶内令牌可瞬时使用
生产选择:
- API限流:令牌桶(如Guava RateLimiter、Redis+Lua实现)
- 数据库保护:漏桶(平滑请求,防止突发打挂)
- 分布式限流:Redis+Lua实现令牌桶,集群统一计数
实际落地:
// Guava RateLimiter(单机)
RateLimiter limiter = RateLimiter.create(100); // 100 QPS
if (limiter.tryAcquire()) {
// 处理请求
}面试加分:提到"Sentinel同时支持QPS限流(令牌桶)和并发线程数限流"。
【面试题】8. Raft算法中Leader选举的具体流程?
【标准答案】 Raft是强一致性算法,Leader选举是其核心机制。
选举流程:
初始状态:
- 所有节点都是Follower状态
- 每个节点有随机选举超时时间(150-300ms)
超时触发:
- Follower在选举超时时间内未收到Leader心跳
- 转为Candidate状态,任期号+1
发起投票:
- Candidate先给自己投票
- 向其他节点发送RequestVote RPC
投票判断:
- 收到投票请求的节点检查:
- 候选人任期 >= 自己的任期
- 候选人日志至少和自己一样新
- 满足条件则投票,每个任期只能投一次
- 收到投票请求的节点检查:
选举结果:
- 获得多数派投票(N/2+1)→ 成为Leader
- 收到更高任期的心跳 → 退回Follower
- 选举超时未获多数 → 重新选举
Leader上任:
- 立即发送心跳确认地位
- 后续所有请求通过Leader处理
关键点:
- 随机超时避免同时选举(脑裂)
- 多数派机制保证只有一个Leader
- 日志新旧判断防止数据丢失
实际应用:Etcd、Consul都用Raft,3节点集群容忍1节点故障。
【面试题】9. Zookeeper临时节点和持久节点的应用场景?
【标准答案】 Zookeeper节点类型决定了生命周期和用途。
持久节点(Persistent):
- 特点:创建后永久存在,直到主动删除
- 应用场景:
- 配置中心:存储配置信息
- 命名服务:服务注册信息
- 路由信息:存储路由规则
临时节点(Ephemeral):
- 特点:客户端会话结束自动删除
- 应用场景:
- 服务注册:服务下线自动注销
- 分布式锁:锁持有者宕机自动释放
- Leader选举:候选者创建临时节点,创建成功者为Leader
临时顺序节点:
- 特点:临时节点+自动递增序号
- 应用场景:
- 公平锁:序号最小的获得锁
- 分布式队列:按序号顺序处理
持久顺序节点:
- 特点:持久节点+自动递增序号
- 应用场景:
- 全局有序ID生成
实际案例:
// 服务注册(临时节点)
zk.create("/services/order-service/host:port", data,
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
// 分布式锁(临时顺序节点)
zk.create("/lock/order-", data,
ZooDefs.Ids.OPEN_ACL_UNsafe, CreateMode.EPHEMERAL_SEQUENTIAL);面试加分:提到"Curator框架封装了这些细节,Recipe包提供现成的分布式锁实现"。
【面试题】10. 分布式链路追踪中TraceId如何在整个调用链中传递?
【标准答案】 TraceId是链路追踪的核心,需要在整个调用链中透传。
传递机制:
RPC调用传递:
- Dubbo:RpcContext中设置attachment
- Feign:RequestInterceptor添加Header
- gRPC:Metadata中传递
HTTP调用传递:
- 放在HTTP Header中:
X-Trace-Id: xxx - 下游服务从Header提取并继续传递
- 放在HTTP Header中:
异步调用传递:
- 线程池:使用装饰过的Runnable/Callable,在任务创建时捕获上下文
- 消息队列:消息Header中携带TraceId
- Spring的@Async:TaskDecorator自动传递
跨服务框架:
- Spring Cloud Sleuth:自动注入TraceId到Header
- Micrometer Tracing:统一抽象,适配多种Tracing后端
- SkyWalking Agent:字节码增强,无侵入传递
技术实现:
// 主线程
String traceId = TraceContext.traceId();
MDC.put("traceId", traceId);
// 跨线程传递(使用TransmittableThreadLocal)
ExecutorService executor = TtlExecutors.getTtlExecutorService(rawExecutor);
// RPC传递
RpcContext.getContext().setAttachment("traceId", traceId);实际方案:
- 微服务:Spring Cloud Sleuth + Zipkin
- 性能要求高:SkyWalking Agent(无侵入,性能损耗<5%)
- 自研框架:TransmittableThreadLocal + Header传递
面试加分:提到"异步场景用阿里TTL库,解决InheritableThreadLocal线程池复用问题"。
本次生成题目覆盖:
- 理论基础:CAP定理
- 分布式锁:Redis锁续期
- 分布式事务:TCC Confirm失败处理
- 分布式ID:Snowflake时钟回拨
- 缓存:穿透/击穿/雪崩
- 分布式会话:JWT刷新注销
- 限流:令牌桶vs漏桶
- 一致性算法:Raft选举
- 协调服务:Zookeeper节点类型
- 链路追踪:TraceId传递
共计10题,覆盖10个核心考点