Java大厂面试实录:Spring Boot、Redis、Kafka、Elasticsearch、分布式事务与云原生高并发系统

Java大厂面试实录:Spring Boot、Redis、Kafka、Elasticsearch、分布式事务与云原生高并发系统

"咚、咚、咚。"

谢飞机推开玻璃门,带着三分紧张七分自信走进了面试间。面试官抬眼看了看,面前摆着简历和一台ThinkPad,屏幕上一堆代码。

面试官(下称"官"):坐。先说下你这个项目吧。你负责什么?用了哪个Java版本?构建工具是什么?

谢飞机(下称"飞"):我这个项目是一个UGC内容社区,用户可以发短视频、图文,还能评论、点赞、搜索。我主要做内容发布和审核这一条链路。Java从8升级到11,构建工具用得最多是Maven,Gradle也看过。后端是Spring Boot + Spring Cloud,数据库MySQL,ORM用MyBatis和JPA,缓存用Redis,消息队列用Kafka,搜索用Elasticsearch。

官:这个技术栈还比较主流。你们这种读多写少的社区,高并发下怎么扛?

飞:我们分了好几层:CDN抗图片和视频,Nginx做负载均衡,应用层用Caffeine本地缓存 + Redis分布式缓存。缓存过期处理上,用布隆过滤器挡穿透,用分布式锁防止热点key击穿,给缓存加随机过期时间防止雪崩。数据库这边做了主从分离,主库负责写,从库扛读,流量再大还能在K8s里扩Pod。

官:不错,这一套是标准答案了。那缓存和数据库一致性怎么做?

飞:Cache Aside。我们更新时先写数据库,再删缓存。如果删失败,就依赖过期时间;后来订阅了MySQL的binlog,用Canal去删Redis,保证最终一致。

官:你还知道Canal?可以。那Spring Boot的自动配置原理呢?

飞:这个我背过,不是,我理解过。@SpringBootApplication里有@EnableAutoConfiguration,它会读classpath下的要导出的自动配置类列表,比如spring.factories。每个自动配置类都有@ConditionalOnClass、@ConditionalOnMissingBean这些条件注解,满足条件才会创建Bean。像RedisAutoConfiguration,只要引入了Redis Starter并且没有自定义RedisTemplate,它就会自动给你配一个。

官:嗯,看来平时看过源码。那日志和监控呢?

飞:日志用SLF4J + Logback,链路追踪用Jaeger和Zipkin;监控用Micrometer + Prometheus + Grafana。我们还会把日志输出到ELK里做错误分析。

官:好,第一轮差不多了。接下来聊聊你们的内容审核模块。

谢飞机心说:终于到了我背得最多的部分。

官:用户上传内容后,审核流程怎么设计?现在AIGC内容这么多,你们怎么做?

飞:我们采用"先审后发"。上传后先把文件和元数据存到OSS和MySQL,状态是"待审核";然后发一条消息到Kafka。审核服务消费后并行做图片/视频审核、OCR、敏感词检测、重复度和AI生成识别。审核通过就把状态改成"已发布",同时把内容写入ES;不通过就进人工复审。AIGC内容会打上标签。

官:那你说说,为什么用消息队列?以及Kafka如何保证消息不丢失?把ISR、HW、LEO这些细节也说说。

飞:主要是削峰、异步、解耦。特别是像用户集中发布的时候,审核服务不会被打爆。Kafka消息不丢失...生产者要设acks=all和重试;Broker要有多副本和min.insync.replicas;消费者要手动提交offset,处理成功后再提交。ISR就是同步副本集合,HW是高水位,LEO是日志末端偏移量。具体怎么推进我有点忘了,我只记得Kafka通过副本机制保证高可用。

官:那最终一致性呢?如果审核服务消费失败或者重复消费怎么办?

飞:我们消费逻辑做了幂等,用Redis的setnx,用contentId作为key,保证同一个内容不会处理两次。处理失败的话,try-catch重试三次,还不行就发到死信队列,人工捞出来处理。至于分布式事务,我们没用到强事务,最终一致就行。

官:好,搜索呢?你们用ES,说说倒排索引。

飞:倒排索引就是"词语 -> 文档ID"的映射。比如一篇文章里有"苹果",那么"苹果"就会指向这篇文档。查询的时候直接查"苹果"就能拿到所有文章,不用扫全表。我们中文用IK分词器,ES里用MatchQuery。

官:那ES深分页问题怎么解决?

飞:我们用search_after。因为from+size超过一万会报错。MySQL那边如果要分页很深,我会用where id > 上一页最大id limit 10,或者用延迟关联。

官:嗯,这轮也算过关。再说说安全和运维。

谢飞机擦了擦汗。

官:用户登录鉴权怎么做的?JWT和OAuth2有什么区别?

飞:我们的登录是账号密码+短信验证码,成功后签发JWT,里面带userId和过期时间。Spring Security的过滤器会解析JWT,验证签名,把用户身份放到上下文里。第三方登录用OAuth2授权码模式,授权服务器用的Keycloak。区别的话,JWT是一种token格式,OAuth2是一个授权框架。OAuth2可以用JWT当access_token,也可以不用。

官:如何防止接口被刷和恶意攻击?

飞:首先Nginx加限流,每个IP限制QPS。然后应用层用Spring Security做权限控制,对敏感接口加验证码。我们还用Redis的令牌桶算法做了一个限流组件。另外防SQL注入用预编译,防XSS做输入过滤,HTTPS是必须的,WAF也上了。

官:项目怎么部署的?Docker和K8s里Java服务的JVM参数怎么设置?

飞:用Jenkins构建镜像,推到镜像仓库,然后K8s的Deployment拉取镜像滚动更新。JVM参数会放在环境变量JAVA_OPTS里,比如-Xms2g -Xmx2g -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dump。注意Java 11默认开了容器识别,所以-Xmx会按容器内存比例来算。

官:线上OOM了,你怎么排查?

飞:先看监控,如果OOM时自动导出了Heap Dump,就用MAT分析,看看是不是有大对象或者内存泄漏。有时候dump文件没有生成,就只能重启服务。如果是线程创建太多导致的OOM,那要查线程池和代码里的new Thread。嗯...大概是这样。

官:那压测时CPU飙到了100%,怎么定位?

飞:用top -Hp找到CPU高的线程,再用jstack把线程栈转出来,看十六进制线程ID对应的栈帧。如果看到GC线程,就调JVM;如果是业务线程,就看是不是死循环。很多时候是因为正则回溯、日志打印太多。这个我实际练过一次,但不太熟练。

官:好,今天聊得差不多了。你回去等通知吧。

谢飞机站起来,走出门口,长出一口气:"完了,我还没说我的测试框架是JUnit和Mockito呢......算了,反正也是背的。"


问题答案详解(小白学习版)

下面我们把面试中的问题逐一展开,用更完整的描述讲清楚。每一个问题都对应一个实际业务场景,小白可以对照着查漏补缺。

第一轮:项目架构与基础

1. 项目介绍、负责模块、Java版本、构建工具

业务场景:一个UGC内容社区,用户上传短视频/图文,其他用户浏览、点赞、评论、搜索。这是典型的互联网高并发场景:读多写少、热点集中、内容产生后有审核和索引需求。

技术点

  • Java 8 / 11 / 17:Java 8是长期支持版本,主流企业大量使用;Java 11带来的ZGC(实验性)、UseContainerSupport(容器内存自动感知)很实用;Java 17是LTS,Spring Boot 3强制要求。
  • Maven:依赖管理和构建生命周期,通过pom.xml统一管理第三方库。Gradle更灵活、构建更快,但Maven更通用。
  • Spring Boot + Spring Cloud:微服务基础框架。Spring Boot负责快速构建单服务,Spring Cloud提供注册中心、配置中心、网关、负载均衡等微服务组件。
  • ORM:MyBatis适合复杂SQL和动态SQL,Hibernate/JPA适合对象关系映射和标准CRUD。
  • 中间件:Redis(缓存)、Kafka(消息队列)、Elasticsearch(搜索)是互联网后端高频组件。
2. 高并发读如何扛?缓存设计

业务场景:用户刷首页视频流和详情页,流量高峰每秒几十万次读。如果全部打到MySQL,数据库会瞬间被击穿。

技术点

  • CDN / OSS:图片、视频等静态资源放对象存储,通过CDN加速,用户就近获取,不经过应用服务器。
  • Nginx:反向代理和负载均衡,分发流量到多个后端实例。
  • 应用层多级缓存:Caffeine本地缓存(服务进程内,最快) + Redis分布式缓存(多实例共享)。
  • 缓存穿透:查询一个一定不存在的key,导致每次都直穿数据库。解决:布隆过滤器(BloomFilter)先判断key是否存在;也可以将空值短时间缓存。
  • 缓存击穿:热点key正好失效,大量请求同时回源。解决:用Redis setnx 做互斥锁,只有一个线程回源,其他线程等待后从缓存读。
  • 缓存雪崩:大量key同时失效,请求全部打到数据库。解决:TTL加随机值;使用多级缓存;Redis高可用 + 持久化。
  • 数据库主从分离:主库写,从库读;进一步提升读能力。
3. 缓存和数据库一致性

业务场景:用户修改昵称后,立刻读到的可能还是旧昵称,这不可接受。需要保证缓存与MySQL最终一致。

技术点

  • Cache Aside Pattern(旁路缓存模式):
    • 读:先读缓存,命中则返回;未命中则读数据库,再回填缓存。
    • 写:先更新数据库,再删除缓存。
  • 为什么不更新缓存而是删除缓存?因为更新缓存需要重新生成数据,在高并发下容易产生覆盖旧值的问题;删除后下次读自然回填,更简单。
  • 删除缓存失败怎么办?引入重试机制(MQ异步重试),或者订阅MySQL Binlog(Canal),数据库变更后自动协调缓存删除。
  • 强一致性 vs 最终一致性:互联网业务大多接受最终一致(秒级)。如果要强一致,需要分布式事务或加锁,但会牺牲性能和可用性。
4. Spring Boot自动配置原理

核心@SpringBootApplication 是一个组合注解,包含:

  • @SpringBootConfiguration:标明是配置类。
  • @EnableAutoConfiguration:开启自动配置。
  • @ComponentScan:扫描当前包及其子包,装配Bean。

@EnableAutoConfiguration 通过 AutoConfigurationImportSelector 加载 META-INF/spring.factories(Spring Boot 2.7之前)或 META-INF/spring/...AutoConfiguration.imports(Spring Boot 2.7+)里的自动配置类。

每个自动配置类上都有一组条件注解:

  • @ConditionalOnClass:classpath中有某个类才生效。
  • @ConditionalOnMissingBean:用户没有自定义某个Bean时才生效。
  • @ConditionalOnProperty:配置项满足条件才生效。

例如 RedisAutoConfiguration,当你引入 spring-boot-starter-data-redis 后,classpath中有了 RedisTemplate 相关类,且你没有自己定义 RedisTemplate,Spring Boot就会自动创建一个。

5. 日志和监控

技术点

  • SLF4J:日志门面(接口),统一不同日志实现。
  • Logback:SLF4J的最好实现之一,性能高,支持滚动文件和异步输出。
  • ELK Stack:Elasticsearch + Logstash + Kibana,收集、存储和展示日志。
  • 链路追踪:微服务跨服务调用时,用 TraceId 串联一次完整请求;Jaeger / Zipkin 是主流工具。
  • Micrometer:监控门面,像SLF4J一样统一指标输出。
  • Prometheus:定时从应用拉取监控指标。
  • Grafana:将Prometheus数据可视化,并配置告警规则。

第二轮:内容审核与异步处理

6. 内容审核流程设计

业务场景:UGC内容必须"先审后发",否则会出现违规信息传播。同时AIGC大量生成内容,也需要识别和打标。

流程

  1. 用户上传内容 -> 文件存到对象存储(OSS),MySQL插入一条"待审核"记录。
  2. 发送MQ消息到 content.audit topic,消息体为 contentId
  3. 审核服务消费消息,执行以下审核:
    • 图片/视频:抽帧,调用第三方AI识别(色情、暴恐、政治敏感)、OCR文字识别。
    • 音频:转写为文本,再做文本审核。
    • 文本:敏感词库(DFA算法)、涉政/广告/引流内容检测。
    • 重复内容:对文本提取SimHash、对图片提取pHash,计算相似度,查重。
    • AIGC识别:用模型判断内容是否AI生成,并打上"AI生成"标签。
  4. 审核结果:
    • 通过:更新MySQL状态为"已发布",写入ES索引,通知feed服务。
    • 疑似违规:进入人工复审队列。
    • 确认违规:删除内容,按规则处罚用户。
7. 为什么用消息队列?Kafka如何保证消息不丢失?

消息队列三大作用

  • 异步:用户发布后立即返回"已提交",审核在后台执行。
  • 削峰:高峰期的任务先堆积在MQ,消费者按自身能力消费。
  • 解耦:内容服务和审核服务互不依赖,通过MQ通信。

Kafka保证不丢消息,需要从三个端分别保证

  • 生产者端:
    • acks=all(或 -1):必须等待所有ISR副本都写入成功。
    • retries 设置大于0:网络抖动/Leader选举时自动重试。
    • enable.idempotence=true:开启幂等,避免重试导致消息重复。
  • Broker端:
    • Topic replication.factor >= 3,即多个副本。
    • min.insync.replicas >= 2:至少两个副本写在ISR中才返回成功。
    • Leader挂了之后,从ISR中选举新Leader,避免消息丢失。
  • 消费者端:
    • 关闭自动提交:enable.auto.commit=false
    • 业务处理成功后再手动提交offset。
    • 消费逻辑必须幂等,防止重复消费。

ISR、HW、LEO

  • LEO(Log End Offset):每个分区日志最后一条消息的偏移量。
  • HW(High Watermark):高水位,表示可被消费者看见的最大偏移量。
  • ISR(In-Sync Replicas):与Leader保持同步的副本集合。HW和LEO的推进涉及副本同步机制,是Kafka可靠性、一致性最核心的细节。
8. 最终一致性、失败重试、重复消费

业务场景:用户发的内容进入审核队列后,审核服务可能宕机、消息可能重复。

技术点

  • 幂等:用 contentId 作为Redis setnx 的key,处理前先加锁,处理完释放,保证同一个消息只处理一次。
  • 本地消息表:业务表和消息表在同一数据库事务中写入,然后定时任务把未发送的消息发给MQ。
  • 事务消息:RocketMQ支持半消息(half message),先发送半消息,本地事务成功后再提交确认;Kafka事务API也可以实现,但模型不同。
  • 重试机制:消费失败捕获异常,最多重试3次,若仍然失败则写入死信队列(DLQ),由人工或补偿系统处理。
  • 分布式事务:内容审核这种场景不需要强事务,最终一致即可。如果跨库/跨服务需要强一致,可用Seata AT模式、TCC、Saga等。
9. Elasticsearch搜索和倒排索引

场景:用户搜索"Java面试",需要毫秒级返回相关视频、文章。

技术点

  • 正排索引:文档ID -> 文档内容。MySQL的 LIKE '%关键词%' 就是顺序扫描所有文档。
  • 倒排索引:词项 -> 文档ID列表。建立过程:
    1. 对文档进行分词。
    2. 过滤停用词、转小写。
    3. 建立词典和倒排列表。
    4. 查询时在词典中找到词项,直接获取文档列表。
  • ES的文档写入:文档被分词后生成倒排索引;查询时从分片并行检索,汇总后按相关性打分排序(BM25算法)。
  • 中文分词:默认分词器对中文不友好,一般集成 IK 分词器。
10. 深分页问题

技术点

  • ES的 from + size 深度分页:例如 from=1000000, size=10,每个分片都要取前1000010条再排序,协调节点进行全局排序,非常耗CPU和内存。默认超过10000条会报错。
    • scroll:适合全量导出,可以游标方式拉取,但不适合实时用户翻页。
    • search_after:必须指定唯一排序值(如 _id + 时间戳),只能下一页,不能随机跳页;性能稳定。
  • MySQL深分页:LIMIT 100000, 10 会扫描前100010行再丢弃,性能很差。
    • 方式一:延迟关联,先查 id,再回表:SELECT * FROM t INNER JOIN (SELECT id FROM t ORDER BY id LIMIT 100000, 10) tmp ON t.id = tmp.id
    • 方式二:记住上一页最大ID:WHERE id > last_max_id ORDER BY id LIMIT 10

第三轮:安全、部署与排障

11. 登录鉴权,JWT和OAuth2的区别

业务场景:用户登录后,需要证明"我是我",并且访问资源时服务端能校验身份。

技术点

  • JWT(JSON Web Token):一种Token格式,由三部分组成:
    • Header:算法和类型。
    • Payload:携带用户ID、角色、过期时间等非敏感信息。
    • Signature:签名,防止篡改。
  • JWT特点:服务端无状态,不保存Token;校验签名即可。但无法主动吊销,泄露后到期前都有效,所以需要刷新令牌(refresh_token)。
  • OAuth2:一种授权框架,解决"第三方应用如何访问用户数据"的问题。
    • 核心角色:资源所有者(用户)、客户端(应用)、授权服务器、资源服务器。
    • 常见流程:授权码模式(Code)、客户端凭证模式(Client Credentials)、密码模式(已不推荐)。
    • OAuth2提供 access_tokenrefresh_token,支持撤销,也能限制权限范围。
  • 关系:OAuth2是一种框架,JWT是一种Token载体。OAuth2授权服务器可以签发JWT作为 access_token,也可以签发不透明Token(opaque token)。
12. 如何防止接口被刷和恶意攻击

技术点

  • 网络层:
    • HTTPS加密传输,防止中间人篡改。
    • WAF(Web防火墙)拦截SQL注入、XSS、恶意扫描。
    • Nginx限流:limit_req_zone 按IP或用户维度限制QPS。
  • 应用层:
    • Spring Security:认证 + 授权。
    • 参数校验:Bean Validation,防止非法参数。
    • 防SQL注入:使用预编译 PreparedStatement,MyBatis中使用 #{} 而不是 ${}
    • 防XSS:用户输入转义、富文本内容白名单过滤。
    • 防CSRF:敏感请求校验CSRF Token,或使用 SameSite Cookie。
    • 接口幂等:前端生成唯一请求ID,后端用Redis setnx 防止重复提交。
    • 限流:令牌桶算法(Guava RateLimiter、Resilience4j)或滑动窗口(Redis Lua)。网关层可用Sentinel。
13. Docker/K8s中Java服务的JVM参数设置

部署流程

  • Jenkins / GitLab CI 拉取代码,执行Maven构建,生成jar包。
  • 编写Dockerfile,基础镜像使用 eclipse-temurin:17-jre-alpineopenjdk:11-jre-slim
  • 将jar包和JVM启动参数放入镜像。
  • 推送镜像到私有仓库,K8s Deployment拉取镜像,设置CPU/内存requests和limits。
  • 滚动更新和弹性扩容由K8s管理。

JVM参数注意点

  • Java 8u191之前,JVM不感知容器内存限制;Java 10+默认开启 UseContainerSupport
  • 常见参数:
    • -Xms4g -Xmx4g:堆大小,通常设为容器内存的75%左右。
    • -XX:+UseG1GC:G1垃圾收集器,适合大堆和低停顿。
    • -XX:MaxGCPauseMillis=200:G1目标停顿时间。
    • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dumps:OOM时自动生成堆转储。
    • -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m:元空间限制。
    • -Djava.security.egd=file:/dev/./urandom:加快启动。
  • 启动命令:java $JAVA_OPTS -jar app.jarJAVA_OPTS 通过K8s环境变量传入。
14. 线上OOM排查

排查步骤

  1. 如果配置了 -XX:+HeapDumpOnOutOfMemoryError,去 HeapDumpPath 下找 .hprof 文件。
  2. 使用MAT(Memory Analyzer)打开 .hprof
    • "Leak Suspects" 一眼看出可疑大对象。
    • "Dominator Tree" 查看对象持有关系。
  3. 如果没有dump,可以手动用 jmap -dump:live,format=b,file=app.hprof <pid> 导出(线上慎用,会STW)。
  4. 分析GC日志:
    • Java 8:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
    • Java 11+:-Xlog:gc*:file=gc.log
  5. 常见OOM类型:
    • Java heap space:堆内存不足(大对象、内存泄漏、加载数据过多)。
    • Metaspace:元空间不足,反射/动态类太多。
    • GC overhead limit exceeded:GC几乎一直在工作但收效甚微。
    • unable to create new native thread:线程数达到系统限制,比如无限创建线程。
    • Direct buffer memory:堆外内存NIO buffer未释放。
15. 压测时CPU飙升定位

定位步骤

  1. top 查看哪个进程CPU高,记录PID。
  2. top -Hp <pid> 查看进程中所有线程,找到CPU占用最高的线程TID。
  3. printf "%x\n" <TID> 转换成十六进制。
  4. jstack <pid> > jstack.log,然后在日志中搜索 nid=0x十六进制
  5. 观察线程栈顶部:
    • 如果显示 GC threads / VM Thread,说明GC压力大,需要看GC日志,调整堆大小、GC算法,检查代码中是否频繁创建大对象。
    • 如果显示业务代码,比如 dead loop、无限递归、正则回溯、密集日志输出,就优化对应代码。
  6. 也可以使用Arthas:thread -n 3 一键查看CPU最忙的线程。

压测时CPU飙升并不可怕,怕的是没有现场分析。所以线上环境务必提前开启JVM自动dump、保留GC日志、接入监控告警。

以上所有问题的答案都是"面试官真正想听到的关键点"。小白学习时不要只背概念,要放到业务场景中去理解。谢飞机虽然背了不少东西,但细节不扎实,所以只能回家等通知。希望正在准备面试的你,能真正把每个技术点吃透,拿下Offer。

相关推荐
嵌入式阿蔡1 小时前
面试高频考点 01:volatile / 中断 / 堆栈 八股精讲
java·面试·职场和发展·嵌入式实时数据库
秋饼2 小时前
LangChain4j + Java 实现企业级 Text-to-SQL 智能问数系统:从自然语言到安全可控的数据洞察
java·ai·技术分享·后端开发
Escalating_xu2 小时前
【C++ STL简介】从六大组件到容器、迭代器与算法协作
java·c++·算法
何以解忧,唯有..2 小时前
Python os模块详解:文件与目录操作指南
java·服务器·python
Csxyzj2 小时前
kubernetes集群部署方法
java·linux·kubernetes
SQL-First布道者2 小时前
全面解构传统持久层框架,拥抱真正的 SQL-First
java·数据库·spring boot·sql·spring·mybatis·spring jdbc
小的~~3 小时前
面试被问分布式锁,我差点语塞…直到搞懂了Redis、ZooKeeper和etcd的“三国杀”
redis·分布式·面试
小蒜学长3 小时前
vue旅游攻略网站(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端·旅游
凤山老林3 小时前
轻量级规则引擎落地:Spring Boot 集成 LiteFlow 实现动态业务编排与热更新
java·spring boot·后端·规则引擎·liteflow
夏贰四3 小时前
管控数据加载调度如何规避任务冲突?数据加载运维监控体系该如何搭建?
java·大数据·运维