
最近几年互联网大厂的Java面试问来问去都绕不开几个固定的主题。微服务架构怎么做服务拆分电商下单这种高并发业务怎么保证数据一致性Redis缓存穿透怎么办消息队列怎么保证消息不丢分布式锁到底该用Redis还是Zookeeper。很多同学背了不少八股文一到面试官追问为什么是这个方案换个场景你还这么选吗就卡壳了。这篇文章围绕互联网大厂Java面试里微服务、电商场景下的高频问题以面试问答的形式做一次完整拆解。内容覆盖服务拆分、注册中心选型、Feign调用、分布式事务、Redis缓存、消息队列、分布式锁以及微服务架构里躲不掉的JVM、并发、Spring Boot这些必考题。适合正在准备跳槽的Java开发工作了2到6年、想冲击大厂P6/P7的同学也可以用这套思路自己复盘知识体系。1. 先摸清套路大厂面试官在微服务电商场景下到底想考察什么1.1 从一条电商链路看懂核心考点我在面试候选人的时候很少上来直接问概念。通常会给一条业务链路比如用户浏览商品加购物车下单支付扣库存发优惠券然后让候选人讲这个流程在他设计的系统里是怎么跑通的。就这么一个开放题基本能把一个人的水平摸个七八成。原因很简单。这条链路表面是业务流程底下全是技术考点商品服务是高并发读场景要考缓存设计下单是写多读少的核心链路要考分布式事务和幂等性支付回调是典型的异步消息场景要考消息队列的可靠性扣库存同时要防超卖要考分布式锁和乐观锁整个链路跨多个服务要考服务调用方式、超时重试、链路追踪数据量大了以后还要考虑分库分表和最终一致性。所以你在准备面试的时候不要死记硬背Redis的八股文而是要想清楚在这个电商场景里Redis具体解决的是什么问题当你把知识点挂到业务链路上面试官追问起来你才有底气。1.2 面试官的三连问是什么、为什么、出了问题怎么办大厂面试的节奏非常固定基本是三连问递进的模式。第一层问是什么。什么是微服务什么是CAP理论什么是缓存穿透。这一层考的是知识面只要是认真准备过面试的同学都能说个大概。第二层问为什么。为什么这个场景要用Redis缓存而不是本地缓存为什么注册中心选Nacos而不是Zookeeper为什么分布式事务用消息最终一致性而不是2PC。这一层考的是理解深度需要你明白每个方案的适用边界和代价。第三层问出了问题怎么办。缓存和数据库不一致了怎么修复消息重复消费怎么处理订单服务挂了怎么保证库存不超卖。这一层考的是实战经验和排查能力是最容易拉开差距的地方。很多候选人挂在第二层和第三层不是因为他不会而是因为他从来没在真实场景里踩过坑。这一篇我会尽量把每个方案的取舍逻辑讲透并给出常见故障的排查思路。1.3 一个正统的微服务电商系统长什么样先给个整体画面。一套典型的微服务电商系统按领域模型拆分通常会有这些服务用户服务、商品服务、订单服务、库存服务、支付服务、营销优惠服务、购物车服务、搜索推荐服务、售后服务和消息通知服务。入口一般是一层API网关统一做鉴权、限流、路由转发后面挂各业务服务。服务之间通过OpenFeign走HTTP调用或者通过Dubbo走RPC调用。注册中心用Nacos配置中心也用Nacos。缓存用Redis集群消息中间件用RocketMQ或Kafka数据存储每个服务独立数据库实例数据量大的表做分库分表。全链路有SkyWalking或Zipkin做链路追踪定时任务用XXL-Job。后续所有的面试问题都是在这个系统的基础上展开的。你脑子里要能画出这张架构图答起题来才有画面感而不是背一段与世隔绝的理论。2. 微服务拆分面试第一问怎么讲出深度2.1 拆分原则不是代码层面的拆分是领域层面的划分面试官问你做过微服务拆分吗很多人的回答是按模块拆订单一个模块用户一个模块。这个回答太浅了拆模块只是代码层面的物理切割真正的微服务拆分是领域驱动设计DDD里的限界上下文划分。核心原则是高内聚低耦合一个服务内部的业务逻辑必须是完整的、闭环的服务之间的交互尽量少。同时遵循单一职责原则每个服务只解决一个领域的业务问题。还有一点经常被忽略服务拆分要和团队组织结构匹配也就是康威定律小团队维护小而自治的服务交付和运维效率才最高。在电商场景里拆分的依据主要是业务维度先把整个电商业务画出来找到清晰的业务边界比如用户域、商品域、交易域、支付域。再结合数据维度看哪些数据是强聚合的比如订单和订单明细必须在一个库一个服务里否则分布式事务的代价会让项目很难受。最后还要看流量维度像商品详情、秒杀这类高并发的读场景可以考虑把查询逻辑独立成商品读服务和商品管理写服务分开。2.2 电商系统的具体拆分案例我拿之前做过的电商项目举例。订单服务独立负责下单、订单查询、订单状态流转库存服务独立负责库存查询和库存扣减商品服务管理SPU/SKU数据、商品详情和上下架支付服务对接第三方支付渠道处理统一支付和回调用户服务管理注册登录、用户信息和收货地址营销服务负责优惠券和活动配置。每一步拆分都有一个现实理由。订单和库存必须分开因为两者的写入压力完全不同订单是交易峰值流量库存是高频扣减分开以后才能独立扩缩容。商品服务和搜索服务分开因为搜索要依赖Elasticsearch这类独立中间件而且搜索索引构建和商品变更是完全不同的吞吐模型。支付服务必须独立因为它涉及资金安全需要通过消息队列异步处理回调还要有独立的日志审计。拆完以后你会立刻感受到代价。一次用户下单以前是一个本地方法调用现在变成了订单服务调用库存服务、调用营销服务、扣优惠券、发消息给支付服务一次操作跨越了网络、涉及多个事务复杂度成倍增长。所以面试官问拆分的时候一定要主动讲清楚拆分带来了什么问题我是怎么解决分布式事务和服务间通信的这就是引导面试节奏的技巧。2.3 拆分粒度的坑与权衡微服务不是拆得越细越好。服务拆得太细会导致一次业务流程要经过十几次网络调用延迟叠加排查问题要跨好几个系统的日志运维成本直接起飞。服务拆得太粗又退化成了单体应用无法独立部署和扩容团队之间互相阻塞。衡量粒度有一个很实用的标准一个服务如果改了代码理想情况下不需要同步改别的服务就能独立上线那么拆分粒度基本是合理的。另一个标准是看团队规模一个服务至少得有2到3个人长期维护数量超过团队协作极限就必须合并。这里还有一个高频追问微服务和SOA有什么区别。SOA倾向于复用企业级ESB总线做集成服务粒度偏大通信依赖重量级的SOAP协议。微服务强调去中心化治理服务自治轻量级通信协议比如HTTP/REST或高性能RPC每个服务可以有自己的数据库甚至自己的技术栈。面试时把两者的核心差异讲清楚比背定义要好得多。2.4 数据一致性伴随的复杂要求拆分之后原本一个单体应用里用一个数据库事务搞定的事情变成了跨服务的分布式操作。比如用户下了一个订单订单库要插入订单数据库存库要扣减库存营销库要核销优惠券支付服务要发起支付。这三四个写在不同的数据库里无法再用本地事务保证同时成功。这时候就必须引入分布式事务方案。库存扣减和订单创建的强一致性要求高我会优先考虑TCC或者最大努力通知。而优惠券核销和支付结果通知这类允许短暂延迟的用消息队列做最终一致性。你在回答拆分粒度问题时主动把拆完以后有哪些地方变得更难了讲出来面试官就会觉得你是有真实架构经验的而不是只会画图的伪架构师。3. 服务注册与发现Nacos、Consul、Zookeeper到底怎么选3.1 先从CAP理论说起注册中心是整个微服务架构的基石没有注册中心服务之间就没法动态发现彼此的地址。市面上主流的有Nacos、Consul、Zookeeper、Eureka。面试官问注册中心选型本质是在问你对CAP理论的理解。CAP指一致性Consistency、可用性Availability、分区容错性Partition tolerance。分布式系统里网络分区不可避免所以P是必选项要在C和A之间做取舍。Zookeeper是CP设计发生网络分区时为了保证一致性和Leader选举正确性会拒绝写入请求、暂时不可用。Eureka是纯AP设计节点彼此完全对等一个节点挂了其他节点照常提供服务保证可用性但可能读到过期服务列表。这个理论理解以后很多问题就不用死背了。比如为什么Eureka在服务宕机时不会立刻把它从列表里摘掉因为Eureka优先保证可用性认为宁可让调用方重试或报错也不能因为更新服务列表而拒绝新注册的服务。3.2 Nacos为什么能成为主流选择现在国内公司用Nacos最多因为它灵活地支持AP和CP两种模式。临时实例默认走AP模式服务注册后如果心跳超时直接剔除节点适合应对服务频繁上下线的微服务场景。持久实例走CP模式通过Raft协议保证一致性更适合需要强一致的场景比如配置管理。配置中心也是Nacos的杀手锏。Spring Cloud Alibaba可以非常方便地把配置放在Nacos里支持动态刷新修改配置以后应用不需要重启就能生效。相比单独搭一套配置中心比如Spring Cloud Config加消息总线Nacos一体化的方案运维成本低很多。按大多数电商项目的实际情况注册中心加配置中心统一交给Nacos管理是最可靠的。如果项目用Dubbo比较多还需要考虑兼容性Zookeeper对Dubbo的兼容历史最悠久但Zookeeper做注册中心在处理服务上下线时有几秒延迟高并发场景下容易误伤调用方。Nacos在这一点上要顺滑很多这也是它在国内Java生态里越来越主流的原因。3.3 服务发现与负载均衡怎么配合注册中心只负责保存服务地址列表真正发起调用时消费方要自己实现负载均衡。如果没有特别引入RibbonSpring Cloud 2020版本以后默认用的是Spring Cloud LoadBalancer。通过OpenFeign调用一个服务时Feign内置了负载均衡器从注册中心拉取服务提供方列表按轮询、随机或权重策略选择目标实例。一个需要注意的细节是负载均衡策略的选择与业务场景强相关。对无状态的查询服务轮询就够了。对单台实例连接数有限的调用方要改用最小并发数策略。对需要区分实例性能差异的场景就要用Nacos注册的权重信息做加权随机。很多人在这个点上答得不好只是知道有几种策略就结束了要说清楚每个策略解决什么实际问题。3.4 服务下线延迟一个容易被忽略的线上事故我踩过最典型的一个坑是服务发布时下游服务一直调用到旧实例。根源在于服务从注册中心摘除得不及时调用方还持有旧的实例列表。现在标准的做法是服务下线前先调用注册中心的注销接口然后等待几秒钟确保调用方刷新了本地缓存的服务列表再真正把进程停掉。线上发布脚本里这个等待通常设为10到15秒。如果服务进程突然被kill -9连注销接口都来不及调用注册中心只能等心跳超时才剔除实例这个窗口期调用方会不断请求到死掉的实例。解决方案是让调用方配合配置重试机制以及把Feign的超时时间设短一点避免单次请求长时间卡死。4. 服务间通信OpenFeign原理与RPC选型4.1 HTTP与RPC之争为什么要两者共存微服务之间的通信现在基本是HTTP和RPC二分天下。OpenFeign走的是HTTP协议基于接口加注解声明式调用对Java开发者非常友好结构清晰、调试方便还能直接用Fiddler或Postman看请求报文。它适合跨语言场景不多、追求开发效率的中小型微服务项目尤其是Spring Cloud全家桶。Dubbo这类RPC框架走的是自定义TCP二进制协议序列化效率更高、传输体积更小、调用延迟更低适合对性能敏感、服务数量庞大、内部Java技术栈统一的大型系统。但是Dubbo的泛化调用、服务治理比较复杂对团队要求也更高。在电商场景里实际往往两类并存对订单、库存这类内部强依赖的核心调用链用Dubbo保证性能对查询商品详情、营销信息这类非关键路径用OpenFeign降低开发成本。面试时你表达出根据依赖强度决定通信方式比盲目吹捧某一种要好得多。4.2 OpenFeign的底层原理面试官问Feign原理其实是想看你有没有看过源码。Feign的核心是一个动态代理机制。你定义一个接口在接口上标注FeignClient(name order-service)Feign会为这个接口生成一个JDK动态代理对象。当你在业务代码里调用接口方法时代理对象会拦截方法调用读取方法参数和注解构建出HTTP请求然后交给底层的负载均衡器和HTTP客户端去执行。构建请求时会结合Spring MVC的注解解析比如RequestMapping、PathVariable、RequestBody。微服务名映射到注册中心的具体服务地址。如果你配置了fallback熔断降级类代理对象还会在调用异常时返回兜底方法。4.3 一次电商调用链路的完整过程拿用户下单举例。用户在网页提交订单请求先打到API网关网关鉴权后路由到订单服务的下单接口。订单服务要想办法扣减库存于是通过Feign调用库存服务。这个调用的完整过程是Feign从Nacos拉取库存服务的实例列表按负载均衡策略选出一个实例IP构造包含商品ID和扣减数量的HTTP请求发送过去。如果扣库存失败或超时Feign会根据配置的重试机制进行重试。重试成功则流程继续重试还失败就走降级逻辑返回业务失败同时记录日志。这个链路里必须关注几个参数connection-timeout用于建立连接的超时时间read-timeout用于等待服务响应的时间。在电商秒杀场景read-timeout设到3秒就差不多了太长会拖垮调用方线程池太短容易因为GC停顿产生大量超时误报。4.4 重试与幂等Feign里最容易出问题的环节Feign的重试机制是把双刃剑。一个超时请求回来时服务端可能已经成功处理了也可能没有处理。如果无脑重试而目标接口不是幂等的就会造成重复创建订单、重复扣库存这类脏数据。所以重试的前提是接口幂等。下单接口设计成幂等需要前端生成全局唯一订单号或业务号服务端检查这个业务号是否已经被处理过已处理就直接返回上次结果。还有一个教训重试次数不要设太多默认的Retryer重试100毫秒间隔最多重试3次就可以了。很多线上故障就是重试风暴引起的上游1秒超时重试5次瞬间把所有下游连接池打满把正常流量也拖垮了。5. 分布式事务电商下单的灵魂拷问5.1 为什么Transactional救不了分布式场景一个朴素的想法是我在服务方法上加Transactional不就能保证所有操作一起提交或者回滚了吗这个想法在单体应用里是对的但在微服务下一个方法调用链路横跨订单库、库存库、优惠券库Spring管理的是本地数据库连接的事务它的回滚只作用于当前服务对应的一个数据库。别的服务的数据库操作根本不在这个事务的管辖范围内。拆开来看一次下单中订单服务插入订单成功调用库存服务扣减库存成功然后最后一步插入订单明细时抛异常。订单服务本地事务回滚但是库存服务的库存已经扣了没有一起回滚。数据就不一致了。所以分布式事务需要跨服务的协调机制而不是依赖单个数据库的ACID。5.2 2PC和TCC强一致方案的代价两阶段提交协议是经典方案有事务协调者和参与者。第一阶段所有参与者执行预提交、锁定资源第二阶段协调者根据所有参与者的响应决定提交或回滚。在理论层面它保证了强一致但实际工程里它的弱点很明显第一阶段锁定资源期间如果某个参与者一直没有响应整个事务都会卡住锁时间过长吞吐量骤降。另外协调者是单点协调者挂了整个事务卡死。TCC是业务层面的补偿方案把一个分布式事务拆成Try、Confirm、Cancel三个阶段。Try阶段完成业务检查并预留资源Confirm阶段真正执行业务Cancel阶段做补偿释放预留资源。比如扣库存Try阶段预扣库存但不实际扣Confirm阶段实际扣减Cancel阶段释放预扣量。TCC的好处是不用长时间锁数据库资源性能比2PC好很多。但它侵入性极强每个业务接口都要实现三个方法开发成本相当高而且空回滚、悬挂这些边界问题很考验功底。5.3 可靠消息最终一致性电商玩得最多的套路现实中大厂电商里真正高频使用、面试最爱问的是可靠消息最终一致性方案。核心思想很简单本地事务和消息发送绑定在一起保证消息一定能发出去消费者一定能消费到通过最终一致的状态达到数据对齐。实现方式通常有三种。一种是本地消息表本地事务里写入业务数据的同时在同一数据库里写一条消息记录。然后有一个定时任务把消息表中的消息扫描出来发送到MQ消费者消费成功后回调更新消息状态。另一种是消息事务RocketMQ的事务消息把发消息的prepare动作和本地事务放在同一个流程里先发半消息执行本地事务本地事务成功就Commit发送正式消息失败就回滚如果事务状态不确定还有反查机制。第三种是订单位标记加定时对账业务表里有一个状态字段比如已支付、待支付之类的定时任务扫描超时未处理数据触发补偿。用户下单减库存的经典落地方案就是订单服务本地事务里创建订单同时发一条扣减库存的半消息给RocketMQ。事务提交后消息被正式发送库存服务消费消息执行扣减扣成功则回执扣失败则记录并重试。这样订单和库存就通过异步消息实现最终一致。5.4 面试这样答分布式事务才有亮点不熟悉这块的人只能说出几个名词熟悉的人要能针对场景选方案。可以按不同业务要求来区分资金扣款要求实时强一致的就用TCC下单扣库存这种允许存在短暂不一致但最终要一致的用可靠消息最终一致性低并发、企业内部系统的强一致场景用2PC也能接受但要考虑扩展性问题。还有一个重要补充分布式事务没有银弹最好的方案是尽量避免跨服务事务。比如把订单和订单明细放到同一个数据库下单扣库存改成通过消息异步化先返回下单成功库存扣减失败走补偿。这种事务外置的思路在架构层面比任何分布式事务框架都更可靠。6. 缓存Redis穿透、击穿、雪崩与一致性6.1 电商的缓存架构是怎么搭的电商系统里缓存是分层设计的。最外层是CDN缓存静态资源和图片。应用测做本地缓存比如Caffeine缓存那些几乎不变并且访问量巨大的热点数据像商品详情里的品牌信息。然后才是Redis集群承担主要的缓存职责。最后是数据库兜底。Redis的key设计有讲究通常是业务前缀加冒号加业务ID比如product:detail:1001。value序列化选JSON还是protobuf要看吞吐要求JSON可读性好对象体积大protobuf体积小、反序列化快但有一定的编译工程成本。过期时间也要按场景设计商品基本信息的过期时间可以长库存数量这类实时数据的过期时间必须短甚至直接不缓存或用分布式锁保护。6.2 缓存三兄弟穿透、击穿、雪崩怎么分缓存穿透查了一个数据库里也不存在的数据导致请求每次都打到数据库。攻击者可以用大量不存在的ID发起请求直接压垮数据库。解决手段主要是布隆过滤器把有效ID放进布隆过滤器里查不到就返回空另一个方法是为空结果也做短暂缓存几秒到几十秒扛住瞬时攻击即可。缓存击穿某个热点key到了过期时间大量并发请求同时打到数据库数据库压力峰值。解决方法是互斥锁只有一个线程去重建缓存其他线程等待或者短暂返回空。还有一个方案是逻辑过期value里存过期时间而不是让Redis自动淘汰读的时候发现逻辑过期就异步更新缓存用户拿旧数据继续看。缓存雪崩大量key同时过期或者Redis集群直接宕机导致所有请求压到数据库。解决办法是设置过期时间时加随机漂移量避免同一时间成片失效同时做多级缓存兜底再结合Redis集群高可用和限流熔断把数据库保护起来。6.3 缓存与数据库的双写一致性最需要实际操作经验缓存一致性是面试里的重头戏网上方案很多但落地起来坑更多。业界最稳定的是Cache Aside Pattern读的时候先读缓存缓存miss再查数据库、回填缓存。写的时候先更新数据库再删除缓存。为什么不是更新缓存而是删除缓存因为更新数据后去写缓存这个写动作本身很容易失败失败就会有一段时间数据不一致。删除缓存失败可以重试而且下次读请求会触发回填自然补偿。删除缓存这个动作看起来简单实际里经常丢。可以先删缓存后更新数据库更新数据库失败旧数据被删了新数据没写用户查出旧数据回填缓存一致性问题更严重。稳妥的做法是延迟双删更新数据库前先删除缓存更新数据库成功后延迟几百毫秒再删一次缓存把并发期间其它线程回填的旧数据清理掉。数据一致性要求更高的写法是订阅MySQL binlog监听数据变更事件异步删除对应key。这个方案对业务代码侵入少可靠性高是电商系统比较成熟的做法。6.4 热点key和缓存倾斜被忽略的实战题经常被问到热点key怎么处理。以前遇到一个案例某个头部主播带货一个SKU的详情页QPS直接拉到十万级单个Redis实例根本扛不住。常规解决思路是做热点key探测将热点数据多副本缓存比如同一个key复制多个备份key分散到不同实例读的时候随机取一个副本。本地缓存也要把这部分热点提前预热让本机吃下很大一部分流量。还有大value问题。有人把一个大对象整串塞进Redis几MB的数据读一次网络传输就要几十毫秒线程被拖死。解决方法是压缩字段、分片存储或者改用哈希结构。这些细节参数没有标准答案核心是让面试官看到你处理过真实流量踩过硬钉子。7. 消息队列削峰与最终一致性的关键拼图7.1 为什么要用MQ电商峰值靠什么顶住没有消息队列之前秒杀期间的下单请求直接打到订单服务每秒几万次瞬间流量应用代码没等扩容就被打崩。引入MQ以后前端请求先进入队列业务系统以自己的最大消费速度慢慢往下游消费。队列像一个蓄水池让流量从尖峰变成一条平稳的河。这就是削峰填谷。同时MQ还承担了解耦职责。支付服务处理完支付成功后把支付成功消息发给订单服务和营销服务订单服务不用同步调用营销服务。这样一来营销服务就算挂了订单还是正常流转过了几分钟营销服务恢复了续上消费就行。7.2 Kafka、RocketMQ、RabbitMQ怎么选在乱谈选型之前先明确事实Java生态和电商场景里RocketMQ的使用率非常高Kafka在日志链路和大数据场景是绝对王者RabbitMQ更多在老项目和一些简单业务场景里使用。选型对比看这几个维度。吞吐量方面Kafka和RocketMQ都能达到十万级每秒RabbitMQ只有万级。可靠性和事务支持RocketMQ自带事务消息这在电商分布式事务场景里是刚需。消息延迟RabbitMQ最低可到微秒级RocketMQ毫秒级Kafka会略高一点。运维成本Kafka依赖ZookeeperRocketMQ有专门的DashboardRabbitMQ最轻量但集群能力弱一些。电商系统里最需要的是可靠性和事务消息所以RocketMQ是比较理想的选择。如果公司是大数据链路为主同时想复用一套中间件Kafka也够用但事务消息需要自己调控件和重试机制代价不低。7.3 消息不丢、不重复、不乱序三大难题逐个击破消息不丢失要分三段来看生产端不丢、Broker不丢、消费端不丢。生产端用同步发送并确认发送结果失败就重发Broker层面开启刷盘和主从复制RocketMQ的主从同步复制可以做到一条消息commit前就把数据复制到备节点消费端先执行业务逻辑成功后再提交ack不要让消费成功成为空头支票。消息不重复本质是要求消费端实现幂等性。去重表或者业务唯一键是常规手段比如订单服务用自己的订单号作为唯一标识Redis里setnx一下如果已经处理过就直接返回成功。消费端没有幂等设计再好的MQ也没办法阻止重复消息到达。消息不乱序方案核心是把同一业务ID的消息发送到同一个MessageQueue消费者也只用一个线程消费这个队列。比如同一个订单的创建、支付、发货消息按订单号哈希到固定队列就能保证按顺序被消费。如果你把消息发到不同队列消费者端并发消费顺序必然乱。7.4 秒杀系统如何用MQ削峰手写一遍印象更深秒杀下单是最经典的案例。用户点抢购按钮网关层先做限流只放一部分请求到后端请求进入Redis预扣库存。Redis库存扣成功就发一条创建订单消息给RocketMQ不直接操作数据库。后台订单服务开启固定数量的消费者线程比如20个线程每个线程串行消费消息最终落到数据库创建订单。这样就保证了数据库每秒处理的请求是可控的不会出现数据库被瞬时流量冲垮的问题。秒杀结束后如果有人订单创建失败消费者重试几次后转人工处理。面试时把这条链路讲透基本能覆盖MQ的大部分考点。8. 分布式锁怎么做才能真正锁住8.1 单机锁和分布式环境的本质差异说到扣库存和防超卖肯定要聊分布式锁。单机应用里直接用synchronized关键词或者ReentrantLock就能保证一个JVM进程内只有一个线程进入临界区。但是微服务部署了多个实例不同的实例运行在不同的JVM里本地锁没法跨进程生效。三个实例同时处理同一个SKU的扣减三个JVM的锁不互斥库存照样超卖。分布式锁的本质是在多个进程之间共享一个互斥状态谁拿到这个状态谁才有资格执行。必须满足互斥性、可重入性、高可用、防死锁这几个基本素质才能称得上是个靠谱的分布式锁。8.2 基于Redis实现分布式锁SETNX与看门狗最简单的Redis锁是SETNX没key就设置成功设置成功代表加锁成功。但要注意不能少了过期时间否则客户端持有锁后进程崩溃锁永远释放不了。Redis里正确加锁是加两个参数同时设置SET lockKey lockValue EX 30 NX这个操作是原子的不会有设置成功但没设置过期时间的坑。锁的value要带上请求唯一ID释放锁的时候用Lua脚本比较value是自己才删除。防止一个线程的锁过期被删了以后另一个线程拿到锁然后前一个线程把自己的锁误删了。上面这个方案有个比较棘手的场景如果业务逻辑执行时间超过了锁的过期时间锁提前释放了别的线程又进来了怎么解Redisson框架的看门狗机制是业内普遍采用的方案。获取锁后后台有一个定时任务每隔锁过期时间的1/3就自动续期直到业务完成为止。我试用过底层用了时间轮调度非常优雅。如果你的项目不允许引额外框架就把锁的过期时间设置成业务预计耗时的十倍但这是静态方案不够稳。8.3 Redlock、Zookeeper锁以及数据库锁Redis分布式锁有一个争议主节点挂掉时锁数据还没来得及复制到从节点从节点顶上来以后新客户端就能重新加锁导致原来持有锁的客户端和新的客户端同时持锁。Redlock算法试图通过加锁到大多数Redis节点来解决这个问题但因为时钟漂移和GC暂停的存在在复杂分布式环境里依然不绝对安全。不过实际业务中绝大多数场景用单点Redis加Redisson就够了Redlock的高成本收益没那么高。Zookeeper锁的定位完全不同它靠临时顺序节点实现。客户端创建临时顺序节点判断自己是不是最小序号是就拿到锁不是就监听前一个节点。临时节点在客户端断连后会被Zookeeper自动删除天然避免了死锁。而且由于顺序一致性锁的公平性有保障。缺点是性能不如Redis锁因为每次加锁释放锁都是Zookeeper的写操作节点多的时候会有性能瓶颈。数据库锁比如select ... for update能实现锁但性能差资源占用高在电商高并发里基本不用只在一些后台管理功能里偶尔出现。8.4 锁粒度与业务场景这才是面试加分项分布式锁本身不难难的是锁的粒度。并发扣减库存场景理想的锁粒度不是锁整个库存服务而是锁单个SKU。我在Redis里的做法是lock:stock:{skuId}只锁住一个商品不同商品的扣减互不干涉并发能力大幅提升。还有一个坑是锁超时导致业务没完成。存这个场景要真正扛住流量单靠分布式锁不够还得配合数据库乐观锁update stock set stock stock - 1 where sku_id ? and stock 1通过受影响行数判断是否能扣减这样鲁棒性更强。面试答到这里就明显比那些只背SETNX加Lua的同学高一个层次。9. Java高频八股文微服务之外躲不开的那些题9.1 面试必考String、HashMap、ConcurrentHashMap微服务问得再深八股文还是绕不开尤其Java基础题是很多大厂的一面门槛。我面试别人的时候经常先丢一道哈希相关的题热场。HashMap在JDK 8里底层是数组加链表加红黑树数组长度超过64且链表长度超过8时链表转红黑树。为什么是8这是基于泊松分布的计算结果链表长度为8的概率小于千万分之一用红黑树来平衡极端情况下查询的性能损失。ConcurrentHashMap要重点说清楚它是怎么做到线程安全的。JDK 8以后放弃分段锁使用CAS加synchronized对数组的每个桶加锁锁粒度更细并发性能更好。谈到这里面试官大概率会追问CAS是什么意思CAS有什么缺点你就可以引出ABA问题以及版本号机制。平时写代码很少用这些底层知识但面试必须能讲清楚。String类有两道经典题。字符串常量池和堆中的String对象区别String不可变的设计动机。不可变的好处是安全、线程安全、可以安全地缓存hashCode。为什么String用final修饰本质是让多个引用共享同一个字符串对象时不会被意外修改。你说清楚这些底层来源面试官就能判断你是背的题还是真懂。9.2 JVM、线程池、GC一组永远问不完的组合拳JVM考点高频的是内存区域划分和垃圾收集器。内存区域至少要能画出堆、虚拟机栈、本地方法栈、方法区、程序计数器各自的作用然后说明哪些是线程私有哪些是线程共享。GC课程上CMS和G1是必谈的现在ZGC也常被问到包括它们的适用场景比如CMS的并发标记清除和内存碎片问题G1通过Region划分做到可预测停顿ZGC目标是把停顿时间控制在10毫秒内。线程池的问题必须能给出具体参数。ThreadPoolExecutor构造函数的七个参数核心线程数、最大线程数、空闲存活时间、时间单位、工作队列、线程工厂、拒绝策略。对CPU密集型任务核心线程数可以设定为CPU核心数加一IO密集型任务核心线程数可以略多常见经验值是CPU核心数乘2。拒绝策略有四种AbortPolicy直接抛异常、CallerRunsPolicy由调用线程执行、DiscardPolicy和DiscardOldestPolicy都是静默丢弃。电商场景下线程池满了直接丢消息是不可接受的所以往往要配合自定义策略加MQ缓冲。9.3 Spring Boot和MyBatis的微服务高频题Spring Boot的自动配置原理是必考题。SpringBootApplication组合了SpringBootConfiguration、EnableAutoConfiguration和ComponentScan。EnableAutoConfiguration会通过隐藏的META-INF/spring.factories或者spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件加载一批自动配置类并且配合ConditionalOnClass、ConditionalOnMissingBean等条件注解根据当前classpath里的依赖情况决定是不是初始化相关Bean。你在项目里引入一个starter本质就是导入了一批自动配置类。Transactional失效这个面试题坑了很多人。自调用失效是很常见的一种场景同类内部通过this调用带事务注解的方法事务不会生效。原因是Spring事务基于AOP动态代理this调用走的是当前对象不会经过代理对象的拦截。还有数据库引擎不支持事务、方法非public、注解被类内部方法直接调用异常被吞了等等能说个四五个场景才够。MyBatis的一二级缓存也是经典掉分题。一级缓存是SqlSession级别的默认开启二级缓存是Mapper级别的默认关闭。有个项目里有人开了二级缓存结果一个商品价格更新后另一个线程查到的还是旧数据因为缓存没有及时失效。很多公司明确要求禁用二级缓存就是吃够了这种苦头。面试时如实讲自己的经验比背书有说服力得多。9.4 Java机考/算法别把基本功丢了虽然现在大厂一二面都倾向于系统设计题算法题的概率也不低尤其是社招P7以下很可能会考排序和动态规划。冒泡排序这种最基础的算法还是需要手写不卡壳。比较常用的是快速排序和归并排序记得对应的时间复杂度与稳定性快排平均O(nlogn)、不稳定归并稳定、O(nlogn)但是需要额外空间。如果还有余力建议把蓝桥杯或力扣上的一些常见题过一遍重点是一维和二维动态规划、滑动窗口、TopK问题。TopK可以用二叉堆也可以手写快速选择复杂度都是O(n)或者O(nlogk)。这些内容在Spring Cloud调优和GC调优面前看着不起眼但是现场答不出来会非常减分。10. 面试策略与实战复盘这些坑不要再踩10.1 简历怎么写才更容易被面到微服务相关题简历上写项目每一条都要能引导出技术问题。简单列一句负责电商订单模块是不会被深挖的。要写清楚项目规模、技术挑战、数据量级和你承担的角色。例如支撑日均千万级订单的电商平台主导订单服务微服务化实现了订单与库存的最终一致性RT从400ms降到120ms。这就等于告诉了面试官你可以聊微服务改造、分布式事务、性能优化、Redis、MQ全是高频考点。同时不要主动写你不熟的技术名词。简历上一旦出现我精通Kafka面试官大概率会问一些Kafka底层原理的冷门问题比如日志段和索引文件的具体结构答不上来就是减分。适当暴露边界把你想被问到的技术点前置到项目描述里是最有效的引导手段。10.2 遇到不会的问题这样答反而加分没有谁什么都会。我面试时最看重的不是你全能而是你遇到不会的问题时的思考方式。直接说我不会会显得没有探索深度。比较好的回答框架是我先说我理解的部分然后给出最常见的可行思路再说明我没做过、不太确定的细节。比如面试官问你们消息队列的消费积压怎么处理就算你没实际处理过也可以说我知道要先确认堆积原因如果只是瞬时流量加消费者实例数进行水平扩容如果下游服务处理瓶颈明显要考虑先把数据快照落库后续再补偿处理。这种对问题的结构有认知、敢于提出解决路径的答法让面试官能评估你的思维模型。比慌张地说没遇到过要好得多。10.3 一场真实面试的复盘从拆分讲到一致性到缓存我有一次面候选人他的项目是电商中台我沿着一套连环问走下来他答得很扎实。先是问服务拆分怎么做的他讲了按领域划分订单、商品、支付、库存并说明库存服务独立的原因。接着问拆分后订单和库存的一致性怎么办他立刻说用RocketMQ事务消息做最终一致性把半消息、本地事务Commit后再发具体细节也讲清了。我又追问库存扣减失败了怎么办他引入了消息重试、人工补偿、定时对账兜底。然后我转向性能问热点商品详情页怎么抗住高并发。他说先做本地缓存加Redis多级缓存布隆过滤器挡穿透热点key多副本存储再配合限流熔断把数据库保护起来。最后我问他Redis和数据库的一致性他说Cache Aside更新库删缓存再结合binlog订阅双保险。这一整轮下来两个人都聊得很舒服。他也许不是每一项都很精但是每一层都有真实思考和踩坑体验这就是大厂面试最想看到的画像。10.4 系统设计题的核心不是背模板而是讲权衡电商和微服务的面试题表面是在考技术实际是在考权衡能力。所有方案都有代价没有一个东西是完美的。你需要展现的是在什么约束条件下你会选择什么方案会放弃什么好处会出现什么问题怎么去兜底和补偿。拿缓存一致性来说你说Cache Aside的时候要能说出更新缓存和删除缓存的差异你说Redis锁的时候要能说出看门狗续期的机制你说MQ削峰的时候要能说出消息丢失和重复消费的防御方案。每一个选择背后都有原因的推导。这套思维模型比任何具体答案都更重要。按照这个思路把整个微服务电商技术栈的每一个环节都梳理清楚你在任何大厂面试的微服务环节里都不会是背八股文的那个人而是可以跟面试官平等交流技术的那个人。