大厂Java面试实录:Spring Boot/Cloud、Redis+Kafka、JVM调优、RAG/Agent(Spring AI)三轮递进拷打
角色
- 面试官(M):语气严肃,追问很细。
- 候选人小Y(Y):会一点,但不多;简单题能对答,难题开始"玄学输出"。
面试场景设定
你应聘的是某互联网大厂 电商+内容社区(UGC) 业务线,近期在做 AIGC 智能客服 (知识库问答 + 工单流转)。技术栈以 Java/Spring 生态 为主:
- 服务:Spring Boot + Spring Cloud + OpenFeign + Resilience4j
- 数据:MySQL(JPA/MyBatis)+ Redis + Elasticsearch
- 消息:Kafka
- 可观测:Micrometer + Prometheus/Grafana + ELK + Jaeger
- AI:Spring AI + RAG + 向量库(Milvus/Redis)+ Agent 工具调用(MCP/函数调用风格)
第一轮(基础到上手):做一个"订单查询+客服入口"服务
Q1(Spring Boot 基础)
M: 你用 Spring Boot 起一个"订单查询"接口,怎么做版本管理、依赖管理?Maven 里常见的依赖冲突怎么排?
Y: 就...spring-boot-starter-web 然后写个 @RestController。依赖冲突的话我一般 mvn clean install,不行就删 .m2 目录重来。
M: (沉默两秒)...你至少说出了一个"终极大法"。那你知道 dependency:tree 吗?
Y: 知道知道,树嘛,能看到树。
M: 行,先过。
Q2(JVM/JDK 基础)
M: 线上用 Java 8/11/17,你能说说它们在 GC 或运行时方面的差异吗?
Y: Java 17...更快?Java 11...也更快?反正越新越好。
M:(表情依旧严肃)"越新越好"不算答案。那你至少说说你熟悉的 GC:CMS、G1?
Y: G1 就是把内存切成很多块,然后...按块回收。
M: 这句还算对。继续。
Q3(数据库连接池与事务)
M: 订单查询走 MySQL,你用 HikariCP。为什么生产上推荐 HikariCP?怎么设置连接池参数?
Y: Hikari...快。参数就默认吧,一般不会出问题。
M: (终于给了点引导)默认不一定安全。比如你订单库有慢查询,会把连接占住,你的池子会被打满。那你知道 maximumPoolSize、connectionTimeout 这些怎么结合 QPS 算吗?
Y: 我一般先配个 200,顶不住再加。
M:(点头但不夸)至少你知道会"顶不住"。
Q4(缓存:Redis + Spring Cache)
M: 订单查询要加缓存,你会怎么用 Redis?怎么避免缓存穿透、击穿、雪崩?
Y: Redis 就 get/set。穿透就...加缓存;击穿就...加锁;雪崩就...多加几台 Redis。
M:(第一次夸)思路方向对了:穿透/击穿/雪崩是三件事。等下我会细问锁怎么加。
第二轮(进阶到分布式):UGC + 订单事件流
Q1(Kafka:异步解耦与一致性)
M: 下单后要发"订单已创建"事件,UGC 社区要展示"我买过这件商品"徽章。你为什么用 Kafka?如何保证"订单落库"和"发消息"不丢?
Y: Kafka 吞吐高。保证不丢就...多发几次?或者失败重试。
M: 你说的是"可能重复",但不等于不丢。那你知道 Outbox 模式或事务消息吗?
Y: 听过...Outbox 就是...有个 box。
M:(语气仍严肃)行,至少听过。
Q2(Spring Cloud:OpenFeign + Resilience4j)
M: 订单服务要调库存服务,用 OpenFeign。库存服务偶发超时,你怎么做超时、重试、熔断、限流?
Y: Feign 可以配超时。重试我觉得可以一直重试直到成功。熔断的话...把服务重启。
M:(轻轻叹气)一直重试会把下游打死。熔断不是重启。你应该提到 Resilience4j 的 CircuitBreaker/Retry/RateLimiter/Bulkhead。
Y: 对对对,就是那个...四件套。
Q3(分布式追踪与可观测)
M: 线上说"下单很慢",你怎么用 Micrometer + Prometheus/Grafana + ELK + Jaeger 排查?
Y: 看日志,看 Grafana。如果不行就加日志。
M:(终于给了鼓励)"加日志"是很多人最后的救命稻草。但大厂更希望你先用指标定位,再用 trace 找具体链路,最后用日志看细节。
Q4(数据库:索引与分页)
M: 订单列表分页查询,表很大。你用 MyBatis 或 JPA 都行。怎么做高性能分页?
Y: 用 limit offset。offset 大了就...加机器。
M:(严肃)offset 大了会扫很多行。你应该考虑"基于游标/主键的翻页"(seek method)和合适的联合索引。
Y: 嗯...游标我在 IDE 里用过。
M: 不是那个游标。
第三轮(高阶到AI落地):AIGC 智能客服(RAG + Agent 工具调用)
Q1(RAG:检索增强生成)
M: 智能客服要回答"我这单什么时候发货",大模型不能瞎编。你怎么用 RAG 降低 Hallucination?
Y: 我让模型"不要瞎编"。提示词写严一点。
M:(冷静)提示词只能缓解。你得把"事实"检索出来喂给模型,并且让它引用来源。
Q2(向量化与向量库)
M: 企业知识库(退换货政策、物流规则、活动规则)怎么入库?Embedding 模型怎么选?向量库选 Milvus/Chroma/Redis 有什么考虑?
Y: Embedding 就...把文字变成数字。向量库我觉得 Redis 最好,因为我熟。
M: 熟是优势,但要讲清楚:规模、召回、延迟、运维成本、过滤能力。
Q3(Agent:工具执行框架与MCP/函数调用)
M: 客服不仅要回答,还要能"查订单状态、发起退款、创建工单"。你怎么做 Agent 工具调用?怎么保证安全与审计?
Y: 我给模型一个接口文档,它就会调。
M:(严肃追问)模型不会"天然守规矩"。你得做工具白名单、参数校验、权限控制(OAuth2/JWT/Spring Security)、以及完整审计日志。
Y: 对对对,我们可以在网关里拦一下。
Q4(会话内存与多轮对话)
M: 用户说"我上次那单怎么还没到",又说"不是这单,是上上单"。你怎么做聊天会话内存?怎么避免把别人的信息串了?
Y: 用一个全局 Map 存起来?key 用用户昵称。
M:(终于露出一丝无奈)昵称会重复。你需要 userId+sessionId+过期策略,必要时落 Redis,并对敏感信息做脱敏与访问控制。
面试收尾
M: 今天先到这里。你基础还行,但复杂系统设计和 AI 落地细节需要补强。回去等通知吧,我们 HR 这两天联系你。
Y: 好的好的,我回去就把...那个 box...补上。
题目答案与小白可落地笔记(按三轮整理)
目标:把面试问题变成"能在真实业务里写出来/配出来/排查出来"的知识点。
第一轮答案:订单查询服务(Spring Boot + MySQL + Redis)
A1 Maven 依赖管理与冲突排查
业务点: 大厂项目模块多、依赖多,冲突是常态(如 Jackson/Netty/Guava 版本打架)。
做法:
- 使用 Spring Boot Parent/BOM 统一依赖版本:
spring-boot-starter-parent或dependencyManagement引入spring-boot-dependencies
- 排查冲突:
mvn -q dependency:tree -Dincludes=groupId:artifactId- 看是否出现多个版本(nearest-wins)
- 解决:
- 在
dependencyManagement锁版本 - 对不需要的传递依赖用
exclusions
- 在
A2 Java 8/11/17 与GC要点(面试常考)
业务点: 版本升级通常为了更好吞吐、更低停顿、更强观测能力。
要点速记:
- Java 8:默认 Parallel GC(Server 模式下常用 CMS/G1 需手动);CMS 已逐步淘汰。
- Java 11:LTS;G1 更成熟;JFR、容器支持更完善。
- Java 17:LTS;G1 进一步增强;ZGC/ Shenandoah 在部分发行版可用;整体运行时优化更多。
- G1 核心思想:Region 化内存 + 预测停顿时间,适合大堆、低停顿诉求。
排查工具链:
jcmd/jstack/jmap/jstat- GC 日志:
-Xlog:gc*(11+)
A3 HikariCP 为什么常用 & 参数如何估算
业务点: 高并发下连接池"过大"会压垮 DB,"过小"会把应用线程卡死。
为什么 HikariCP:
- 性能好、实现简洁、延迟低
- 对连接泄漏检测(leakDetectionThreshold)等支持较好
关键参数:
maximumPoolSize:连接池最大连接数minimumIdle:最小空闲连接connectionTimeout:取连接超时(避免线程无限等)idleTimeout/maxLifetime:连接存活与回收(需小于 MySQL wait_timeout)
粗略估算思路:
需要连接数 ≈ QPS × 平均SQL耗时(秒) × 安全系数- 例:2000 QPS,平均 SQL 10ms:2000×0.01=20;乘 2~3 的安全系数 → 40~60
A4 Redis 缓存三大问题与解法
业务点: 订单查询是典型读多写少,适合缓存,但要扛恶意流量与热点。
- 缓存穿透 (查不存在的订单号):
- 参数校验 + 布隆过滤器(Bloom Filter)
- 对"确实不存在"写入空值缓存(短 TTL)
- 缓存击穿 (热点 key 过期瞬间):
- 互斥锁/单飞(singleflight):只有一个线程回源
- 逻辑过期(value 带过期时间,后台异步刷新)
- 缓存雪崩 (大量 key 同时过期/Redis 故障):
- TTL 加随机抖动
- 多级缓存(Caffeine 本地 + Redis)
- Redis 高可用(哨兵/集群)+ 降级策略
第二轮答案:事件驱动 + 微服务治理 + 可观测
B1 Kafka 为什么用 & "落库+发消息不丢"的实现
业务点: 下单完成后要驱动多个下游(积分、库存、UGC 徽章、风控),同步调用会把链路拉长。
Kafka 价值:
- 异步解耦、削峰填谷、吞吐高、可回放(按 offset)
不丢消息的经典方案:Outbox(事务外盒)
- 订单服务本地事务:
- 写订单表
- 写 outbox 表(事件内容、状态)
- 后台任务/CDC(Debezium)读取 outbox,投递 Kafka
- Kafka 投递成功后标记 outbox 已发送
消费端幂等:
- 消费消息带
eventId,落库去重(唯一索引) - 处理失败重试 + 死信队列(DLQ)
B2 OpenFeign + Resilience4j 的正确姿势
业务点: 下游偶发超时是常态,要"可控失败"。
建议组合:
- 超时:连接超时 + 读超时(别太大)
- 重试:只对幂等请求重试(如 GET 查询库存),并设置退避(backoff)
- 熔断:CircuitBreaker(失败率阈值、半开探测)
- 隔离:Bulkhead(线程池/信号量隔离,防止被拖死)
- 限流:RateLimiter(保护下游与自身)
- 降级:Fallback(返回兜底、提示稍后再试)
B3 可观测三件套:指标、链路、日志
业务点: "慢"可能来自 DB、下游、GC、网络、锁竞争。
排查路径:
- Metrics(Prometheus/Grafana + Micrometer)
- QPS、P95/P99 延迟、错误率
- JVM:堆、GC 暂停时间、线程数
- 连接池:活跃连接、等待时间
- Tracing(Jaeger/Zipkin)
- 还原一次请求的调用链,找哪个 span 最慢
- Logging(ELK)
- 结合 traceId 检索日志
- 对关键路径打结构化日志(JSON)
B4 大表分页:别用深 offset
业务点: 订单列表越翻越慢是必现问题。
问题: limit offset offset 很大时,MySQL 仍需扫描大量行再丢弃。
解法:Seek/游标分页
- 以上一次最后一条记录的
(create_time, id)作为游标:where (create_time,id) < (?,?) order by create_time desc, id desc limit 20
- 配合联合索引:
(user_id, create_time, id)
第三轮答案:AIGC 智能客服(RAG + Agent + 安全)
C1 RAG 如何降低幻觉(Hallucination)
业务点: "物流状态、退款规则"必须以真实系统/文档为准。
RAG 基本流程:
- 用户问题 → 重写/归一化(可选)
- 向量检索召回相关文档片段(TopK)
- 将片段作为上下文(context)喂给 LLM
- 生成回答并引用证据(source),必要时输出"不确定/需人工"
关键策略:
- 让模型只基于 context 作答(指令约束)
- 设定置信度阈值:召回不足则转人工
- 输出带引用(文档名/段落ID/链接)
C2 知识库入库:Embedding 与向量库选型
入库流程:
- 文档加载(PDF/HTML/Markdown/数据库)
- 清洗与切分(chunking,按段落/标题;控制 token)
- Embedding 向量化(OpenAI/Ollama 等)
- 写入向量库(向量 + metadata,如业务线、版本、生效日期)
选型考虑:
- Redis 向量检索:运维简单、适合中小规模与低延迟场景;但在超大规模与复杂检索能力上可能不如专用向量库。
- Milvus:更偏大规模向量检索、索引类型丰富、适合企业级。
- Chroma:更轻量,适合原型/中小项目。
必须做的工程化:
- metadata 过滤(如只查"当前生效版本")
- 增量更新(Flyway/Liquibase 管理规则版本,或给文档加版本号)
C3 Agent 工具调用:别让模型"想调就调"
业务点: 客服要能执行动作(查订单、发起退款、建工单),属于高风险操作。
推荐架构:
- LLM 只负责"计划/意图识别"
- 工具调用由 工具执行框架 托管(Spring AI function calling / 自研 tool registry)
- 工具以白名单注册:
getOrderStatus(orderId)createTicket(userId, category, content)applyRefund(orderId, reason)
安全与审计:
- 身份认证:JWT/OAuth2(Spring Security)
- 授权:用户只能查自己的订单(ABAC/RBAC)
- 参数校验:Bean Validation + 业务校验(订单是否属于该用户)
- 审计日志:记录 toolName、参数、调用人、结果、traceId
- 敏感信息脱敏:手机号/地址
MCP(模型上下文协议)类思想落地:
- 将"工具描述、参数 schema、返回结构"标准化
- 让工具可扩展、可插拔、可测试
C4 会话内存:隔离、过期、可追溯
业务点: 多轮对话要"记得住",但不能串用户、不能泄露隐私。
实现建议:
- 会话 key:
userId + sessionId(不要用昵称) - 存储:短期内存 + Redis(带 TTL),必要时落库
- 记忆分层:
- 短期:本轮关键信息(订单号、意图)
- 长期:用户偏好(需用户授权)
- 合规:敏感信息加密/脱敏;权限校验贯穿工具调用
你可以怎么"补强小Y"
- 把 Outbox + 幂等消费 写成一套 demo
- 用 Resilience4j 给 Feign 做一套"超时+熔断+降级"
- 给订单接口接上 Micrometer 指标 + traceId 日志
- 用 Spring AI 做一个最小 RAG:文档切分 → 向量库 → 检索 → 引用回答
(全文完)