免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

电商大厂Java面试:Spring Boot、Redis、Kafka、微服务与JVM调优全纪实

电商大厂Java面试:Spring Boot、Redis、Kafka、微服务与JVM调优全纪实 电商大厂Java面试Spring Boot、Redis、Kafka、微服务与JVM调优全纪实面试官西装笔挺面前放着一杯美式咖啡目光像扫描器一样扫过面前的简历。谢飞机穿着格子衬衫背着双肩包坐下时不小心把椅子推出了一米远又赶紧拖回来。面试官谢飞机是吧先做个自我介绍。谢飞机面试官您好我叫谢飞机喜欢编程会写Java做过一个“飞机商城”项目。虽然叫飞机商城但是不卖飞机卖的是飞机杯……不是不是模型飞机模型飞机面试官……好那我们直接开始。你的简历上写了熟悉Spring Boot、Redis、微服务那我们今天围绕一个电商下单场景来聊。第一轮基础框架与数据存储面试官你用Spring Boot搭建“飞机商城”的时候用的是哪个构建工具Maven和Gradle有什么区别你怎么管理依赖版本谢飞机我用Maven也听说过Gradle。Maven用pom.xmlGradle用build.gradle。Gradle的构建速度比Maven快因为支持增量构建和并行任务配置也更简洁。我平时用Spring Initializr生成项目依赖版本交给Spring Boot的BOM统一管理这样不会打架。面试官不错看来基本功还行。那说说Spring Boot的自动配置是怎么实现的为什么引入一个starter就能自动帮你创建Bean谢飞机这个我背过主要靠EnableAutoConfiguration注解。Spring Boot在启动时会从META-INF/spring.factories文件里加载一堆自动配置类然后用ConditionalOnClass、ConditionalOnProperty这些条件注解判断条件满不满足。比如你引入了spring-boot-starter-webclasspath里有了Tomcat和SpringMVC自动配置类就会帮你创建DispatcherServlet、内嵌Tomcat这些Bean。面试官理解得不错。那电商项目里一般有商品、订单、用户数据库访问层你选JPA还是MyBatis连接池为什么选HikariCP谢飞机JPA适合简单CRUDMyBatis适合复杂SQL。我们订单模块用MyBatis因为要写动态SQL和复杂的多表join。连接池用HikariCP因为它快性能好。面试官那HikariCP为什么快能说几个具体的点吗谢飞机呃……它字节码优化做得比较好好像还用了一些并发集合减少锁竞争。具体的我……我回去再深入看看。面试官好吧。那再说说Redis。你如何保证缓存和数据库的一致性比如用户查商品详情先查缓存不命中再查数据库那更新商品时怎么办谢飞机更新数据时先更新数据库再删除缓存。如果删除缓存失败就延迟双删先删除一次缓存过几百毫秒再删除一次防止读请求把旧缓存写回去。不过这个方案也做不到100%一致但大部分业务可以接受。面试官那缓存穿透、缓存击穿、缓存雪崩分别怎么解决谢飞机缓存穿透就是查一个不存在的key每次都会打到数据库用布隆过滤器拦截或者把空值也缓存起来缓存击穿是某个热点key突然过期大量请求直接打到数据库可以用互斥锁重建缓存或者让缓存不过期加逻辑过期时间缓存雪崩是很多key同时过期或者Redis挂了解决办法是过期时间加随机值Redis做集群然后做降级和熔断。面试官很好你对这块掌握得挺扎实。我们进入下一轮。第二轮微服务、消息队列与分布式事务面试官你那个“飞机商城”如果拆成微服务商品、订单、库存、用户各自部署那服务间怎么调用谢飞机用Spring Cloud OpenFeign声明式HTTP客户端。服务启动后注册到注册中心比如Eureka或者Consul。调用的时候用Feign客户端配上服务名底层通过Ribbon做负载均衡还有重试机制。面试官那如果库存服务响应很慢订单服务一直等着最后拖垮了整个系统怎么办谢飞机用熔断和降级。用Resilience4j实现熔断器比如5秒内失败率超过50%就打开熔断器后续请求直接走降级方法返回“库存不足”之类的提示过一段时间再放一部分请求试试。不过我以前用过Hystrix后来停更了就学Resilience4j了。面试官嗯。你提到了Kafka那你们下单后是怎么用Kafka的谢飞机用户下单后订单服务先写订单库然后发一个“订单创建”消息到Kafka。库存服务消费这个消息去扣库存积分服务消费消息加积分。这样服务之间不用同步调用削峰填谷。面试官那怎么保证Kafka消息不丢失消费者又怎么保证不重复消费谢飞机生产端设置acksallLeader和ISR都收到消息才返回成功。消费端关闭自动提交处理完业务后再手动提交offset。但是手动提交会带来重复消费所以消费逻辑要幂等比如用数据库唯一键或者Redis里存处理过的消息ID。面试官行那最后一个复杂的点用户下单时需要扣库存、生成订单、扣优惠券、发积分如果某个步骤失败怎么保证一致性谢飞机呃……这个我们当时用……可以用Seata的AT模式或者TCC分布式事务。但TCC需要写冻结和解冻接口太复杂了。我们其实采用的是最终一致性订单服务先写本地消息表然后发消息给下游下游消费成功后回调更新消息状态。如果失败就定时任务重发……具体细节……反正就那样。面试官那样是哪样你能说一下如果库存扣减成功但订单发送消息失败你的方案是怎么处理吗谢飞机我……我们用了事务同步发的消息如果发送失败就抛异常回滚不对消息已经发出去了但事务提交失败也会产生不一致……其实这块我当时只是负责调用没有深入设计。面试官你说得对这地方我确实有点模糊。面试官没关系分布式事务本来就难。下一轮。第三轮性能排查、安全与CI/CD面试官你现在上线了一个秒杀接口结果今天线上订单接口突然变慢用户反馈下单要五秒。你怎么排查从JVM层面说说。谢飞机先看服务器CPU、内存、磁盘、网络。CPU高就用top定位进程再用jstack导出线程快照看看是不是有阻塞或者死循环。内存高就用jmap查看堆内存jstat看GC情况是不是频繁Full GC。如果怀疑有慢SQL再看数据库慢查询日志。我还会用Arthas来做在线诊断不用重启服务。面试官如果最后发生OOM了你怎么分析谢飞机启动时加-XX:HeapDumpOnOutOfMemoryError让JVM在OOM时自动导出堆Dump文件然后用MAT或者VisualVM打开看大对象和GC Roots引用链找到内存泄漏的源头。如果是线上环境我会把Dump文件拷下来分析或者在服务器上用jmap -dump手动导。面试官好。那说说JWT和OAuth2有什么区别你的项目里怎么用的安全框架谢飞机JWT是一种令牌格式适合做单点登录OAuth2是一种授权框架允许第三方应用获取受限资源。JWT可以作为一种OAuth2的Access Token格式。我的项目里用Spring Security JWT做登录认证用户登录后拿到JWT后续请求在Header里带上。OAuth2是用来对接第三方登录的比如微信、GitHub。面试官那怎么防止别人恶意刷你的订单接口比如用脚本一秒请求1000次。谢飞机做限流。可以用Sentinel也可以在Redis里实现令牌桶算法比如每秒钟放N个令牌请求获取到令牌才放行否则返回“请求太频繁”。还可以按用户ID做IP限流。但具体参数要压测后调整。面试官最后一个问题你的项目怎么部署CI/CD流程是什么样的谢飞机用GitLab CI和Jenkins。开发提交代码到GitPush后自动触发流水线单元测试、代码扫描、打包成Docker镜像然后Push到镜像仓库最后通过Kubernetes的滚动更新发布到集群。Kubernetes里有ConfigMap管理普通配置Secret管理密码、私钥等敏感信息。面试官很好面试就先到这里。你的情况我们大致了解了你先回去等通知吧我们会在三个工作日内联系你。谢飞机好的谢谢面试官我送您一个飞机模型吧面试官……不用了你快走吧。谢飞机走后面试官在简历上写了一句“基础尚可分布式、JVM需加强建议备选。”答案详解下面是针对每一轮面试问题的详细解答帮助读者理解业务场景和技术点。第一轮详解基础框架与数据存储1. 构建工具Maven 和 Gradle 区别如何管理依赖版本业务场景在开发一个Spring Boot电商项目时团队需要统一构建工具。Maven和Gradle都是Java项目常用的自动化构建工具。Maven基于XML的pom.xml声明依赖Gradle基于Groovy或Kotlin DSL更灵活。Maven项目结构固定约定优于配置。依赖管理使用坐标groupId, artifactId, version中央仓库提供大量依赖。生命周期clean, compile, test, package, install, deploy清晰插件生态成熟。缺点是构建速度较慢且XML配置冗长。Gradle基于任务图Task Graph支持增量构建、构建缓存、并行执行所以更快。使用Gradle DSL编写构建脚本比XML简洁支持更复杂的条件逻辑。兼容Maven仓库也能用maven-publish发布制品。学习曲线较陡但已成为Android和许多新项目的主流。依赖版本控制Spring Boot项目推荐使用spring-boot-dependenciesBOMBill Of Materials管理版本。在Gradle中可以用platform()或dependencyManagement导入BOM在Maven中通过dependencyManagement导入。自己项目维护的通用依赖可以抽取到dependencyManagement统一版本避免子模块版本冲突。使用mvn dependency:tree或gradle dependencies分析冲突。2. Spring Boot 自动配置原理业务场景只要引入spring-boot-starter-data-redis不需要写任何配置就能用RedisTemplate。这是因为Spring Boot的自动配置在背后做了事情。核心注解SpringBootApplication包含EnableAutoConfiguration。Spring Boot在启动时会通过SpringFactoriesLoader读取所有依赖包中META-INF/spring.factories新版本是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中的自动配置类列表。以RedisAutoConfiguration为例它是一个标注了Configuration的类类上有ConditionalOnClass({RedisOperations.class})表示classpath中存在RedisOperations类才生效。ConditionalOnMissingBean保证用户如果不自定义RedisTemplateSpring Boot才会创建默认的RedisTemplate并且通过EnableConfigurationProperties绑定RedisProperties对应application.yml中spring.redis前缀的配置。这样只需要引入starter对应的Bean就会被自动装配极大减少了手动配置。3. JPA 和 MyBatis 选择HikariCP 为什么快业务场景电商项目涉及订单、商品等复杂查询。Java持久层框架最常用的就是Spring Data JPA基于Hibernate和MyBatis。JPA优点面向对象自动建表简单CRUD几乎不用写SQL通过方法名或Query定义查询。缺点复杂的动态查询、多表join映射比较麻烦很难优化SQL容易被“黑魔法”带偏。适用业务简单、对象模型明确、CRUD为主的系统。MyBatis优点SQL完全可控动态SQL非常强大if、foreach等适合复杂查询和报表性能优化空间大。缺点需要手写SQL字段映射需要维护Mapper接口和XML文件多会有额外工作量。适用电商、支付、订单这类需要精确控制SQL的系统。HikariCPHikariCP是目前性能最高的Java数据库连接池Spring Boot 2/3默认使用它。快的核心优化字节码级优化通过Javassist生成高字节码代理减少反射调用开销。大量使用final关键字和并行集合比如自定义的ConcurrentBag它比BlockingQueue在获取/释放连接时锁更少。优化invoke调用栈减少方法调用深度。连接池大小默认10不是越大越好请根据IO和数据库并发测试。4. 缓存与数据库一致性缓存穿透/击穿/雪崩业务场景商品详情页访问量大用Redis缓存商品信息但商品信息会被后台修改。需要保证缓存和数据库最终一致。一致性方案更新数据库然后删除缓存推荐。为什么不是更新缓存因为缓存数据可能涉及冗余字段更新缓存成本高且并发写可能产生脏数据。删除缓存失败怎么办使用延迟双删先删除缓存再更新数据库然后等待几百毫秒比读请求完成时间更长再次删除缓存。解决并发“读请求把旧数据写回缓存”的问题。更强方案用Canal订阅MySQL binlog异步删除缓存保证最终一致性。注意任何缓存都不能保证强一致业务能接受短时间延迟即可。三种问题穿透查询一个肯定不存在的数据请求绕过缓存直接打DB。解决null也缓存布隆过滤器Bloom Filter快速判断key是否存在。击穿某个热点key在过期瞬间大量并发请求涌入DB。解决互斥锁Redis setnx只让一个请求去查数据库其他请求等待或逻辑过期缓存不过期但存一个过期时间字段后台异步刷新。雪崩大量key在同一时间过期或Redis实例宕机。解决过期时间加随机值如1-5分钟避免同时过期Redis高可用主从哨兵/集群做多级缓存本地缓存Caffeine Redis接口层限流降级。第二轮详解微服务、消息队列与分布式事务5. 微服务间调用OpenFeign、注册中心、负载均衡业务场景订单服务需要调用库存服务、用户服务。如果采用硬编码IP服务一多就难以维护且无法自动扩缩容。因此引入微服务治理。注册中心Nacos、Consul、Eureka都是注册中心。服务启动时上报自己的IP端口服务下线时自动摘除。消费者根据服务名获取服务列表。OpenFeign声明式HTTP客户端。在接口上标注FeignClient(stock-service)定义方法签名Spring会在运行时生成代理对象调用时通过LoadBalancerClient从注册中心选择一个可用实例。负载均衡早期用RibbonSpring Cloud新版使用Spring Cloud LoadBalancer。支持轮询、随机、最少并发等策略。OpenFeign默认集成LoadBalancer。超时和重试Feign支持连接超时、读超时设置。重试会带来重复请求必须配合幂等。6. 服务容错Resilience4j 熔断、降级、限流业务场景一个请求链路是调用库存服务 - 调用优惠券服务 - 调用积分服务。如果库存服务因为慢SQL导致响应越来越慢线程池会被占满最终拖垮整个订单服务。熔断类似电路熔断如果失败率超过阈值例如10秒内失败率50%熔断器打开后续请求直接走降级逻辑不再远程调用。过一段时间进入半开状态放少量请求去试探成功后关闭熔断器。降级提供一个“兜底”方法比如返回“商品正在补货”或者从缓存获取旧库存。降级不是失败而是主动放弃保证核心链路可用。隔离Resilience4j支持线程池隔离或信号量隔离每个远程调用独享一个线程池防止一个服务故障耗尽整个应用线程。限流基于速率限制器每秒最多允许N个请求超过就拒绝。7. Kafka 消息可靠性如何不丢不重业务场景订单服务发送消息库存服务消费消息扣库存。消息如果丢了会导致库存不回减如果重复处理会导致库存多扣。不丢失生产者端设置acksall或-1要求分区Leader和所有ISR副本都写入成功才返回成功。同时开启retries重试设置合理的linger.ms和batch.size不要一次性发太多导致发送超时。Broker端每个topic的副本数replication-factor至少2或3min.insync.replicas配置至少2防止Leader独吞消息。消费者端关闭自动提交enable.auto.commitfalse在处理完业务后手动调用commitSync()。如果处理业务后再提交提交失败会重复消费但不会丢失。不重复幂等消费即使手动提交也可能在提交前崩溃导致同一消息被再消费。幂等方案消费者在数据库中插入消息处理记录唯一键是messageId利用数据库唯一索引防重或者RedisSETNX messageId或者业务本身就是幂等的例如“扣减库存”改为“把库存设置成指定值”而不是“库存-1”。8. 分布式事务最终一致性方案业务场景下单操作跨订单服务、库存服务、积分服务。本地事务无法跨库。分布式事务追求强一致如Seata AT或最终一致消息。常见方案Seata AT 模式工作方式每个数据库都配置Seata的undo_log表。事务开始时Seata生成“前镜像”和“后镜像”业务SQL执行完记录undo_log。提交时先全局提交异步删除undo_log回滚时通过undo_log做反SQL恢复。优点是侵入小开发像写本地事务一样。缺点是性能损耗且需要Seata Server。TCCTry-Confirm-CancelTry阶段锁定资源比如库存冻结N件Confirm阶段真正扣减Cancel阶段释放冻结。需要每个参与方实现三个接口开发量大但性能比AT高。本地消息表 消息队列订单服务创建订单时在同一个本地事务里写入“订单表”和“消息表”status待发送然后启动后台任务把消息发送到Kafka。收到Broker确认后将消息表状态改为“已发送”。库存服务消费消息扣库存扣完后发送一个“扣减成功”回执给订单服务可以用另一个topic订单服务更新消息状态为“完成”。如果消费失败消息表里的数据会重试消费者必须幂等。这个方案核心本地事务保证消息一定被持久化消息队列保证异步最终一致。难点非事务消息发送与业务操作的原子性问题。所以尽量不要用“先发消息再操作数据库”或“先操作数据库再发消息”而是通过本地消息表或Outbox模式解决。第三轮详解性能排查、安全与CI/CD9. 线上接口变慢JVM层排查业务场景秒杀时订单接口从平均200ms变成5s需要快速定位瓶颈。操作系统层面top查看CPU、内存、负载。如果CPU 100%可能是死循环或频繁GC如果负载高但CPU低可能是IO等待磁盘或网络。free -h看内存df -h看磁盘iostat看磁盘IO。Java进程层面jps找到Java进程ID。jstack pid导出线程快照重点看RUNNABLE状态的线程堆栈搜索自定义业务类名看是否卡在锁、数据库调用或网络调用。多次执行jstack对比如果某线程一直相同则可能死循环/阻塞。jstat -gcutil pid 1000 5观察GC情况。如果Full GC频繁且时间很长说明堆内存压力大可能是对象分配太快或内存泄漏。SQL层面开启数据库慢查询日志找出慢SQL用EXPLAIN查看是否走索引是否全表扫描。检查连接池是否被打满比如HikariCP默认最大连接数10如果数据库连接不够请求会排队。在线诊断工具Arthasdashboard看整体运行状态thread -n 3查最忙的线程watch监控方法入参出参trace追踪调用耗时。非常方便不用重启服务。10. OOM 内存溢出分析步骤业务场景服务运行几天后突然OOM日志里出现java.lang.OutOfMemoryError: Java heap space。提前准备JVM启动参数增加-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/dump/ -Xms1g -Xmx1g这样OOM发生时自动生成dump文件。分析dump用MATMemory Analyzer Tool打开.hprof文件。查看“Leak Suspects”报告系统会提示可能存在泄漏的类对象。查看“Dominator Tree”找占用内存最大的对象然后看他的GC Roots引用链确定是谁创建了这个大对象。常见OOM原因Redis或HTTP连接未关闭、ThreadLocal未清理、集合持有过多对象、StringBuilder拼接超大字符串、循环中的实体类被某个静态字段持有等。案例如果发现char[]数组很多且被HashMap$Node引用可能是一个Map缓存了太多商品数据没有过期策略内存持续增长。11. JWT 和 OAuth2 区别Spring Security 集成业务场景用户登录“飞机商城”浏览器/App需要携带凭证访问受保护接口。JWTJson Web Token一种开放标准RFC 7519将用户信息、过期时间签名后生成三段式tokenHeader.Payload.Signature。无状态服务端不需要存储Session每次请求验证签名即可。缺点无法主动吊销除非加黑名单Payload信息不能放敏感数据只是Base64加密。OAuth2一种授权框架用于解决第三方应用如何安全访问用户资源的问题。有四种角色资源所有者用户、客户端第三方应用、授权服务器、资源服务器。常见流程授权码模式用户点击“微信登录”微信授权后返回code服务器用code换取token再调用微信API获取用户信息。OAuth2关注的是“授权”JWT关注的是“令牌格式”。二者不是同一层次的东西。Spring Security 集成场景单应用登录集成spring-boot-starter-securityjjwt自定义OncePerRequestFilter从Header的Authorization: Bearer token中解析JWT校验签名和过期时间然后设置SecurityContext。微服务网关通常在网关统一验签内部服务之间用Spring Security OAuth2 Resource Server校验JWT并解析出角色权限。权限控制方法上使用PreAuthorize(hasRole(ADMIN))。12. Redis 限流CI/CD 流程与 Kubernetes 配置管理业务场景防止秒杀接口被脚本刷爆。限流方案计数器INCREXPIRE一个用户1秒内最多N次请求。滑动窗口用ZSET每次请求添加当前时间戳并清除窗口之外的记录统计窗口内数量。令牌桶Redis的Lua脚本实现每秒往桶里放N个令牌请求取令牌取到则通过。RedisLua是原子的性能高。使用Sentinel限流配置QPS阈值并使用Sentinel Dashboard监控。CI/CD 流程开发推代码到GitGitLab/GitHub。触发CI流水线Jenkins/GitLab CI编译、单元测试、代码扫描SonarQube、构建Docker镜像、推送镜像到仓库。更新Kubernetes应用使用kubectl set image deployment/order-service order-service:1.2.3或通过Helm/Argo CD滚动发布。健康检查K8s的readinessProbe和livenessProbe确保Pod就绪和存活。ConfigMap 和 SecretConfigMap存普通配置如application.yml、日志级别、连接池参数。以环境变量或挂载文件的方式注入容器。Secret存敏感信息如数据库密码、JWT密钥、第三方AK/SK。存储时Base64编码不是加密可以通过KMS加解密。两者的主要区别是用途不同Secret内容默认只分发到由Kubernetes调度的运行Pod节点更安全。以上就是本次面试涉及的全部技术点。谢飞机虽然有些问题回答得模棱两可但整体覆盖面不少。对于读者来说能完整掌握这些内容已经具备冲击互联网大厂Java初级/中级岗位的潜力了。记得回去后把HikariCP的并发设计和分布式事务的细节真正搞懂别再像谢飞机一样“回去等通知”了。
返回列表