看到“牛客面经八股”这几个字,很多准备校招的朋友都会会心一笑:分布式,几乎是大厂技术栈的必考章节,也是很多人背得最痛苦的部分。你背了CAP、背了两阶段提交、背了Redis分布式锁,但面试官往往不止问“是什么”,还喜欢追问“为什么”和“还有没有更好的方案”。今天这篇,我想以面经题为线索,把分布式的核心考点串一遍,里面会揉进我实际做项目、刷题和面试时积累的经验,希望能帮你把散落在牛客讨论区里的零散八股,变成一套能说清楚、能抗追问的完整知识体系。
我见过太多人拿着厚厚的一摞八股文档,从“分布式事务”背到“分布式锁”,看起来什么都知道,可一被追问就卡壳。问题不在于背得不够多,而在于没有把这些点连成一张网。分布式不是一堆孤立的技术名词,而是一整套应对“系统变大之后复杂度上升”的思考方式。所以这篇不止是罗列知识点,更会讲清楚每个方案背后的取舍,以及面试回答时怎么讲才有层次。
1. 面经里的分布式,到底在考你什么
1.1 为什么分布式成了必考模块
早年很多公司的技术栈都是单体应用,一个工程打包部署,数据库挂在下面,用户量不大时完全够用。但随着业务增长,单机服务器的CPU、内存、磁盘总有一个先到瓶颈;再往后,业务模块越来越多,团队几十上百人同时在一个工程里改代码,发布一次要协调所有人,风险极高。
于是大家开始把系统拆开:按业务边界拆成订单服务、库存服务、用户服务,每个服务独立部署、独立伸缩。这就是分布式系统最基本的形态。但拆分之后,原本在同一个进程里的方法调用,变成了跨服务的网络调用;原本由一个数据库事务搞定的一致性,变成了多个服务、多个数据库之间的一致性问题;原本用JVM锁就能实现的并发控制,变成了多节点之间的分布式锁问题。这些就是分布式架构的核心难点,也是面试官最想考察的地方。
分布式考点背后,其实是在考察候选人有没有能力处理“系统复杂度上升”这件事。校招虽然不会要求你有大规模生产环境的经验,但至少要知道:系统为什么会变复杂,复杂在哪,业界有哪些成熟的解法。所以分布式成为必考,不是因为“流行”,而是因为它直接反映候选人的系统设计思维。
1.2 高频考点全景图
把牛客上关于分布式的面经题汇总一下,其实高频考点非常集中,我整理了一个全景图,供大家自测:
| 考点领域 | 代表问题 | 重要程度 |
|---|---|---|
| 架构理论 | CAP、BASE、一致性哈希 | 必背 |
| 微服务组件 | 注册中心、网关、配置中心、熔断降级 | 必背 |
| 远程调用 | RPC原理、Feign/Dubbo、序列化 | 高频 |
| 分布式事务 | 2PC、3PC、TCC、可靠消息、Seata | 必背 |
| 分布式锁 | Redis锁、ZooKeeper锁、数据库锁 | 必背 |
| 分布式缓存 | 穿透、击穿、雪崩、缓存一致性 | 必背 |
| 分布式存储 | 分片、副本、分布式ID、一致性哈希 | 高频 |
| 分布式调度 | XXL-Job、ElasticJob、ShedLock | 高频 |
| 消息队列 | 削峰、异步解耦、最终一致性 | 高频 |
| 链路追踪 | 日志串联、TraceId、SkyWalking | 加分 |
从表格能看出来,分布式知识覆盖面很广,但真正的高频题目其实就集中在“架构理论 + 数据一致性 + 高可用方案”这三块。准备的时候不建议平均用力,优先把必背的搞透,再往加分项扩展。
1.3 面试官考察的核心能力
很多同学会把八股题当成“背诵题”来准备,但面试官真正想看的,是你面对一个具体问题时能不能做出合理的技术选型。比如同样是“分布式锁”这道题,初级回答是背出SET NX EX命令;中级回答是能说出Redisson看门狗、可重入锁;高级回答是能对比Redis和ZooKeeper锁的优劣,并结合业务场景说清楚为什么选Redis而不是ZK,以及主从切换时锁可能丢失怎么兜底。
从初级到高级的差别,不在于知识点的数量,而在于有没有把“背答案”变成“做权衡”。分布式系统里几乎没有银弹,每个方案都有代价:追求强一致,可能要牺牲可用性;追求高性能,可能要先接受最终一致。面试官想听到的,是你能把这种权衡讲明白,而不是机械地罗列名词。
2. 分布式架构与微服务:从CAP到Spring Cloud
2.1 为什么需要从单体走向分布式
聊分布式之前,必须先理解单体架构的痛点。单体应用最直接的问题有两个:一是资源无法按需扩展,用户量上来之后,你只能把整个应用堆到更大的机器上,但可能真正吃性能的只有一两个模块;二是故障没有隔离,一个模块出现内存泄漏或者死循环,整个进程可能直接宕掉,所有业务一起受影响。
微服务架构把系统按业务拆分成多个服务,每个服务独立进程、独立部署、独立扩缩容。这样订单服务压力大就只扩容订单服务,库存服务出故障也只会影响库存相关的功能。但拆分是有代价的:服务之间从本地方法调用变成网络调用,网络就会带来延迟、超时、重试、幂等等一系列新问题;原本一个数据库事务能搞定的事情,现在要跨服务、跨库,所以分布式事务才成了必须讨论的话题。
我刚接触微服务时有个误区,觉得服务拆得越细越好。后来在一个项目里把一个模块拆成了七八个服务,结果一个简单的查询要串五六个服务,排查问题链路长得离谱。拆分的本质是“在合适的粒度上划清边界”,而不是追求数量。面试聊到微服务时,如果能主动说出“过度拆分也是一种坏味道”,会给面试官留下很深的印象。
2.2 CAP与BASE:分布式系统的底层坐标系
CAP理论几乎是分布式面试的第一个必考题。C是一致性,A是可用性,P是分区容错性。所谓分区容错,指的是分布式系统里网络节点之间可能出现消息丢失或延迟,这个情况叫“网络分区”。在一个分布式系统里,网络分区是不可避免的,所以P必须满足,剩下的C和A只能二选一。
可以这样理解:两台机器之间的网线断了,这时候你收到一个读请求。如果选择一致性,你需要让请求失败,告诉调用方“我现在无法保证数据是最新的”,这就是CP,比如ZooKeeper;如果选择可用性,你会继续返回数据,哪怕可能是旧数据,等网络恢复后再同步,这就是AP,比如Eureka。实际的分布式系统,尤其是互联网业务,往往更偏向AP和最终一致性,因为用户不能接受“请稍后再试”的概率太高。
BASE理论是CAP在工程实践的落地:基本可用、软状态、最终一致。基本可用指系统在异常时尽量保证核心功能可用,比如大促时降级非核心功能;软状态指允许系统存在中间状态;最终一致指经过一段时间,数据最终会达成一致。缓存和数据库之间的异步同步、消息队列的异步处理,本质上都是BASE思想。
2.3 RPC与注册中心:服务之间怎么互相找到
分布式服务之间需要通信,最简单的方式是HTTP接口,但HTTP在性能、传输效率和接口语义上不够直接,所以企业里更多用RPC框架,比如Dubbo、gRPC、Spring Cloud中的Feign。RPC的过程可以简化成:客户端本地调用一个代理对象,代理把方法名、参数序列化成字节流,通过网络发送给服务端,服务端反序列化后执行真实方法,再把结果返回给客户端。对业务代码来说,看起来就像调用本地方法一样。
但服务有多个实例,客户端怎么知道该调用哪个IP和端口?这就轮到注册中心登场。服务提供方启动时往注册中心注册自己的地址,服务消费方启动时从注册中心拉取服务列表,再通过负载均衡策略选一个实例发起调用。常见的注册中心有Eureka、ZooKeeper、Nacos、Consul,它们的区别本质上还是落回CAP:Eureka偏向AP,ZooKeeper偏向CP,Nacos支持切换模式。
面试时如果被问到“服务调用失败怎么办”,不能只说“重试”。要考虑幂等性:如果下游处理成功但响应超时,重试会带来重复操作,所以接口设计里通常需要幂等键;同时还要考虑重试次数过多导致的雪崩,所以需要熔断和限流。
2.4 Spring Cloud微服务架构的一次请求链路
Spring Cloud是目前微服务生态里最常被问到的技术栈。一个典型的请求链路大概是:外部请求先到API网关,网关做统一鉴权、限流、路由;然后转发到具体业务服务,业务服务通过Feign或RestTemplate调用其他服务;调用过程从注册中心拉取服务列表,配置从配置中心拉取,链路信息通过SkyWalking或Zipkin收集;如果某个下游服务开始异常,熔断器会快速失败而不是一直等待;如果流量过大,还会触发限流和降级。
面经里常见的“Spring Cloud分布式架构设计”问题,其实就是要你把这个链路讲清楚。推荐画一张示意图:网关在最外层,下面是各业务服务,右侧是注册中心、配置中心、熔断器、链路追踪等基础设施。然后面试官问哪一个组件,你就往深聊哪一个。比如问“服务间负载均衡怎么做”,就聊Ribbon的负载均衡策略;问“配置动态刷新怎么做”,就聊Spring Cloud Config或Nacos的配置监听。
还有一点容易被忽略:微服务不是把服务拆完就结束,还需要配套的监控、日志、告警。没有链路追踪,一个跨五个服务的请求出现问题,你连日志都串不起来。所以面试时说微服务,最好把“可观测性”也带上,这是区分有没有真实落地经验的关键。
2.5 分布式定时任务的架构方案
单机架构下,定时任务用Spring @Scheduled就能解决。但一旦服务部署多个实例,@Scheduled的同一任务会在每个实例上同时执行,产生重复处理。比如定时给用户发送通知,本来想发一次,结果发了好几次。
最简单的方案是“分布式锁+定时任务”,每次任务执行前先尝试获取分布式锁,拿到锁的实例才执行,其他实例直接跳过。这种方案优点是轻量,缺点是任务路由不灵活,无法做到分片。复杂一点的方案是引入分布式任务调度平台,比如XXL-Job、ElasticJob,部署独立的调度中心,执行器注册上来,调度中心负责任务触发,执行器负责任务执行,支持分片广播、失败重试、动态调整任务参数。
在Spring Cloud环境下,比较常见的做法是用ShedLock配合Redis实现轻量级分布式任务锁,或者直接上XXL-Job。面试时建议把两种方案都讲出来,说明各自适用场景,而不是一上来就说“我们用XXL-Job”,显得没有思考过程。
3. 分布式事务:别只会背2PC,Seata原理才是加分项
3.1 典型场景:下单扣库存
分布式事务最常见的面试场景是“订单与库存”。用户下单,订单服务创建一条订单数据,库存服务扣减一个库存,这两个操作发生在不同的服务、不同的数据库里,本地事务根本管不了。如果订单创建成功、库存扣减失败,就会出现超卖;如果库存扣减成功、订单创建失败,就会出现库存被扣但用户没买到东西。
这时候需要分布式事务来保证多个服务之间的数据一致性。但分布式事务的成本很高,不能所有业务都无脑上。面试时会先问场景,再问你选什么方案,所以要把每个方案的适用场景和优缺点梳理清楚。
3.2 分布式事务方案全景:2PC、3PC、TCC、可靠消息、最大努力通知
先全景式看一下常用方案:
| 方案 | 一致性强度 | 业务侵入 | 适用场景 |
|---|---|---|---|
| 2PC(两阶段提交) | 强一致 | 低 | 单体数据库跨库、传统X/Open分布式事务 |
| 3PC(三阶段提交) | 强一致,减少阻塞 | 低 | 对2PC改进,工程落地少 |
| TCC(Try/Confirm/Cancel) | 强一致 | 高 | 金融、账务类,业务可定义补偿逻辑 |
| 可靠消息最终一致性 | 最终一致 | 中 | 高并发、允许短暂不一致的业务 |
| 最大努力通知 | 最终一致 | 低 | 跨平台支付回调、第三方结果通知 |
2PC是最经典的方案,分为准备阶段和提交阶段。协调者先问所有参与者“能不能提交”,所有人都说可以,才进入提交阶段;只要有一个参与者说不能,就全部回滚。缺点也很明显:所有参与者要一直持有数据库资源等协调者命令,容易出现同步阻塞,协调者单点故障还会导致阻塞更久。3PC改进了超时机制,但实际落地并不多。
TCC更像一种业务层面的分布式事务方案,把每个操作拆成Try(预留资源)、Confirm(确认执行)、Cancel(回滚释放)。比如订单服务在Try阶段创建待支付订单,库存服务在Try阶段冻结库存;Confirm阶段真正扣减库存、订单生效;Cancel阶段释放冻结库存、订单关闭。TCC的优点是性能好、能做到强一致,缺点是对业务侵入非常大,每个操作都要写三套逻辑。
可靠消息最终一致性则是通过消息队列实现:把本地业务操作和消息发送放在同一个本地事务里,消息发送成功后再由消费者消费,消费者处理成功后再给生产者确认。这个过程允许数据短暂不一致,但最终能对账成功。最大努力通知适合对接外部系统,比如支付结果通知,支付平台会反复回调,直到收到成功应答或达到重试上限。
3.3 Seata AT模式的核心原理
Seata几乎是现在分布式事务面试的顶流话题。它的AT模式在原理上很像2PC,但做得更自动化。Seata有三大角色:TC(事务协调者)、TM(事务管理器)、RM(资源管理器)。TM向TC申请开启全局事务,RM负责本地事务的提交和回滚,TC负责全局事务的协调。
AT模式的核心在于“快照+undo log”。业务SQL执行前,RM会先保存当前数据的快照,然后执行业务SQL,把业务数据和undo log写入同一本地事务,并注册分支事务到TC;如果全局事务提交,就删除undo log;如果全局事务回滚,就根据undo log反向生成补偿SQL,把数据恢复原样。
相比TCC,AT模式对业务代码侵入很小,不需要写Confirm和Cancel。但它也不是万能的:因为要使用全局锁来保证并发事务之间不会互相干扰,性能会有一定损耗。面试时可以说清楚AT模式是把2PC的“人工准备”变成了“自动快照”,用空间换开发效率,这比只会背“Seata是阿里的分布式事务框架”要有深度得多。
3.4 怎么把事务方案讲出层次感
回答分布式事务题目时,不要一上来就说“我们用Seata”。更推荐的顺序是:先描述业务场景的一致性是强一致还是最终一致;然后给出候选方案,并说出每个方案的代价;最后再结合场景选出最合适的。
比如面试官问“订单和库存怎么保证一致”,你可以这样回答:如果扣库存操作必须和订单创建保持强一致,可以选用Seata的AT模式,虽然性能略有损耗,但业务改动小。如果库存系统并发非常高、不希望被分布式事务拖慢,可以把“扣库存”改为异步消息或预占库存,接受短暂不一致,通过定时对账来兜底。这种回答方式会显得你是在做技术选型,而不是在背PPT。
4. 分布式锁:面试高频题型与Redisson落地
4.1 问题的起点:并发扣库存
分布式锁这道题几乎每次面试都会被问到。面试官常给的场景是:商品库存只有10件,用户同时发起大量请求,多个服务实例同时执行“判断库存>0,库存减一”的操作,怎么保证不超卖?
单机环境下可以用synchronized或JUC的Lock,但多个实例部署后,每个实例的JVM锁互相看不到,必须引入一个所有服务都能访问的“外部锁”。分布式锁的本质很简单:让多个进程通过一个共享的第三方组件来竞争同一个资源,谁抢到谁执行。
4.2 Redis分布式锁的正确姿势
最常见的实现是Redis分布式锁。最早有人用SETNX,后来发现需要同时设置过期时间,于是改用SET key value NX EX命令,一条命令完成加锁和设置超时。value要存一个唯一标识,比如UUID,释放锁时先比对value再删除,这是为了防止误删别人的锁。
释放锁这一步必须用Lua脚本保证原子性,不能先GET再DEL。因为如果某个线程的锁过期了,另一个线程加锁成功,第一个线程再来释放锁时,不加判断会把别人的锁给删掉。Lua脚本如下:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end还要注意锁的过期时间设置。过期时间太短,任务还没执行完锁就自动释放;太长,一旦持有锁的实例宕机,其他实例要等很久才能获取锁。所以能不能给锁自动续期,就成了一个很关键的面试点。
4.3 Redisson看门狗与可重入
Redisson封装了分布式锁的很多细节,面试时一定要会讲。Redisson的RLock是基于Redis Hash结构实现的,同一个线程可以多次加锁并释放,这就是可重入。更厉害的是看门狗机制:默认锁的leaseTime是30秒,如果持有锁的线程还在执行,看门狗会每10秒自动续期一次,把锁的时间拉回30秒,避免业务没执行完锁就过期。
但看门狗也不是万无一失。如果Redis主节点宕机,锁数据还没来得及同步到从节点,另一个实例可能会在同一把锁上加锁成功。Redis官方提出了RedLock算法,在多个独立Redis节点上同时加锁,超过半数成功才认为加锁成功。但RedLock本身也有争议,很多工程团队并不推荐,因为复杂度高且仍然存在时钟跳跃等问题。面试时能提到这个争议,会显得你真的思考过可靠性问题。
4.4 ZooKeeper分布式锁和数据库锁
除了Redis,ZooKeeper也常被用来实现分布式锁。方案是:在ZooKeeper的某个持久节点下创建临时顺序节点,如果自己创建的节点序号最小,就说明拿到锁;否则监听前一个节点,前一个节点被删除时再尝试获取锁。
ZooKeeper锁最大的优势是“临时节点+会话超时”机制:客户端崩掉,会话结束,临时节点自动删除,锁自然释放,不会像Redis那样存在锁超时没人续期的问题。而且ZooKeeper是CP模型,一致性更强。缺点是性能不如Redis,创建节点和监听事件都有额外开销,高并发场景下可能成为瓶颈。
数据库锁也有两招。悲观锁用SELECT ... FOR UPDATE,性能不高;乐观锁用版本号,更新时带上version条件,适合冲突较少的场景。但数据库锁本身不适合高并发分布式锁,面试中提一下即可,重点还是Redis和ZK的对比。
4.5 锁选型与避坑经验
实际项目里怎么选?我的经验是:如果追求高性能,并且能接受极端场景下锁丢失,用Redis+Redisson;如果业务对可靠性要求极高,比如不允许出现并发重复写入,用ZooKeeper锁。
避坑方面,第一,锁粒度要尽量小,能用订单维度就不要用商品维度,避免把不相关的操作互相阻塞;第二,锁内代码一定要快,不要在锁里面做远程调用和耗时IO,否则吞吐量会非常难看;第三,多个锁嵌套时要保证加锁顺序一致,否则会出现死锁;第四,释放锁必须放在finally里,并配合Lua脚本比对value。这些细节比单纯背命令更能体现你的工程素养。
5. 分布式缓存与存储:穿透、击穿、雪崩和集群架构
5.1 缓存三兄弟:穿透、击穿、雪崩
缓存是分布式系统里提升性能最直接的手段,但用不好会带来三个经典问题。
缓存穿透:查询一个根本不存在的数据,比如用一个不存在的用户ID,缓存里没有,请求直接打到数据库;如果被攻击者批量构造不存在的ID,数据库压力会非常大。解决方案:缓存空值但设置较短过期时间;更彻底的办法是用布隆过滤器,在请求缓存前先判断数据是否存在,不存在直接返回。
缓存击穿:某个热点key在缓存过期的瞬间,大量请求同时打到数据库。解决思路是“单点重建缓存”:用互斥锁让一个线程去查数据库并回填缓存,其他线程等待或先返回旧值;或者对热点key设置“逻辑过期”,缓存里存的是旧数据,后台异步刷新。
缓存雪崩:大量key在同一时间集中过期,或者Redis实例宕机,导致所有请求涌向数据库。解决方法是给key的过期时间增加随机值,避免同一时刻大面积过期;同时做Redis高可用和多级缓存兜底。
面试时建议用“先讲表象,再讲解决方案,最后讲怎么避免”的结构,这样逻辑最清晰。
5.2 缓存一致性:先更新DB还是先删缓存
缓存和数据库双写时,保证一致性是另一道高频题。业界最常用的是Cache Aside模式:读的时候先读缓存,没命中就读数据库,再回填缓存;更新的时候先更新数据库,再删除缓存。
为什么不更新缓存而是删除缓存?因为并发更新缓存很可能产生旧值覆盖新值的问题。删除缓存策略也有一个问题:如果先更新数据库、再删缓存,删缓存这步失败,老缓存依然存在,数据还是不一致。常见补救是“延迟双删”:先删除缓存,更新数据库,再睡一小段时间,再删除一次缓存。但延迟时间很难精确控制,最终一致性要求高的场景,更推荐订阅MySQL的binlog,由Canal把数据库变更事件推给消费者,消费者再去删除或者更新缓存,这样不侵入业务代码。
这道题没有完美答案,重点是把方案演进过程说清楚,从最简单的“先更新DB再删缓存”,到“延迟双删”,再到“binlog异步同步”,每一步都是为了解决上一步的缺陷。
5.3 Redis Cluster与缓存分片
单机Redis内存有上限,比如128G的机器,Redis能用的远小于这个值,而且单机性能和容量终究有限,所以需要集群。Redis Cluster把数据分片到多台节点上,通过16384个哈希槽管理数据分布。客户端计算key的CRC16值并取模16384,得到这个key属于哪个槽,再找到对应节点。
这里有一个很容易混淆的点:Redis Cluster是“分片+主从”的组合,分片解决容量扩展,主从解决高可用。主节点挂了,从节点自动提升为主节点继续服务。但在主从切换过程中,会有短暂的数据丢失或不可用,这是CAP里选择AP(或者说弱一致)的体现。
面试里还常问一致性哈希。一致性哈希的核心价值是节点增减时,只影响少量key的迁移,不像简单取模那样需要大规模重新映射。虚拟节点是为了解决数据倾斜问题。理解一致性哈希的关键是“把数据映射到一个环上,而不是线性取模”,可以用经典的“服务器节点顺时针查找”来记忆。
5.4 分布式存储基础概念
缓存之外,还有一类问题是怎么在海量数据上做分布式存储。面试通常不会考太深,但基础概念要能讲清楚。
数据分片:把大表的数据按某种规则分散到多个存储节点。按ID范围分片容易热点,按哈希分片相对均匀但范围查询困难。副本:每个分片保存多份数据,既保证高可用,也能分摊读压力。分布式存储系统的核心设计问题就是“数据怎么分、副本怎么同步、节点故障怎么恢复”。
对象存储、分布式文件系统、分布式数据库这些概念在面经里也可能出现,不需要每个都精通,但要能说出它们的共同点:通过分片扩展容量,通过副本保证可用性,通过一致性协议保证数据可靠。
5.5 分布式ID有哪些方案
分布式存储和分库分表之后,数据库自增主键就不够用了,因为每个分片各自生成自增ID会导致全局重复。于是分布式ID成了必问题。
| 方案 | 优点 | 缺点 |
|---|---|---|
| UUID | 本地生成、无网络开销 | 无序、太长、不适合作为索引进而影响性能 |
| 数据库号段 | 性能好、趋势递增 | 依赖数据库,需要做高可用 |
| 雪花算法 | 趋势递增、不依赖外部组件 | 依赖机器时钟,时钟回拨会出问题 |
雪花算法是重点。它生成的64位long类型ID,通常由1位符号位+41位时间戳+10位机器ID+12位序列号组成。核心思路是“时间戳在高位,同一个毫秒内用序列号区分”,所以生成的ID呈趋势递增趋势。缺点在机器时钟回拨时可能产生重复ID,解决办法是记录上次生成ID的时间,发现回拨就等待或直接报错。分布式UUID这道题,面试官还想听你对方案做选型,而不是简单列举。
6. 分布式任务调度、压测与爬虫:考点延展与实战经验
6.1 分布式任务调度平台:XXL-Job架构速览
前面聊定时任务时提到了XXL-Job,这里展开一下它的核心架构。XXL-Job分成调度中心和执行器两部分。调度中心是一个独立的Web应用,负责任务的CRUD、触发和调度;执行器是业务服务里集成的一个组件,启动后自动注册到调度中心。当到达触发时间,调度中心把任务分发给一个或多个执行器执行。
常见的路由策略有轮询、第一个、最后一个、随机、故障转移、分片广播等。分片广播适合大数据处理场景:比如有10台执行器,要处理100万条数据,每台执行器按自己的分片序号处理1/10的数据。这个设计思想很重要,它可以用来回答“如果任务执行耗时太长怎么办”“怎么让多台机器分摊任务”等问题。
如果只是轻量场景,不想引入独立平台,也可以用ShedLock+Redis。ShedLock的原理是给任务加上一个分布式锁,拿到锁的实例才能执行,其他实例跳过。它不适合做复杂路由,但胜在简单。面试时两种方案都讲,面试官会觉得你有实际选型能力。
6.2 本地联调:如何用IDEA启动多个实例
很多人在牛客上搜过“idea怎么分布式启动项目,就是一个模块起多个”,这确实是很实用的本地调试技巧。本地是单体IDEA工程,但你想模拟分布式环境,比如验证某个服务启动两个实例后Feign能不能负载均衡、分布式锁会不会生效,那就要在IDEA里启动多份同一服务。
具体步骤:打开Run/Debug Configurations,复制一份现有Spring Boot启动配置,在VM options里加-Dserver.port=8081,再复制一个改成8082,然后同时启动即可。如果项目用了Nacos或Eureka,两个实例会自动注册到同一个服务名下;访问接口时可以通过网关或者负载均衡看到请求会被分发到不同端口。
本地多实例调试时有一个坑:如果服务里用了本地缓存或者内存存储,多实例之间数据不共享,可能产生和线上不一致的行为。但换个角度看,这恰恰能帮你发现“哪些数据必须用Redis或者数据库存储”。我当年就是用这种方式验证了一个分布式任务重复执行的问题,印象非常深刻。
6.3 JMeter分布式压测:本地跑不出高并发怎么办
压测是分布式系统里绕不开的话题。JMeter单机压测时,一台机器所能产生的并发请求数量有限,搞不好压测机自己先成了瓶颈。想要更大的压力,就需要JMeter的分布式压测模式,由一台Master调度多台Slave执行机,共同向目标系统发起请求。
配置并不复杂:准备一台Master和多台Slave,所有机器都安装JMeter;在Slave上启动jmeter-server,在Master的jmeter.properties里配置remote_hosts,填上Slave的IP和端口;然后在Master上启动测试计划,选择“远程启动”即可把测试任务分发下去。执行完成后,聚合结果会汇总到Master展示。
压测看起来简单,细节决定成败。要确保Master和Slave的JDK版本、JMeter版本一致;测试计划里的文件路径要保证每台Slave都能访问;目标系统是否需要独立的压测数据集,否则会互相影响。压测是定位系统瓶颈的手段,压测前要明确目标:是JVM内存不够,还是数据库连接池打满,还是CPU被计算逻辑吃光了。每轮压测后先看监控数据再调优,而不是盲目加大并发数。
6.4 分布式爬虫:任务队列与去重
分布式爬虫在面经里出现频率不算特别高,但一旦出现,很多人会懵。其实它不是特别神秘,核心问题和分布式任务调度非常像:单机爬虫只能串行或有限并行,分布式爬虫要让多台机器协同抓取。
通常的做法是引入一个共享的任务队列,比如Redis List或RabbitMQ;某个Worker每次从队列里取一个URL请求,解析出新的URL后放回队列;为了避免不同Worker爬到同一个URL,还需要一个去重集合,比如Redis的Set或者布隆过滤器,记录已经抓取过的URL。Scrapy-Redis就是把Scrapy的调度器和去重器改成基于Redis实现,从而支持多台Worker协同工作。
面试时不需要讲得太深,但要能说出“队列负责任务分发,去重负责避免重复,Worker负责执行抓取”这样一个架构思路,顺便和分布式任务调度平台做类比,就能体现你举一反三的能力。
6.5 容易被追问的“分布式冷门点”
除了上面这些主流考点,偶尔会遇到几个冷门概念,比如分布式版本控制、分布式账本、分布式DMA等。分布式版本控制其实是最常见的Git,每个开发者的本地仓库都是完整历史,大家通过pull和push协作,和中心化版本控制的理念正好相反。分布式账本则是区块链里的底层概念,多个节点共同维护一份账本,通过共识机制保证一致,概念上可以理解成“一种特殊的分布式数据库”。
像分布式DMA这种更底层的硬件概念,如果面试官不是做底层研发,一般不会深问。遇到完全没听过的问题,不用慌,诚实说“这个方向我没有深入研究,但我的理解是……”,并尽量往熟悉的概念上靠。面试官更看重的是你的思维过程,而不是知识点全知全能。碰到冷门题,也是展示学习能力的机会。
准备分布式八股,我的真实体会是:不要按章节死记硬背,而是给自己准备一个“场景库”。每遇到一个考点,就把它套进一个具体业务场景里,比如下单扣库存、秒杀抢购、分布式任务跑批,反复在心里推演:这个场景会遇到什么问题,有哪些方案,各自代价是什么。然后再用IDEA把服务多启动几个实例,亲手试一把,你会发现很多背不下来的原理,在做完实验之后就自然而然地长在脑子里了。这也是为什么我在上面花了大量篇幅讲本地调试和压测,因为面试时能讲出“我实际跑过”的经验,比任何八股答案都更有说服力。