本轮 Java分布式架构面试题库(带答案)
【面试题】1. 拜占庭将军问题是什么?和FLP不可能定理分别解决了分布式系统中的什么问题?
【标准答案】拜占庭将军问题研究的是分布式系统中存在"叛徒"节点的情况——部分节点可能发送错误甚至恶意信息,导致诚实节点无法达成一致。它要解决的是"容错共识"问题,即在有恶意节点时如何达成正确决策。Lamport证明了3f+1个节点才能容忍f个叛徒节点。而FLP不可能定理说的是另一个层面:在异步系统中,哪怕只有一个节点可能崩溃(不是恶意),也不存在一个确定性算法能保证在有限时间内达成共识。简单说,拜占庭问题关注"有叛徒怎么办",FLP关注"网络延迟不可控怎么办"。实际工程中,Paxos/Raft这类算法都绕过了FLP——它们引入了随机超时和多数派机制,用"概率性活锁"换"确定性安全",虽然理论上可能一直选不出Leader,但实际中几乎不会发生。面试时重点说清两点:拜占庭是容恶意,FLP是容崩溃,工程上的做法都是"理论不完美但实际够用"。
【面试题】2. Redlock算法的完整流程是什么?为什么Martin Kleppmann认为它不安全?你怎么看?
【标准答案】Redlock是Redis作者antirez提出的多节点分布式锁算法。流程:客户端向N个独立Redis实例(通常5个)同时请求加锁,用相同的key和随机值,每个实例用SET NX EX加锁;客户端计算成功获取锁的实例数,如果>=N/2+1且总耗时没超过锁过期时间,则认为加锁成功;否则向所有实例释放锁。释放锁时用Lua脚本校验随机值匹配才删除。Martin Kleppmann的质疑主要有三点:一是时钟跳跃问题——Redlock依赖过期时间判断,如果某个Redis实例发生GC停顿或时钟跳变,可能导致锁被误判为过期;二是进程暂停问题——持有锁的客户端发生GC停顿,醒来后锁已过期被别人持有,造成两个客户端同时持锁;三是建议用fencing token(单调递增的令牌)替代时间判断。我的看法是:Redlock对大多数业务场景够用,但如果你需要严格互斥(如金融扣款),确实应该用Zookeeper或基于fencing token的方案。面试时说清楚"Redlock适合效率型锁(防重复计算),不适合正确性锁(防数据损坏)"这个区分就够了。
【面试题】3. 2PC的协调者单点问题怎么解决?为什么3PC在工程中几乎没人用?
【标准答案】2PC的协调者是个单点——如果协调者在第二阶段(commit/abort)发出部分指令后就宕机了,参与者会一直阻塞等待,无法自行决定提交还是回滚。解决思路有几层:一是协调者日志持久化+恢复机制,重启后根据日志继续未完成的事务;二是引入备份协调者,主协调者宕机后备节点接管,但需要同步事务日志;三是超时自动abort,参与者等不到指令就回滚,但这可能导致数据不一致。3PC把2PC的第二阶段拆成了PreCommit和DoCommit两步,增加了"超时提交"机制——参与者收到PreCommit后如果等不到DoCommit,超时后自动提交。理论上减少了阻塞,但实际中3PC的问题更多:网络分区时超时自动提交可能导致数据不一致;多一轮交互增加延迟;而且3PC并没有真正解决2PC的核心问题——阻塞只是概率降低,没有消除。工程界的选择很务实:需要强一致性用Paxos/Raft底层的分布式共识,需要业务柔性用TCC/Saga,2PC只在数据库XA事务中偶尔出现,3PC基本是教科书里的概念。
【面试题】4. 本地消息表方案的核心流程是什么?和最大努力通知有什么区别?
【标准答案】本地消息表的核心思路是"把分布式事务拆成本地事务+消息投递"。流程:业务操作和写消息表在同一个本地事务中提交(保证原子性);后台定时任务扫描消息表中状态为"待发送"的消息,投递到MQ;消费者消费消息执行远程操作,成功后回调确认;定时任务对超时未确认的消息重新投递,直到成功或超过最大重试次数。关键设计点:消息必须幂等(消费者需做幂等校验),重试要有退避策略,消息表要定期清理已完成的记录。最大努力通知是另一种模式,区别在于:本地消息表是"发起方保证投递成功",消费者确认后才算完;最大努力通知是"发起方尽力通知,不保证一定通知到",接收方需要主动查询来兜底。典型场景:支付结果通知——支付平台通知商户支付成功,通知几次后不再通知,商户需要主动调用查询接口确认状态。面试时一句话总结:本地消息表=可靠投递+确认机制,最大努力通知=尽力而为+主动查询兜底,前者追求最终一致性,后者接受有限一致性。
【面试题】5. Redis Cluster做故障转移时,从节点晋升为新主节点的完整流程是什么?集群什么时候会下线?
【标准答案】Redis Cluster故障转移流程分四步:第一,故障检测——某个主节点A宕机后,其他主节点通过Gossip协议发现A的PFAIL(疑似故障)标记,当超过半数主节点标记A为PFAIL,A的状态升级为FAIL(确认故障)。第二,从节点竞争——A的从节点发现主节点FAIL后,开始竞选,每个从节点计算自己的复制偏移量(replication offset),偏移量最大的从节点优先级最高(数据最新)。第三,选举投票——优先级最高的从节点向其他主节点请求投票,获得过半主节点同意后晋升为新主节点。第四,通知集群——新主节点通过Gossip广播自己晋升的消息,其他节点更新路由表。如果A没有从节点,那么A负责的槽位无人服务,集群进入下线状态(CLUSTER_DOWN),此时所有请求到这些槽的命令都会返回错误。集群下线的另一个条件是:任意一个槽没有主节点负责。所以生产环境一定要给每个主节点配从节点,且至少6个节点(3主3从)才能保证基本高可用。
【面试题】6. 数据库分布式锁用唯一索引和悲观锁分别怎么实现?各有什么局限?
【标准答案】唯一索引方案:创建一张锁表,加锁时INSERT一条记录(lock_name, holder_id, expire_time),利用唯一索引约束保证同一把锁只能被一个客户端插入成功;释放锁时DELETE这条记录。优点是实现简单,依赖数据库已有的ACID保证;局限也很明显——锁没有自动过期机制(需要额外定时清理过期记录),数据库是单点(主库挂了锁服务不可用),性能差(每次加锁都要走磁盘IO),不支持可重入。悲观锁方案:用SELECT ... FOR UPDATE加行锁,同一时刻只有一个事务能获取到行锁,其他事务阻塞等待。优点是数据库原生支持,锁的互斥性有保障;局限是依然依赖数据库可用性,FOR UPDATE会占用数据库连接资源,高并发下连接池容易耗尽,而且长事务持有锁的时间不可控可能导致死锁。两者共同的硬伤:数据库不是为分布式锁设计的,性能和可用性都不如Redis/Zookeeper方案。实际选型中,数据库锁只在"没有引入Redis/ZK、并发量很低、对性能不敏感"的场景下使用,比如后台管理系统的定时任务防重。
【面试题】7. NWR协议是什么?如何通过调整N、W、R来控制一致性级别?
【标准答案】NWR是Amazon Dynamo论文提出的可调一致性模型。N=数据副本数,W=写操作成功需要的确认数,R=读操作需要的确认数。核心规则:当W+R>N时,读和写的法定人数(Quorum)有交集,能保证读到最新写入的数据(强一致性);当W+R<=N时,可能读到旧数据(最终一致性)。举个例子:N=3(3副本),如果设W=2、R=2,则W+R=4>3,保证强一致性;如果设W=1、R=1,则W+R=2<=3,只能保证最终一致性,但可用性更高(任一副本可写、可读)。工程中的经典配置:N=3, W=2, R=2——兼顾一致性和可用性,Cassandra和Riak都支持这种配置。调参策略:要强一致性就提高W和R(但延迟增加、可用性降低),要高可用就降低W和R(但可能读到旧数据)。面试时可以对比一下:NWR和CAP的关系——W+R>N就是偏向CP,W+R<=N就是偏向AP。另外要提一嘴:NWR保证的是"读到你刚写的",但不等于线性一致性,因为并发写入的顺序无法保证。
【面试题】8. Elasticsearch的分布式架构是怎么设计的?写入一个文档的完整流程是什么?
【标准答案】Elasticsearch是典型的主从分片架构。一个索引被拆成多个主分片(Primary Shard),每个主分片有0到多个副本分片(Replica Shard)。分片路由公式:shard = hash(routing) % number_of_primary_shards,默认routing是文档ID。写入流程:客户端发送写请求到任意节点(协调节点Coordinating Node),协调节点根据routing计算目标分片,将请求转发到该分片的主节点;主分片执行写入操作(先写内存Buffer和Translog,refresh生成Segment后可搜索),写入成功后并行转发到所有副本分片;所有副本确认后,主分片向协调节点返回成功。几个关键点:主分片数在索引创建后不可修改(因为路由公式依赖分片数),所以要提前规划容量;副本分片数可以动态调整;refresh间隔默认1秒,所以写入后最多1秒才能搜索到;Translog保证写入不丢失,宕机后可以重放。面试时如果能说清"主分片决定数据分布和容量上限,副本分片决定读吞吐和可用性"这个设计思路,就很加分。
【面试题】9. Etcd和Zookeeper在架构设计上有什么核心差异?分别适合什么场景?
【标准答案】核心差异有四点:第一,一致性算法——ZK用ZAB协议,Etcd用Raft算法。ZAB有专门的崩溃恢复和消息广播两种模式,Raft更简洁,只有Leader处理写请求、日志复制+心跳。第二,数据模型——ZK是树形ZNode,每个节点可以存1MB数据,适合层次化的配置和命名;Etcd是扁平KV,用前缀模拟目录结构,value支持几MB,更适合配置和服务发现。第三,Watch机制——ZK的Watch是一次性的,触发后需要重新注册,大范围监听容易丢事件;Etcd基于HTTP/gRPC长轮询+MVCC版本号,支持从某个revision开始监听,不会丢历史变更。第四,运维和生态——ZK是Java写的,依赖JVM调优;Etcd是Go写的,单二进制部署,Kubernetes原生依赖Etcd。选型建议:如果是Hadoop/Kafka/HBase生态,ZK是标配;如果是Kubernetes/云原生体系,Etcd是标配;如果从零开始做配置中心或服务发现,Etcd更轻量更好运维。面试时一句话总结:ZK是"老牌贵族"功能全但笨重,Etcd是"云原生前锋"精简但够用,Kubernetes选Etcd,Hadoop选ZK。
【面试题】10. 服务网格Istio的Sidecar模式是怎么工作的?和传统的微服务框架(如Spring Cloud)有什么本质区别?
【标准答案】Sidecar模式的核心思路是"把服务治理逻辑从业务代码中剥离出来"。Istio在每个Pod中自动注入一个Envoy代理(Sidecar),拦截该Pod所有的入站和出站流量。流量路由、负载均衡、熔断限流、安全认证、链路追踪这些治理能力全部在Envoy层完成,业务代码完全无感知。和Spring Cloud的本质区别在于"治理能力的下沉方式":Spring Cloud是代码级集成,业务代码要引入各种Starter依赖(Eureka/Ribbon/Hystrix/Sleuth等),治理逻辑和业务逻辑耦合在同一个进程里,而且绑定了Java生态;Istio是基础设施级集成,治理能力在Sidecar代理层实现,业务代码零侵入,任何语言写的服务都能接入。代价也很明显:Sidecar会带来额外的网络跳转(业务→Sidecar→网络→Sidecar→业务),延迟增加约1-3ms;每个Pod多一个Envoy容器,资源开销增加;调试和排错更复杂(流量被代理截获)。面试时说清一个核心观点:Spring Cloud是"把治理写进代码",Istio是"把治理下沉到基础设施",前者简单但绑语言,后者解耦但开销大。当前趋势是两者融合——MCP协议就是让AI Agent也能接入服务网格。