大厂Java求职面试实战:Spring Boot自动配置、Maven/Gradle、ORM选型、OpenFeign、Kafka、Redis缓存、JWT/OAuth2、K8s排查、JVM调优与秒杀系统设

大厂Java求职面试实战:Spring Boot自动配置、Maven/Gradle、ORM选型、OpenFeign、Kafka、Redis缓存、JWT/OAuth2、K8s排查、JVM调优与秒杀系统设 大厂Java求职面试实战Spring Boot自动配置、Maven/Gradle、ORM选型、OpenFeign、Kafka、Redis缓存、JWT/OAuth2、K8s排查、JVM调优与秒杀系统设计谢飞机搞笑实录“下一位谢飞机。”谢飞机整理了一下自己印着Hello World的T恤推门走进面试间。面试官老马坐在对面脸上没有多余的表情桌面上放着一台笔记本和一杯凉透的咖啡。“坐吧。先简单介绍一下你自己和你做过的项目。”谢飞机清了清嗓子“我叫谢飞机今年毕业后一直在搞Java后端。最近做的是一个电商平台用Spring Boot MySQL Redis微服务架构服务之间用Feign调用消息用Kafka前端用Vue。基本上就是现在的互联网主流技术吧。”老马点了点头“行那我们先从基础开始。”第一轮基础与框架面试官“你刚才提到Spring Boot那你说说Spring Boot的自动配置是怎么实现的”谢飞机眼睛一亮这个他知道“Spring Boot的自动配置是通过EnableAutoConfiguration注解实现的。它里面会通过Import导入一个AutoConfigurationImportSelector这个类会扫描META-INF/spring.factories文件里的EnableAutoConfiguration配置项把里面列出的自动配置类加载到Spring容器里。然后再通过ConditionalOnClass、ConditionalOnBean这些条件注解判断按需生效。”老马微微点头“不错基础还算扎实。那构建工具你常用哪个说说Maven和Gradle的区别。”谢飞机挠了挠头“两个都用过。Maven是XML配置生命周期固定团队里用得比较多Gradle是基于Groovy和Kotlin DSL的配置更简洁支持增量构建和并行构建速度更快。我个人更喜欢Gradle但是公司项目用的是Maven。”老马“你觉得在项目里应该怎么选”谢飞机支支吾吾“呃……看公司习惯如果从零开始可能用Gradle更好但是Maven生态更老…有更多现成的例子。嗯……差不多就这样。”老马没追问换了个话题“那你ORM框架用过哪些MyBatis和Hibernate的区别是什么”谢飞机松了口气“这两个都用过。Hibernate是JPA实现全自动ORM不用写SQL通过实体类自动生成表结构对简单CRUD很方便MyBatis是半自动的SQL要自己写但灵活性高容易优化复杂查询。我的项目里主要是用MyBatis因为电商的查询逻辑太复杂了。”老马“那HikariCP你知道为什么比C3P0和Druid快吗”谢飞机有点慌“啊……HikariCP它…它是用Java字节码优化过的吧还有就是连接池大小设置成CPU核数1缓冲区比较小呃…反正就是快具体原理我忘了。”老马没有继续纠缠“行我们再聊聊微服务这块。”第二轮微服务与分布式面试官“你刚才说你的电商项目是微服务架构那订单服务和库存服务之间是怎么通信的”谢飞机“订单服务调用库存服务最开始用的OpenFeign走HTTP同步调用。后来发现调用量太大而且库存扣减要求响应快、还要削峰就改成消息队列Kafka异步了。”面试官“为什么要改成异步怎么保证Kafka消息不丢失”谢飞机“因为是高并发场景同步调用会占用请求线程而且如果库存服务挂了订单就会失败。改成Kafka以后订单服务把扣减库存的消息发到broker库存服务订阅后异步处理这样就能削峰填谷实现最终一致性。”面试官“具体怎么保证消息不丢失”谢飞机“嗯……生产端用ack机制发送失败重试broker用副本存储三个副本消费端手动提交offset。差不多这样吧。”面试官“那如果重复消费呢怎么保证幂等”谢飞机“……啊可以用Redis setnx或者数据库唯一索引这个我了解过但没具体实现过。”面试官没说话继续问“你们的缓存是怎么设计的怎么解决缓存穿透、击穿和雪崩”谢飞机这个会“穿透的话用布隆过滤器或者缓存空对象击穿的话可以用互斥锁或者热点数据永不过期雪崩的话就是过期时间加随机值避免同时失效再加搭建Redis集群实现高可用。”面试官“你说的互斥锁具体怎么做”谢飞机开始含糊“就是……请求来了如果发现缓存没有就加锁锁拿到以后再去数据库查然后回填缓存。锁可以用Redis的set NX……具体代码我记不太清了。”面试官又转向另一个问题“用户登录模块怎么做的JWT和OAuth2有什么区别”谢飞机“登录用的JWT用户登录成功后后端签发一个token前端每次请求带在Header里然后拦截器解析token。OAuth2是用来做第三方授权登录的比如微信登录。JWT是token格式OAuth2是授权流程两个不是同一个层面的东西。”面试官“那你清楚JWT怎么防篡改和过期吗”谢飞机“JWT有签名用HS256或者RS256加密服务端校验签名。过期用exp字段拦截器检查。”面试官“如果用户退出了JWT还能用吗”谢飞机愣住了“啊……JWT是无状态的可以设置Redis黑名单但这样就不完全无状态了。实际项目里……好像没做。”第三轮云原生与高并发场景面试官“你们的项目部署在Kubernetes上吗”谢飞机“是的用的Docker容器K8s管理有deployment和service。”面试官“怎么做健康检查和滚动更新”谢飞机“健康检查……嗯就是加一个Actuator的health端点然后K8s那配置livenessProbe和readinessProbe。滚动更新……就是设置maxUnavailable和maxSurge。”面试官“如果Pod起来了但是进程还连不上数据库K8s会不会把它杀掉再重启”谢飞机“这个……应该会吧因为如果readinessProbe失败就不会流量进来。但liveness……如果也失败可能会重启。但具体配置参数我记不得了。”面试官“你刚才说项目遇到过高并发那如果订单服务CPU飙升或者频繁Full GC你怎么排查”谢飞机“我会先重启一下服务看看能不能缓解。”面试官脸色微微一沉“重启可以暂时缓解但问题还在。如果线上真的出问题你第一件事做什么”谢飞机额头冒汗“先用top命令看CPU最高的进程然后用jstack导线程快照看有没有死锁。再用jmap看堆内存抓dump文件分析。如果是内存泄漏就看那些对象占满了堆。”面试官“那怎么定位到具体类或者方法”谢飞机“呃……用MAT分析dump或者看gc日志这个我实际操作过但当时是同事帮的我打下手。”面试官没有再追问给出了最后一个场景题“假设你负责设计一个‘大促秒杀’活动面临超预期的流量。你会如何设计技术方案从用户点秒杀按钮到库存扣减成功说说你的思路。”谢飞机“秒杀嘛首先用户请求先经过CDN和Web层到应用层之前用网关和Nginx做限流用Redis缓存商品信息和库存先扣减Redis里的库存成功了再异步发送消息给下单系统数据库做最终扣减。”面试官“如果Redis里的库存扣减成功但是异步下单失败了怎么办”谢飞机“那就会导致用户看到‘秒杀成功’其实订单没生成。可以用本地消息表或者MQ的重试机制实在不行人工补偿。”面试官“那数据库的库存怎么保证最终一致会不会超卖”谢飞机“数据库扣减的时候用UPDATE stock SET count count - 1 WHERE id #{id} AND count 0。用乐观锁或者就用这个条件更新如果影响行数为0就说明没了。”他说得越来越没底。这时候面试官看了一眼手表说“最后一个简单的问题。HTTP的GET和POST有什么区别”谢飞机终于松了一口气“GET参数在URL上有长度限制POST参数在Body里更安全GET会被浏览器缓存POST不会GET是幂等的POST不是。”面试官点了点头“基本对。好了今天的面试就到这里你回去等通知吧。”谢飞机收拾好简历心想“等通知……这个‘通知’大概率是‘无音讯’吧”面试问题答案详解第一轮问题详解1. Spring Boot自动配置原理Spring Boot的自动配置核心是EnableAutoConfiguration。它通过Import(AutoConfigurationImportSelector.class)引入一个选择器选择器会扫描classpath下所有META-INF/spring.factories在Spring Boot 3.0以后改为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中的EnableAutoConfiguration配置项将其中声明的自动配置类注册为BeanDefinition。而这些自动配置类上面通常带有多个条件注解例如ConditionalOnClass当classpath中存在指定的类时生效。ConditionalOnMissingBean当容器中不存在指定Bean时生效。ConditionalOnProperty当配置文件中存在指定属性时生效。ConditionalOnWebApplication当前是一个Web应用时生效。以RedisAutoConfiguration为例它标注了ConditionalOnClass(RedisOperations.class)用户添加了spring-boot-starter-data-redis后条件成立Spring Boot会自动创建RedisTemplate、StringRedisTemplate等Bean。这样用户不需要手动写配置类只需要引入依赖并加配置项即可。2. Maven vs GradleMaven是一个基于POMProject Object Model的构建工具使用XML格式描述项目结构、依赖、插件和目标生命周期。Maven的生命周期分为clean、default、site三部分其中default生命周期包含validate、compile、test、package、verify、install、deploy等阶段。优点规范统一、插件生态成熟、集中式仓库Maven Central。缺点XML冗长构建速度慢不支持灵活任务配置。Gradle使用了Groovy或Kotlin DSL脚本build.gradle、build.gradle.kts通过有向无环图计算任务之间的依赖关系支持增量构建、构建缓存和并行执行因此构建速度更快。默认使用Apache Ivy/Ivy? 其实内部有自己的依赖解析引擎。它兼容Maven仓库支持非常灵活的自定义任务。缺点构建脚本需要学习Groovy/Kotlin团队约定成本高配置灵活性过大可能导致风格不一致。选择建议新项目且团队熟悉Groovy/Kotlin时优先Gradle传统企业项目或要求严格统一规范则Maven更稳妥。面试时重点突出Gradle的增量构建和Maven的生态。3. MyBatis vs Hibernate| 维度 | Hibernate/JPA | MyBatis | |------|--------------|---------| | SQL控制 | 自动生成SQL开发者用HQL/JPQL/Criteria | 开发者手写SQL完全掌控 | | 学习成本 | 高需要理解ORM映射和缓存原理 | 低会SQL就能上手 | | 性能优化 | 需要调优一级二级缓存、懒加载、批量操作 | 原生SQL易于针对具体SQL加索引、分库分表 | | 复杂查询 | 不适合动态多表join及复杂子查询 | 动态SQL强大if、foreach等 | | 自动建表 | 支持通过hbm2ddl.auto或jpa生成 | 一般不自动生成靠脚本管理 | | 缓存 | 一级缓存默认二级缓存可配置 | 有简单一级缓存可接Ehcache/Redis但需自行编写 |使用场景MyBatis适合互联网高并发、业务复杂、SQL优化频繁的项目Hibernate适合业务模型清晰、CRUD为主、需要快速开发的企业系统。很多团队把两者混用Spring Data JPA MyBatis也是一套常见组合。4. HikariCP为什么快HikariCP号称“Java连接池中的性能王者”原因包括字节码精简使用细粒度优化减少本地字节码生成类加载量小。无锁集合设计核心使用ConcurrentBag采用线程局部存储和弱引用来获取连接避免大量锁竞争。更小的对象分配尽量减少ConnectionState等包装对象复用底层对象。优化的代理采用字节码动态生成代理连接比JDK动态代理更快。合理默认配置maximumPoolSize默认10并提示按CPU核数和磁盘IO调整minimumIdle默认与最大相同避免动态创建连接带来的延迟。此外它的连接释放、超时检测等逻辑也更简洁。C3P0已基本退出历史舞台Druid则提供监控和SQL防注入但纯性能上市HikariCP确实更快。第二轮问题详解5. OpenFeign vs Kafka 在服务通信中的选型在微服务架构中同步通信常用OpenFeignHTTP REST实现声明式调用。优点简单直观可读性好易于调试缺点导致服务间强耦合调用时占用线程在高并发下容易造成线程阻塞和级联故障。异步通信常用Kafka、RabbitMQ等消息队列。场景订单服务创建订单后需要通知库存服务扣减库存、通知积分服务累计积分、通知推送服务发短信。如果同步调用链路响应时间等于所有下游耗时之和且每个服务挂掉都会影响主流程。改为投递到Kafka后订单服务只需要保证消息写入成功下游各自消费实现解耦、削峰、提高吞吐。Kafka保证不丢失消息需要生产者Broker消费者三层配合生产者设置acksall发送失败同步或异步重试开启幂等enable.idempotencetrue避免重复消息。Brokerreplication.factor3min.insync.replicas2保证分区主从都有数据副本。消费者手动提交offsetenable.auto.commitfalse等业务处理成功后再提交。重复消费问题因为消费者可能在提交offset前崩溃重启后会从上次提交的offset开始消费从而重复。解决方案消费逻辑幂等比如在数据库表中对order_id建唯一索引插入冲突就更新。使用Redis SetNX标记消息ID处理成功后设置存在再处理时跳过。本地消息表/事务表记录消费过的消息ID。6. Redis缓存穿透、击穿、雪崩的应对穿透查询不存在的数据缓存和DB都没有请求直接打到数据库。解决布隆过滤器提前加载所有合法key到布隆过滤器命中后再查缓存。缓存空对象即使查询结果不存在也缓存一个空值TTL设置短一些如1分钟。参数校验对非法请求如id小于0直接拦截。击穿一个热点key过期同一时刻大量请求同时打到DB。解决互斥锁只让一个线程重建缓存其他线程等待或返回旧值。热点数据永不过期不给key设过期时间而是后台异步更新。逻辑过期value中保存过期时间异步线程发现快过期时主动刷新。雪崩大量key同时过期或Redis宕机。解决过期时间加随机值set key value EX expire random(1000)避免大面积同时失效。Redis高可用主从哨兵或Redis Cluster分片。多级缓存本地缓存Caffeine/Ehcache作为二级即使Redis不可用部分请求仍可被本地缓存命中。限流降级数据库请求限制流量开启熔断保护。互斥锁的具体做法用SET key value NX EX 10尝试加锁只有成功后查询DB并写缓存其他请求可以while循环睡眠重试或者如果维护的是旧值就先返回旧值后台异步更新。7. JWT与OAuth2的关系JWTJSON Web Token是一种token格式由Header、Payload、Signature三部分组成用Base64URL编码签名保证数据完整性。JWT可以自包含用户信息服务端无需存session适合分布式场景。OAuth2是一种授权框架定义了授权码、隐式、密码、客户端凭证四种授权模式解决“第三方应用如何安全访问用户资源”的问题。它不规定token格式但实践中常用JWT作为访问令牌载体。区别JWT解决“token怎么生成和校验”OAuth2解决“批准谁访问什么资源”的流程。两者常组合使用比如Spring Security OAuth2默认生成JWT令牌。JWT防篡改HS256使用对称密钥随便改会验签失败RS256使用私钥签名公钥验签。业务系统通过解密和验签获取用户身份再通过exp、nbf校验有效期。退出登录由于JWT无状态退出时服务端无法主动让token失效。常规做法引入Redis黑名单登出后把jtitoken ID加入黑名单并设置剩余有效期或者缩短token有效期配合RefreshToken刷新。第三轮问题详解8. K8s健康检查与滚动更新K8s探针有三种livenessProbe存活探针检测进程是否存活失败会重启容器。readinessProbe就绪探针检测Pod是否准备好接收流量失败则从Service Endpoints中移除不会终止进程。startupProbe启动探针用于慢启动容器避免liveness在启动阶段误杀。Spring Boot配合Actuator暴露/actuator/health端点作为探针路径。配置示例livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 10 periodSeconds: 5 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 5 periodSeconds: 10如果readiness失败Pod不会立即被杀掉而是不接收流量liveness失败才会重启。若数据库连不上通常readiness失败因为Spring Boot的ReadinessState会标记为DOWN但liveness可能仍为UP所以不会重启避免“数据库没恢复时频繁重启”。滚动更新Deployment设置strategy.type: RollingUpdatemaxUnavailable控制在更新期间允许不可用最大副本数maxSurge控制允许超出期望副本数的最大数量。例如副本5maxUnavailable: 1maxSurge: 1则更新时先新增1个新Pod再暂停下线旧Pod保持服务可用。minReadySeconds用于设定新Pod就绪后等待多少秒稳定运行。9. CPU飙升与Full GC排查线上JVM排查思路以Linux为例top或uptime看负载用top -Hp pid或ps -mp pid -o THREAD,tid,time找到CPU最高的线程tid。printf %x\n tid把十进制tid转十六进制nid。jstack pid | grep nid -A 20打印对应线程栈查看哪个方法正在疯狂执行是业务循环还是GC线程。如果是GC线程频繁导致CPU高先看GC日志jstat -gcutil pid 1000 10观察FGC频率和FCT。频繁Full GC通常意味着堆内存不足或存在内存泄漏。jmap -dump:formatb,fileheap.hprof pid导出堆快照用MAT或VisualVM分析大对象、GC Roots引用链。常见问题集合类无限制增长HashMap增加数据未清除、缓存在堆中过大、数据库连接/IO流未关闭。若排查出业务代码Bug紧急修复发版若内存不足调整-Xmx优化对象结构或使用本地缓存。注意线上进程不要轻易重启否则线程栈和heap dump都会丢失问题无法定位。10. 秒杀系统设计秒杀是典型的高并发、高可用、数据一致性场景。架构分三层前端/流量层采用CDN加速静态页面用户点击按钮后JS限制重复提交使用NginxLua进行IP限流令牌桶算法每用户每秒限制N次请求。活动页面静态化秒杀按钮可提前“置灰”。应用/缓存层秒杀请求先进入网关网关层做全局限流如Sentinel机制再经过分布式缓存Redis。热点商品数据提前预热到Redis或本地缓存Caffeine。核心逻辑校验用户是否已秒杀成功用SETNX在Redis创建订单ID。检查库存DECR stock_keyRedis原子减如果结果小于0则回补并返回失败。减成功后发送消息到Kafka消息内容包含用户ID、商品ID、时间戳。异步消费者创建订单、扣减数据库库存。数据层数据库库存更新使用条件更新UPDATE inventory SET stock stock - 1 WHERE product_id #{pid} AND stock 0若影响行数为0说明库存不足记录幂等结果。利用Kafka的消费分区顺序和手动提交保证不遗漏订单。一致性问题Redis扣减成功而DB扣减失败时需要补偿机制消费者处理失败后重试超过重试次数进入死信队列由定时任务扫描并回补Redis库存。更可靠的做法是把库存数量也保存在MySQL事务中Redis只是预热副本最终以DB为准。秒杀中的保护手段还包括使用MQ对下游库存系统限流、订单服务开启熔断回退、服务降级返回“排队中”、支付页动态通知。核心思想是“前端拦截、缓存承接、MQ削峰、DB严格限扣”。最后GET vs POST总结GET用于获取资源参数在URL上长度受浏览器和服务器限制相对不安全会暴露在历史记录中可缓存幂等POST用于提交数据参数在请求体中没有长度限制不可缓存一般不幂等同一请求会创建多份资源。接口规范中还要注意GET的URL用于定位资源POST用于操作资源或触发行为。实际开发中Spring MVC通过GetMapping/PostMapping区分请求体用RequestBody。面试官老马最后对谢飞机说“回去等通知”一半是客套一半是观察。而谢飞机是否能拿到offer取决于他能不能把那些“含糊”的答案变成真正的实战经验。希望这篇文章能帮到每一位正在准备Java面试的求职者。