本轮 Java分布式架构面试题库(带答案)
【面试题】1. 请解释CAP定理,并说明在分布式系统中为什么只能三选二,给出实际选型案例。
【标准答案】CAP定理指出分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance),最多同时满足两个。因为网络分区是客观存在的——网络一定可能断,所以P必须选。剩下的就是CP和AP的选择。选CP就是分区时牺牲可用性保一致性,典型如Zookeeper、Etcd、HBase;选AP就是分区时牺牲一致性保可用性,典型如Cassandra、Eureka、DynamoDB。面试时要强调一点:CAP不是非此即彼的绝对选择,比如Zookeeper在正常情况下是CA都能满足的,只在分区时才做取舍。另外CAP的一致性是线性一致性,比较强,实际中很多场景用最终一致性就够了,这就引出了BASE理论。
【面试题】2. Redisson实现分布式锁的原理是什么?相比手写SET NX EX有什么优势?
【标准答案】手写SET NX EX的核心问题是锁续期和锁误删。如果业务没执行完锁就过期了,别的线程拿到了锁,然后原线程执行完把别人的锁删了,这就出问题了。Redisson的看门狗(Watchdog)机制就是解决续期问题的——默认锁30秒,每10秒(1/3超时时间)自动续期,业务没完成就一直在续,业务完成就停止续期,锁自然过期。加锁用的是Lua脚本保证原子性,锁的value是uuid+threadId,解锁时校验是不是自己的锁再删,避免误删。另外Redisson还支持可重入锁,内部用Hash结构记录重入次数;支持读写锁、公平锁、红锁(Redlock)等多种锁类型。手写方案在简单场景能跑,但续期、可重入、误删这几个坑基本都得踩一遍,线上还是推荐Redisson。
【面试题】3. TCC模式和Saga模式的区别是什么?各适用于什么场景?
【标准答案】TCC是业务层面的两阶段提交:Try阶段预留资源(冻结金额)、Confirm阶段确认提交(扣减冻结金额)、Cancel阶段回滚(解冻金额)。每个阶段都是业务代码,不是数据库事务。Saga是长事务拆成多个本地事务串联执行,每个本地事务有对应的补偿操作,失败时反向执行补偿。核心区别:TCC是资源预留制,Try阶段就占住了资源,隔离性好但锁定时间长;Saga没有预留,直接提交,失败再补偿,吞吐高但没有隔离性,可能出现脏读。选型上:TCC适合资金、库存等对一致性要求高、事务较短的场景;Saga适合业务流程长、参与者多的场景,比如订单→支付→物流→通知这种跨服务编排。实际中Seata框架同时支持这两种模式,TCC模式需要业务写Try/Confirm/Cancel三个方法,Saga模式可以用状态机或注解编排。
【面试题】4. 雪花算法生成分布式ID的原理和可能的问题是什么?如何解决时钟回拨?
【标准答案】Snowflake生成64位Long型ID:1位符号位+41位时间戳+10位机器ID+12位序列号。41位时间戳可用约69年,10位机器ID支持1024个节点,12位序列号每毫秒可生成4096个ID。主要问题就是时钟回拨——NTP同步或人工调整导致系统时间往回跳,可能生成重复ID。解决方案:①回拨小于5ms时,用等待策略,等时间追上再生成;②回拨5ms~500ms时,用备用workerId方案,切换到另一个机器ID;③回拨超过阈值,直接报错拒绝服务;④百度UidGenerator用Future模式预生成一批ID放在环形缓冲区,回拨时从缓冲区取;⑤Leaf的号段模式完全不依赖时间戳,天然免疫时钟回拨。面试时要说清楚:时钟回拨是Snowflake最核心的工程问题,不是理论问题,生产环境必须有应对策略。
【面试题】5. 缓存穿透、缓存击穿、缓存雪崩分别是什么?各自的解决方案?
【标准答案】穿透:请求的数据在缓存和数据库中都不存在,每次请求都打到数据库。解决方案:①布隆过滤器拦截不存在的key;②缓存空值(设短过期时间);③接口层参数校验。击穿:某个热点key过期的瞬间,大量并发请求同时打到数据库。解决方案:①互斥锁(只放一个请求去查库,其他等待);②逻辑过期(不设TTL,数据里放逻辑过期时间,过期后异步更新,其他线程返旧值);③热点key永不过期+异步刷新。雪崩:大量key同时过期或Redis宕机,请求全打到数据库。解决方案:①过期时间加随机值打散;②多级缓存(本地缓存+分布式缓存);③Redis集群高可用(哨兵/Cluster);④限流降级兜底;⑤缓存预热,系统启动时提前加载热点数据。这三者面试必考,核心就是区分"不存在""热点过期""大面积过期"三个场景的对应方案。
【面试题】6. Raft算法的Leader选举过程是怎样的?什么情况下会触发重新选举?
【标准答案】Raft中节点有三种状态:Follower、Candidate、Leader。正常情况下只有Leader处理写请求,Follower接收Leader的心跳。选举过程:①Follower在选举超时(150-300ms随机)内没收到Leader心跳,转为Candidate;②Candidate自增当前任期(term),投自己一票,向其他节点发RequestVote RPC;③收到多数节点投票的Candidate成为新Leader,立即发送心跳确立权威;④如果同一轮没有Candidate获得多数票(分票),各节点超时后重新选举,随机超时时间降低再次分票概率。触发重新选举的场景:①Leader宕机,Follower收不到心跳;②网络分区导致Leader隔离,少数派分区超时选举;③Leader自身崩溃重启。关键点:Raft的任期机制保证同一任期最多一个Leader,旧Leader恢复后发现自己的term过期会自动降为Follower。面试时要强调随机超时时间这是Raft比Paxos工程化程度高的体现。
【面试题】7. 分布式Session方案有哪些?JWT方案有什么局限性?
【标准答案】主流方案:①Session复制:Tomcat集群间同步Session,延迟大、浪费内存,基本淘汰;②Session粘性:Nginx按IP Hash固定到同一台服务器,宕机就丢Session;③Session集中存储:Redis/Memcached统一存Session,应用无状态,目前最主流;④JWT无状态方案:Token自带用户信息,服务端不存储,天然支持水平扩展。JWT的局限性:①无法主动失效——Token签发后在过期前一直有效,踢人、改密码场景难处理;②Token较大——Payload带信息,每次请求都传,带宽开销大;③安全风险——Token泄露无法即时撤销;④无法踢人——用户登出后Token仍可用直到过期。解决方案:JWT+Redis黑名单(登出时把Token加入黑名单)、缩短Token有效期+RefreshToken机制、敏感操作二次验证。实际项目中,Session集中存储(Redis)是最稳妥的方案,JWT适合对无状态要求高的移动端和微服务网关场景。
【面试题】8. Sentinel限流和Guava RateLimiter的区别是什么?集群限流怎么实现?
【标准答案】Guava RateLimiter是单机令牌桶限流,只能限制单个JVM的QPS,适用于单机场景,实现简单,基于SmoothBursty算法支持预存令牌应对突发流量。Sentinel是阿里开源的流量治理组件,支持单机和集群限流,限流规则可动态配置,还支持熔断降级、系统保护、热点参数限流等。集群限流的核心:需要一个Token Server作为独立的限流服务端,所有客户端节点向Token Server申请令牌,Token Server统一计数判断是否放行。Sentinel集群限流有两种模式:①独立模式——Token Server独立部署,专门做限流;②嵌入模式——某个应用节点同时作为Token Server。集群限流的挑战:Token Server单点问题(需要自动故障转移)、网络延迟、Server宕机时的降级策略(可退化到单机限流)。面试时要说清:单机限流是基础,集群限流是进阶,Sentinel把两者统一在一个框架里,配置热更新是它相比Guava最大的优势。
【面试题】9. Zookeeper的Watch机制有什么注意事项?为什么说它是轻量级通知?
【标准答案】Watch是一次性的——触发一次就失效,客户端收到通知后必须重新注册。这是设计上的选择:如果Watch是持久的,服务端要维护大量Watch关系,内存压力极大。一次性Watch虽然增加了客户端重注册的开销,但服务端压力小。注意事项:①Watch是一次性的,处理完事件后必须重新注册;②Watch是异步通知的,客户端收到通知时数据可能已经变了(不是强一致快照);③NodeCreated/NodeDeleted/NodeDataChanged/NodeChildrenChanged四种事件类型,监听子节点变化不会监听数据变化;④大量Watch同时触发会产生"羊群效应",比如大量客户端监听同一个节点,节点变化时所有客户端同时收到通知同时重连。解决方案:用临时顺序节点代替直接Watch,客户端只Watch自己前面那个节点。面试时要强调"轻量级"体现在:服务端只通知"发生了什么事件",不传变化后的数据,客户端需要自己再去读,这样通知数据量小、网络开销低。
【面试题】10. XXL-Job的架构是怎样的?任务分片是怎么实现的?
【标准答案】XXL-Job采用调度中心+执行器架构。调度中心(xxl-job-admin)负责任务管理、调度触发、日志监控,集群部署时通过数据库行锁保证同一时刻只有一个节点触发调度。执行器(嵌入在业务应用中)负责任务执行,通过Netty与调度中心通信,支持自动注册发现。任务分片实现:调度中心在触发任务时生成分片参数(分片总数+分片序号),每个执行器实例收到相同的分片总数和不同的分片序号(0, 1, 2...),业务代码根据分片序号决定处理哪部分数据。比如3个执行器处理100万条数据,分片总数=3,执行器0处理id%3==0的数据,执行器1处理id%3==1的数据,执行器2处理id%3==2的数据。分片参数由调度中心动态分配,执行器扩容后下一轮调度自动调整分片。关键点:分片逻辑在业务代码里实现,XXL-Job只负责分发分片参数,不做数据分片本身。调度中心的高可用依赖数据库行锁(SELECT FOR UPDATE),保证调度不重复不遗漏。