大厂Java面试实录:Spring Boot/JPA/Redis/Kafka/K8s 可观测性 + Spring AI RAG/Agent(小Y翻车现场)
故事背景
你正在面试一家互联网大厂「内容社区 + AIGC 助手」业务线:用户发帖(UGC)、评论、点赞、关注,外加一个"智能创作助手"帮用户润色标题、自动生成摘要、以及企业侧知识库问答(RAG)。
面试官(严肃脸):M
候选人(搞笑水货程序员):小Y
规则:三轮面试,每轮 3~5 题,循序渐进。
第一轮:基础能力 + 线上常见坑(UGC发帖服务)
Q1:Spring Boot 请求进来后,到 Controller 再到 Service/DAO,大致链路是什么?你如何在链路上做统一日志与Trace?
M: 你先讲讲一次"发帖"请求在 Spring Boot 里的典型处理链路。
小Y: 就......请求进来,然后 Controller 接一下,再调用 Service,最后 MyBatis/JPA 写库。日志嘛......我一般 log.info("来了")。
M: 这只是"写代码的链路",我问的是"框架链路"。比如 Filter、Interceptor、AOP、异常处理、线程模型、TraceId。你能补充吗?
小Y: Filter 我用过,Interceptor 也差不多吧......TraceId 就随缘打印。
M:(点头但不满意)基础还行,细节得补。
Q2:你们发帖用了 JPA/Hibernate 或 MyBatis?如果出现"发帖成功但读不到"的问题,你从事务隔离和缓存角度怎么排查?
M: 发帖成功返回 200,但用户刷新列表却看不到。你会怎么定位?
小Y: 可能是缓存没更新?或者事务没提交?我一般重启试试。
M:(眉毛一挑)重启不是方案。说说事务隔离级别、读写分离延迟、二级缓存、以及 Redis 缓存一致性。
小Y: 事务隔离......有个可重复读?读写分离有延迟我知道。
M: OK,继续。
Q3:连接池你用 HikariCP 还是 C3P0?为什么大厂默认更偏向 HikariCP?关键参数你怎么设?
M: 我们高并发发帖服务,连接池怎么选、怎么配?
小Y: 我都用默认配置,HikariCP 好像快一点。
M: 默认不一定扛得住。说说 maximumPoolSize、connectionTimeout、leakDetectionThreshold、以及数据库端 max_connections 的联动。
小Y: 呃......maximumPoolSize 就越大越好?
M:(叹气)继续下一轮。
第二轮:分布式与性能(Feed流、点赞、异步化)
Q4:点赞接口如何设计才能抗住热点?用 Redis 计数 + Kafka 异步落库怎么做?
M: 点赞是典型热点。你会怎么做?
小Y: 直接 update 表加 1?不行就加索引。
M:(严肃)热点 update 会打爆行锁。请给出:Redis 原子计数、去重、Kafka 异步、幂等、以及最终一致性。
小Y: Redis INCR 我知道,Kafka 就发消息,幂等......靠运气?
M:(冷笑)运气不是设计。
Q5:你们微服务之间用 OpenFeign 还是 gRPC?怎么做超时、重试、熔断与限流?
M: 发帖后要调用「内容审核服务」「用户画像服务」。你怎么选通信方式?
小Y: 我用 Feign,写起来舒服。超时就加长点,重试就再试试。
M: 讲讲 Resilience4j 的 TimeLimiter、Retry、CircuitBreaker、Bulkhead,以及重试导致的放大流量问题。
小Y: Resilience4j 我听过,但没背过。
M:(记笔记)继续。
Q6:线上慢接口怎么定位?你会用哪些监控链路:Micrometer/Prometheus/Grafana + ELK + Jaeger/Zipkin?
M: 你被叫去救火:发帖接口 P99 从 80ms 变 800ms。你怎么排?
小Y: 先看日志吧,日志里搜 error。然后看下 CPU。
M: 说具体:指标、日志、Trace 的组合拳。比如直方图、慢 SQL、GC、线程池队列、外部依赖耗时。
小Y: 嗯嗯,我回去补。
第三轮:云原生 + AIGC/RAG(智能创作助手)
Q7:Kubernetes 上灰度发布怎么做?你如何保证"发帖服务"与"AI助手服务"升级过程中不影响用户?
M: 你们用 Kubernetes。灰度/回滚怎么设计?
小Y: K8s 我会 kubectl apply,不行就回滚一下。
M: 讲:Deployment 滚动更新参数、readiness/liveness、PodDisruptionBudget、HPA、以及金丝雀发布的流量切分。
小Y: 我一般靠运维同学。
M:(面无表情)运维同学不是答案。
Q8:智能助手要做企业知识库问答(RAG)。你如何设计:文档加载→向量化→向量库→召回→重排→提示填充→模型调用→结果引用?
M: 给你 2 周落地一个企业文档问答。你会怎么做?
小Y: RAG 就是先把文档丢进去,然后问就出来答案。
M:(盯着小Y)"丢进去"具体怎么丢?Chunk 策略?Embedding 选型?向量库用 Milvus/Chroma/Redis?怎么做引用与防幻觉?
小Y: Chunk 就切一切,Embedding 用 OpenAI?引用我就加一句"仅供参考"。
M:(记笔记)继续。
Q9:如果要做 Agent(智能代理)执行"查库存+下单+发券+通知"这种复杂工作流,你如何设计工具调用标准化?如何避免幻觉导致误操作?
M: 我们要做一个 Agent 帮运营自动处理工单:查订单、改地址、补发券。你怎么做工具框架与权限控制?
小Y: Agent 就让模型自己决定调用哪个接口......它应该不会乱来吧。
M:(冷冷地)线上系统不接受"应该"。讲:工具白名单、Schema 校验、幂等、审批、审计日志、会话内存、失败补偿。
小Y: 嗯......我觉得可以加个"确认"按钮。
M:(叹气)
面试收尾
M: 今天先到这里。你基础还可以,但分布式、可观测性、云原生和 RAG/Agent 的工程化细节比较薄。回去等通知吧。
小Y: 好的好的,我回去把 Resilience4j 和 RAG 背熟。
题目答案详解(业务场景 + 技术点,小白可直接学习)
下面把每一题的"面试官想听到的答案"拆开讲清楚,尽量贴近大厂真实工程。
A1:Spring Boot 一次请求的框架链路 & 统一日志/Trace
典型链路(Servlet 栈 Spring MVC):
- Nginx/LB :反向代理、限流、Header 透传(如
X-Request-Id)。 - Spring Boot 内置容器(Tomcat/Jetty/Undertow)接入请求。
- Filter (Servlet Filter):
- 最适合做:TraceId 注入、请求体大小限制、鉴权预处理、CORS。
- 例如自定义
OncePerRequestFilter生成/透传 TraceId(MDC)。
- Spring MVC DispatcherServlet:核心分发器。
- HandlerInterceptor :
- 更适合做:用户身份解析、业务埋点、接口耗时统计。
- Controller → Service → Repository/Mapper。
- AOP :
- 适合做:统一日志、参数校验、权限注解、事务边界(
@Transactional本质是 AOP)。
- 适合做:统一日志、参数校验、权限注解、事务边界(
- 异常处理 :
@ControllerAdvice+@ExceptionHandler统一返回结构。
统一日志/Trace(关键点):
- 通过 SLF4J + Logback/Log4j2。
- 使用 MDC 放入
traceId、userId:- 入口 Filter 创建
traceId,写入 MDC。 - 日志格式加
%X{traceId}。
- 入口 Filter 创建
- 分布式链路追踪:
- Micrometer Tracing + OTEL (或 Brave)上报到 Jaeger/Zipkin。
- 下游调用(Feign/gRPC)把 trace context 透传。
(加分点)WebFlux:线程模型是事件循环,MDC 需要 Reactor Context 适配,不能靠 ThreadLocal 直接传。
A2:发帖成功但读不到:事务、读写分离、缓存一致性排查
常见原因清单:
- 事务未提交/回滚 :
@Transactional是否生效(不要在同类内部调用导致 AOP 失效)。- 异常被吞掉导致回滚/不回滚不符合预期。
- 读写分离延迟 :
- 写入主库成功,但读走了从库(复制延迟)。
- 解决:写后读走主库、或引入"读主"策略(短时间 sticky)。
- 隔离级别导致的可见性 :
- 常见是 READ COMMITTED / REPEATABLE READ 配合长事务。
- 如果列表查询在一个长事务里,可能读到旧快照(RR)。
- 缓存不一致 :
- 列表页缓存(Redis)没失效,仍返回旧数据。
工程化做法:缓存一致性三板斧
- Cache Aside(旁路缓存):写库后删除缓存;读时缓存未命中再查库回填。
- 删除缓存要注意:
- 先写库再删缓存(常用);
- 或引入消息队列重试删除(避免删除失败)。
- 对热点列表:用 逻辑过期 + 异步刷新,降低缓存击穿。
(加分点)如果用了 Hibernate 二级缓存,要确认二级缓存与查询缓存是否开启,可能导致"写后读旧"。
A3:HikariCP vs C3P0 & 关键参数
为什么 HikariCP 常用:
- 性能和稳定性更好(更轻量,延迟低)。
- Spring Boot 默认也倾向 HikariCP。
关键参数(示例思路,不是固定值):
maximumPoolSize:- 不是越大越好。
- 需要结合:单实例 QPS、SQL 耗时、DB
max_connections、以及实例数。
minimumIdle:空闲连接预热,防止突发延迟。connectionTimeout:拿不到连接的等待时间,防止线程堆积。leakDetectionThreshold:连接泄漏检测(仅排查期启用,避免性能影响)。
与数据库联动:
- 假设 DB 允许 2000 连接,总共有 50 个应用实例,则单实例 pool 上限要有预算(比如 20~30),还要留给运维工具、其他服务。
A4:点赞热点:Redis 计数 + Kafka 异步落库 + 幂等
目标:
- 接口快(内存级)。
- DB 不被热点行锁打爆。
- 允许最终一致,但要可控。
设计:
- Redis 去重 :
SADD like:post:{postId} userId判断是否首次点赞。
- Redis 计数 :
- 首次点赞成功后
INCR like:count:{postId}。 - 取消点赞:
SREM+DECR。
- 首次点赞成功后
- Kafka 异步 :
- 发送 like/unlike 事件到 Kafka topic。
- 消费端批量落库(减少写放大)。
- 幂等(必须) :
- 事件包含
eventId或(postId,userId,action,ts)。 - 消费端用唯一键/去重表保证同一事件只处理一次。
- 事件包含
- 最终一致性兜底 :
- 定时任务把 Redis 计数与 DB 对账(或以 Kafka 日志为准重放)。
A5:Feign vs gRPC & Resilience4j 容错治理
选择:
- OpenFeign(HTTP/JSON):生态好、易调试、适合对外 REST。
- gRPC(HTTP/2 + Protobuf):性能高、IDL 强约束、适合内部高频调用。
治理:Resilience4j 常见组合:
- TimeLimiter:控制最大调用时长。
- Retry:只对幂等请求重试;注意重试会放大流量。
- CircuitBreaker:下游异常率高时快速失败,保护自身。
- Bulkhead(舱壁隔离):线程池/信号量隔离,防止被慢依赖拖垮。
- RateLimiter:限流。
要点:
- 超时要比上游更短,形成"超时金字塔"。
- 重试要加退避(backoff),并设置最大重试次数。
- 写操作尽量不重试或使用业务幂等 token。
A6:慢接口定位:指标 + 日志 + Trace
三件套:
- 指标(Metrics) :Micrometer 暴露到 Prometheus,Grafana 看:
- QPS、P95/P99 延迟直方图
- JVM:GC 次数/耗时、堆内存、线程数
- Tomcat/线程池:busy threads、queue size
- 连接池:active/idle、等待时间
- 日志(Logs) :ELK(Elasticsearch + Logstash/Fluentd + Kibana):
- 慢 SQL、异常堆栈、关键业务日志(带 traceId)
- 链路(Tracing) :Jaeger/Zipkin:
- 看一次请求在 DB、Redis、Kafka、外部 HTTP 上分别花多久
典型排查路径:
- P99 飙升 → 看是否某个下游 span 变慢 → 对应慢 SQL/连接池耗尽/GC 抖动。
A7:K8s 灰度发布与稳定性
滚动更新关键参数:
maxSurge/maxUnavailable控制替换节奏。readinessProbe:未就绪不接流量。livenessProbe:卡死自动重启。
高可用:
- PDB(PodDisruptionBudget):避免同时被驱逐太多。
- HPA:根据 CPU/自定义指标扩缩。
灰度/金丝雀:
- 基础:分批次滚动。
- 进阶:基于 Ingress/Service Mesh 做按比例/按用户标签切流。
- 必须配套:
- 指标看板(成功率、延迟)
- 一键回滚
A8:RAG 落地全链路(Spring AI/向量库/防幻觉)
**业务目标:**企业文档问答,回答要"可追溯、有引用"。
标准流水线:
- 文档加载(Document Loader):PDF/HTML/Word/Confluence 等。
- 清洗与切分(Chunking) :
- 按语义段落/标题切;
- chunk 大小常见 300~800 tokens,配 overlap(例如 50~100 tokens)。
- 向量化(Embedding) :
- 选 OpenAI/本地 Ollama embedding 模型;
- 保证同一语料同一模型版本,方便重建。
- 向量库(Milvus/Chroma/Redis Vector) :
- 存
vector + docId + chunkId + metadata(来源,页码,权限)。
- 存
- 召回(Retrieval) :
- 相似度检索 topK。
- 可混合 BM25(Elasticsearch)做混合检索。
- 重排(Rerank,可选):提升相关性。
- 提示填充(Prompt Template) :
- 把检索片段以"引用块"形式塞进 prompt。
- 模型调用(LLM) :
- 输出答案 + 引用列表。
- 防幻觉策略:
- 明确指令:只根据提供的 context 回答;不知道就说不知道。
- 输出强制包含引用(无引用则拒答)。
- 关键场景加人工审核/置信度阈值。
Spring AI 对应点:
VectorStore(Milvus/Redis 等实现)EmbeddingModelRetriever+PromptTemplate- 会话记忆(Chat Memory)用于多轮问答,但要注意权限隔离。
A9:Agent 工具调用框架:标准化、权限、审计、反幻觉误操作
**目标:**让模型"会用工具",但不允许乱用工具。
核心设计:
- 工具白名单 + Schema :
- 每个工具定义:name、description、JSON Schema(入参/出参)。
- MCP/函数调用标准化就是为这个服务的。
- 权限控制 :
- 工具执行前做鉴权(RBAC/ABAC)。
- 细到"能查不能改""能改必须审批"。
- 幂等与审计 :
- 工具调用要有 requestId。
- 所有工具调用写审计日志(谁、何时、改了什么)。
- 高风险动作二次确认 :
- 改地址、退款、发券:必须 human-in-the-loop。
- 失败补偿 :
- 工作流要支持重试、回滚、死信队列。
- 会话内存 :
- 只保存必要信息;注意不同用户/租户隔离。
避免"幻觉操作"的硬规则:
- 模型输出不能直接执行写操作;必须通过工具层校验。
- 工具层不信任模型:校验参数、范围、业务约束。
你可以如何用这套答案去准备面试
- 把每题当成一条"项目叙事线":UGC发帖 → 点赞热点 → 微服务治理 → 可观测性 → K8s 灰度 → RAG/Agent 落地。
- 真实面试里,面试官更喜欢你讲:为什么这么做、踩过什么坑、怎么验证有效。