大厂Java面试实录:Spring Boot + JVM + Redis/Kafka + 微服务治理 + Spring AI/RAG 一条龙
场景:某互联网大厂【电商内容社区 + UGC + AIGC 智能客服】团队面试。
角色:
- 面试官:严肃、节奏快,喜欢追问落地细节。
- 小Y:水货但嘴硬,简单题能答出来,复杂题开始"嗯...大概...应该..."。
第一轮(基础到中阶):单体服务扛得住吗?
业务背景:电商APP首页有"内容社区/UGC种草"信息流,后端是 Spring Boot 单体起步。大促时 QPS 暴涨,出现接口超时、CPU飙高。
Q1:JVM 里对象创建很频繁,为什么会导致 GC 抖动?你会怎么排查?
面试官 :大促期间你们信息流接口 GET /feed 超时,CPU 90%,你怀疑是 GC。说说对象创建频繁为什么会导致 GC 抖动?如何排查?
小Y :GC 抖动嘛...就是一直 GC。对象太多就会...回收不过来。排查的话我一般 jps 看一下,然后 jstat 看一眼......
面试官 :jstat 只是开始。你至少得讲出:Young GC 频率、晋升失败、Old GC、停顿时间,以及如何用堆转储定位热点对象。
小Y:嗯对对对...可以 dump 一下...然后用 MAT 分析......
面试官:还行,方向对。继续。
Q2:Java 并发下,为什么用线程池而不是每次 new Thread?线程池参数如何按接口类型设置?
面试官 :信息流接口做了并行聚合:商品、库存、推荐、广告。你用线程池还是 new Thread?线程池参数怎么拍?
小Y:肯定线程池啊,不然线程太多。参数嘛...核心线程数就 CPU 核数,最大线程数再大一点。队列随便用个 LinkedBlockingQueue。
面试官:随便?那你就等着 OOM。你得区分 IO 密集 vs CPU 密集,还要说明拒绝策略、队列长度、线程命名、可观测性。
小Y:嗯...是是是...我回去再细化一下。
Q3:Spring Boot 中 HikariCP 为什么快?连接池你会怎么避免连接泄漏?
面试官:你们用 MySQL,连接池从 C3P0 换到 HikariCP,说说为什么快?怎么防止连接泄漏?
小Y:HikariCP 就是轻量...快。泄漏的话...用完 close?
面试官:回答太"水"。不过你至少知道 close。再补点:超时、检测、监控指标。
小Y :嗯...可以设置 leakDetectionThreshold?
面试官:这句终于像干过活的。
Q4:Redis 做缓存,如何解决缓存穿透、击穿、雪崩?并结合"UGC内容详情"场景说一下。
面试官 :UGC内容详情 GET /note/{id},热点内容被刷爆。缓存三大问题怎么解?
小Y:穿透就...布隆过滤器?击穿就加锁。雪崩就加随机过期。
面试官:不错,这题答得干净。可以继续深入了。
第二轮(进阶):微服务化后怎么不翻车?
业务背景:单体拆成微服务:内容服务、推荐服务、订单服务、用户服务。开始上 Spring Cloud/OpenFeign,消息用 Kafka。大促时出现:调用链很长、超时叠加、偶发雪崩。
Q1:OpenFeign 调用为什么会"越调越慢"?你怎么做超时与重试的治理?
面试官:内容服务调推荐服务,偶发 RT 3s 变 10s。Feign 为什么越调越慢?治理怎么做?
小Y:可能网络慢...或者对方慢...我们把超时调大点?
面试官:调大点是等死。要讲:超时分层、重试幂等、隔离与熔断。
小Y:嗯...可以用 Resilience4j 熔断限流?
面试官:终于提到关键组件了。
Q2:Kafka 在"订单创建事件"场景如何保证不丢消息、不重复消费?
面试官 :下单成功后发 OrderCreated 事件给推荐和营销。Kafka 怎么做到不丢?不重复?
小Y:不丢就...acks=-1?不重复...消费者自己去重?
面试官:能说出来 acks 还行。去重怎么做?
小Y:用数据库唯一键?或者 Redis set?大概是这样。
面试官:OK,算及格。
Q3:链路追踪怎么落地?Micrometer + Prometheus + Grafana + Jaeger/Zipkin 你会怎么选?
面试官:大促你怎么快速定位"慢在哪里"?讲讲指标、日志、链路三件套。
小Y:日志用 ELK,指标用 Prometheus,链路用 Jaeger?
面试官:选型没问题。关键是:你怎么把 traceId 串起来,怎么把慢 SQL、线程池队列长度都指标化。
小Y:嗯...用 MDC?然后埋点...Micrometer?
面试官:可以,再往下。
Q4:Kubernetes 上灰度发布怎么做?CI/CD 用 Jenkins/GitLab CI 你怎么设计流水线?
面试官:你们上了 K8s。新版本推荐服务想灰度,怎么做?流水线怎么配?
小Y:Jenkins 打包 Docker 镜像,推镜像仓库,然后 kubectl apply。灰度...可以搞两个 deployment?
面试官:太粗糙。至少得讲 RollingUpdate、金丝雀、流量分配、回滚与健康检查。
小Y:嗯...回滚就是把镜像 tag 改回去......
面试官:回去好好补补。
第三轮(高阶&加分):AIGC 智能客服怎么接入又不"胡说八道"?
业务背景:你们要做【电商智能客服 + 企业知识库问答】。需求:
- 用户问"怎么退货/发票/物流异常"
- 模型回答要基于企业文档,不能瞎编(Hallucination)
- 要支持复杂流程:查订单、查物流、创建工单
Q1:用 Spring AI 做 RAG,你的链路设计是什么?需要哪些组件?
面试官:RAG 说说链路,别背概念。给我一条从"用户提问"到"答案返回"的工程流程。
小Y:就是...先把问题丢给大模型,然后它会回答...RAG 就是再查一下资料?
面试官:你这叫"祈祷式AI"。你要讲:文档加载、切分、向量化、向量库检索、提示填充、引用来源。
小Y:嗯...向量库可以用 Milvus 或者 Redis?Embedding 用 OpenAI 或 Ollama?
面试官:这句可以,加分。
Q2:怎么降低 AI 幻觉?以及如何让答案"可追溯可解释"?
面试官:客服最怕胡说。你怎么降低幻觉?怎么让答案可解释?
小Y:在 prompt 里让它不要胡说?
面试官:不够。还要:检索约束、引用片段、置信度阈值、拒答策略、敏感词与合规。
小Y:拒答...就是让它回答"我不知道"?
面试官:以及转人工。
Q3:Agent(智能代理)在"查订单+改地址+触发退款"这种复杂工作流怎么做?工具执行框架如何设计?
面试官:用户说"把昨天订单的收货地址改成公司,并把发票抬头改成xx"。你如何用 Agent 调用内部服务?
小Y:Agent 就是...它自己会调用?我们给它一些接口就行。
面试官:接口怎么标准化?参数怎么校验?怎么做权限?怎么防止它乱调?
小Y:呃...加鉴权?JWT?
面试官:你开始飘了。说点工程化:工具注册、schema、审批、沙箱、幂等。
小Y:嗯...我知道 MCP 是模型上下文协议...可以用来做工具调用标准化?
面试官:行,至少没完全跟不上。
Q4:聊天会话内存怎么做?短期记忆、长期记忆分别放哪?
面试官:客服是多轮对话。会话内存怎么设计?
小Y:存在 Redis?
面试官:Redis 适合短期。长期呢?
小Y:存数据库?或者向量库?
面试官:可以。关键是:隐私、过期、召回策略。
面试收尾
面试官:整体看你基础题还行,缓存那块答得不错。但微服务治理、K8s 发布、以及 AI 工程化你还需要系统补一补。
面试官:今天先到这,回去等通知。有结果我们 HR 会联系你。
小Y:好的好的!谢谢老师!(小声)回去我就把 Resilience4j 和 Spring AI 速成一遍...
面试题答案详解(按业务场景讲清楚)
目标:让小白能把每个点落到"为什么+怎么做+怎么验证"。
第一轮详解:单体服务性能与稳定性
1)对象创建频繁为何导致 GC 抖动?如何排查?
现象:接口 RT 抖动、吞吐下降、CPU 升高;GC 日志显示频繁 Young GC,甚至 Full GC。
原因链路:
- 请求高峰产生大量短生命周期对象(DTO、临时集合、字符串拼接、Jackson反序列化中间对象)。
- Eden 很快被填满 → 频繁 Minor GC。
- 如果对象"活过一次 GC"(比如被缓存、被线程池任务引用、或存活时间略长),会进入 Survivor/Old。
- Old 区增长过快或发生 晋升失败/碎片化 → 触发 Full GC(停顿更长)。
排查步骤(Java 8/11/17通用思路):
- 先证据 :开启/收集 GC 日志(Java 11+
-Xlog:gc*,Java 8-XX:+PrintGCDetails等)。 - 运行态观察 :
jstat -gcutil <pid> 1s 20看 YGC/FGC 次数与耗时。jcmd <pid> VM.native_memory summary(11+)看本地内存。
- 堆转储 :
jcmd <pid> GC.heap_dump /path/heap.hprof。 - MAT/YourKit 分析 :找 Top Dominators:
- 是否存在大集合、缓存不当、重复字符串、巨型 byte[]。
- 代码层优化 :
- 减少临时对象:复用对象/缓冲区(注意线程安全)。
- 避免无意义的
new ArrayList()/String +,使用 StringBuilder。 - Jackson 反序列化优化:字段裁剪、避免深层嵌套。
验证:对比 GC 次数、P99 RT、吞吐(QPS)、CPU 使用率。
2)为什么用线程池?参数如何设置?
为什么不用 new Thread:
- 线程创建/销毁成本高。
- 无上限会导致线程爆炸 → 上下文切换过多 → 吞吐下降,甚至 OOM(
unable to create new native thread)。
线程池参数按任务类型估算:
- CPU 密集(计算、压缩、加密):线程数≈CPU核数或核数+1。
- IO 密集 (HTTP/RPC/DB):线程数可大于核数,粗略:
Nthreads ≈ Ncpu * (1 + Wait/Compute)。
队列与拒绝策略:
LinkedBlockingQueue默认可"无限长"→ 高峰堆积任务导致内存膨胀,危险。- 建议:
- 明确队列长度(如 1000/5000),用
ArrayBlockingQueue。 - 拒绝策略:
CallerRunsPolicy:降速保护系统(常用)。AbortPolicy:快速失败(需要上层兜底)。
- 明确队列长度(如 1000/5000),用
工程化建议:
- 线程命名(便于排查):
ThreadFactory。 - 指标化:活跃线程数、队列长度、拒绝次数(Micrometer)。
3)HikariCP 为什么快?如何防止连接泄漏?
快的原因(核心点):
- 设计极简,减少锁竞争;高性能的连接管理。
- 更少的内部对象与逻辑路径,延迟更低。
防泄漏措施:
- 确保
try-with-resources正确关闭Connection/Statement/ResultSet。 - Hikari 参数:
leakDetectionThreshold:超过阈值未归还连接会打印堆栈。maxLifetime、idleTimeout、connectionTimeout防止僵尸连接。
- 监控:连接池使用率、等待时间(Micrometer 自带 binder)。
4)Redis 缓存穿透/击穿/雪崩(UGC详情)
以 GET /note/{id} 为例:
- 穿透 (大量请求不存在的 id):
- 布隆过滤器(Bloom Filter)拦截。
- 对空值缓存(短 TTL)防止重复打 DB。
- 击穿 (某热点 key 过期瞬间):
- 互斥锁(Redis setnx)或 singleflight,只有一个请求回源。
- 逻辑过期(缓存值带过期时间,后台异步刷新)。
- 雪崩 (大量 key 同时过期):
- TTL 加随机抖动。
- 分批预热、热点隔离、限流降级。
第二轮详解:微服务治理、消息与可观测性
1)Feign 为什么越调越慢?如何治理超时/重试?
常见根因:
- 下游变慢导致上游线程池/连接池耗尽(级联放大)。
- 不合理重试:在已慢的情况下重试叠加,雪上加霜。
- 超时配置不分层:连接超时、读超时、整体超时混乱。
治理套路:
- 超时:设置合理 connect/read timeout,并在网关/调用方设全局 deadline。
- 重试:只对幂等接口(GET/可安全重放的请求)重试;限制次数并加退避。
- 隔离:线程池隔离/舱壁(Resilience4j Bulkhead)。
- 熔断/限流:Resilience4j CircuitBreaker + RateLimiter。
- 降级:返回兜底数据或"稍后重试",保护核心链路。
2)Kafka 如何保证不丢与去重?
不丢(生产端):
acks=all(等 ISR 确认)+ 合理retries。- 开启幂等生产者(
enable.idempotence=true)减少重复。
不丢(消费端):
- 手动提交 offset:处理成功再 commit。
- 失败重试进入重试队列/死信队列(业务层设计)。
不重复(业务语义):
- Kafka 只能做到"至少一次/至多一次/接近恰好一次",业务通常要 幂等消费 :
- 数据库唯一键(
orderId + eventType) - Redis 去重 key(带 TTL)
- 数据库唯一键(
3)指标+日志+链路怎么落地?
- 指标(Prometheus+Grafana) :
- HTTP 请求 QPS、P95/P99、错误率
- 线程池 active/queue/reject
- Hikari 连接池 active/pending
- 日志(ELK) :
- JSON 日志结构化,关键字段:traceId、userId、orderId
- 使用 SLF4J + Logback/Log4j2,MDC 注入 traceId
- 链路(Jaeger/Zipkin + OpenTelemetry) :
- 统一 traceId 贯穿网关→服务→DB→MQ
- 关键 span:Feign 调用、SQL、Kafka produce/consume
4)K8s 灰度发布与 CI/CD
灰度/发布策略:
- RollingUpdate:逐步替换 pod,配 readiness/liveness。
- 金丝雀(Canary):
- 两套版本并存,按流量比例或按用户标签导流(配合网关/Service Mesh)。
- 回滚:
- 保留历史 ReplicaSet/镜像 tag,失败自动回滚。
流水线(Jenkins/GitLab CI):
- 单测(JUnit5 + Mockito)
- 构建(Maven/Gradle)
- 安全扫描/制品归档
- Docker build & push
- 部署到测试环境(Helm/Kustomize)
- 自动化回归/冒烟
- 灰度到生产 + 监控验收
第三轮详解:Spring AI / RAG / Agent 工程化
1)Spring AI 做 RAG 的工程链路
离线(知识入库):
- 文档加载(PDF/HTML/Confluence/数据库):Document Loaders
- 文本切分(chunking,带重叠)
- 向量化(Embedding:OpenAI/Ollama)
- 写入向量数据库(Milvus/Chroma/Redis Vector)
在线(问答):
- 用户问题 → embedding
- 向量库相似度检索 topK(语义检索)
- 将检索片段 + 业务约束(不确定就拒答)做 提示填充
- 调用大模型生成回答(带引用来源)
- 返回:答案 + 引用文档/片段 id(可追溯)
2)降低幻觉与可追溯
- 检索约束:只允许基于检索内容回答;未检索到足够证据则拒答。
- 引用输出 :要求模型输出
sources[](文档ID/段落)。 - 阈值策略:相似度低于阈值 → "转人工/创建工单"。
- 提示模板:明确格式、禁止编造。
- 合规:敏感信息脱敏、权限校验(不同用户可见知识不同)。
3)Agent 工作流与工具执行框架
核心思想:模型负责"决定调用哪个工具",系统负责"安全地执行工具"。
工程要点:
- 工具注册:每个工具有名称、描述、入参 schema、出参 schema。
- 标准化调用:可借鉴 MCP/Function Calling 思路,让调用可解析可验证。
- 权限与审计:JWT/OAuth2 校验用户身份;记录每次工具调用。
- 幂等:退款/改地址必须有幂等 key,防止重复执行。
- 沙箱与护栏:限制可调用工具集合、调用次数、参数范围;高风险操作需二次确认。
4)会话内存:短期 vs 长期
- 短期记忆 (本次会话上下文、最近N轮对话):
- Redis/内存缓存(带 TTL)
- 长期记忆 (用户偏好、历史工单摘要、可检索的历史对话):
- 结构化数据:MySQL/PostgreSQL
- 语义召回:向量库(对摘要做 embedding)
注意:隐私合规、过期策略、可删除(Right to be forgotten)。