大厂 Java 面试实录:Spring Boot 微服务 + Kafka/Redis + K8s 可观测性 + RAG/Agent(小Y社死版)
故事背景
某互联网大厂电商事业部,准备做一个**"AIGC 智能客服 + 内容社区(UGC)"**的融合项目:
- 用户在商品页/社区发帖里提问(物流、售后、尺码、活动规则)。
- 客服系统先走知识库检索(RAG) ,不够再走工单/人工。
- 高并发、强可观测、要能灰度、要能抗故障。
面试官(严肃、惜字如金) vs 候选人小Y(搞笑水货但自信)。
第一轮:从单体到微服务的基本功(3-5问,循序渐进)
Q1(场景引入):
面试官: 电商客服入口在 App 内,QPS 高峰 2w+。你会怎么用 Spring Boot 快速搭一个「会话服务」?核心接口怎么设计?
小Y: 这题我熟!Spring Boot 起手 @RestController,然后 POST /chat/send、GET /chat/history。存储我先用 MySQL,缓存 Redis,保证丝滑。
面试官: 嗯,接口拆得还算直观。那你怎么考虑幂等?
小Y: 幂等...就是别重复嘛。可以传个 requestId,然后......就不重复了。
面试官: (点头)方向对,细节后面再补。
Q2(JVM 基础):
面试官: Java 11/17 下,面对大量短生命周期对象(每条消息都要序列化/反序列化、拼 prompt),你会如何看 GC?G1 适合吗?
小Y: GC 我懂,G1 就是"均匀地回收",适合大堆。我们用它就行了,反正不会 OOM。
面试官: (沉默 1 秒)你说的"不会 OOM"依据是什么?
小Y: 依据是......我感觉。
面试官: 好,我们继续。
Q3(数据库与连接池):
面试官: 会话消息落库,MySQL 写多读多。为什么大厂几乎都用 HikariCP?C3P0 的问题在哪?
小Y: HikariCP 快!C3P0...比较老。大概就是性能差点。
面试官: 还行,至少知道主流。
Q4(API 设计与序列化):
面试官: 给我一个消息发送接口的 OpenAPI/Swagger 关键要点。JSON 用 Jackson 没问题,但如果要做跨语言网关,为什么很多团队会选 Protobuf?
小Y: Swagger 就写注解 @Operation 啥的。Protobuf...因为它"更小更快"?
面试官: 对,继续保持。
第二轮:微服务稳定性与链路(3-5问,层层加压)
Q1(Kafka/消息链路):
面试官: 智能客服要把用户消息异步投递到「检索服务」和「LLM 编排服务」。Kafka 怎么保证不丢消息?至少说出生产端与消费端各一条策略。
小Y: 不丢消息...Kafka 天生就不丢吧。生产端我开个 acks=all,消费端我......自动提交 offset?
面试官: (眉头一动)自动提交 offset 你确定?
小Y: 呃......我觉得差不多。
Q2(分布式限流与熔断):
面试官: LLM 调用贵且慢。你会在 OpenFeign 或 WebClient 调用外部模型时怎么做超时、重试、熔断、隔离?Resilience4j 怎么落?
小Y: 这题我也会!设置 timeout,然后失败重试,熔断我就"关掉接口"......Resilience4j 就加依赖然后 @CircuitBreaker。
面试官: 注解能写出来算入门,但隔离策略呢?线程池/信号量?
小Y: 隔离...就是别让它影响别的请求。可以多加点机器?
面试官: (叹气)继续。
Q3(Redis 缓存与一致性):
面试官: 商品规则、活动解释等 FAQ 可以缓存。你如何用 Spring Cache + Redis 做缓存?缓存穿透/击穿/雪崩怎么处理?
小Y: Spring Cache 我会 @Cacheable。穿透就加布隆过滤器,击穿就加锁,雪崩就加随机过期时间。
面试官: 这段回答不错,继续往下。
Q4(可观测性):
面试官: 线上出现"响应变慢",你如何用 Micrometer + Prometheus + Grafana 定位?分布式追踪 Jaeger/Zipkin 怎么串起来?
小Y: Grafana 看图,Prometheus 拉指标。Jaeger 就是看链路,慢在哪里一眼就知道。
面试官: (点头)方向对,但需要你说出关键指标和埋点位置。
第三轮:AIGC/RAG/Agent + 企业级落地(3-5问,终极拷打)
Q1(RAG 设计):
面试官: 我们要做企业知识库问答:商品说明、售后政策、物流规则、社区规范。请你描述 RAG 的链路:文档加载、切分、向量化、向量库、召回、重排、提示填充。并说说如何降低幻觉。
小Y: RAG 我最近也看了!就是先把文档放进去,然后问的时候去搜出来,再喂给大模型。降低幻觉嘛......就让它"别瞎说"。
面试官: (冷笑一声)"别瞎说"写在 prompt 里确实有用,但不够。
Q2(Agent 与工具调用):
面试官: 用户问"我的订单为什么还没发货",Agent 需要调用工具:订单查询、物流轨迹、优惠券补偿规则。你怎么设计工具执行框架?MCP/Google A2A/函数调用标准化你了解多少?
小Y: Agent 就是让模型自己干活。我可以给它三个接口地址,它会自己请求。MCP 我听过,好像是"模型上下文协议",A2A 就是......AI to AI?
面试官: (面无表情)了解程度:听过。
Q3(会话记忆与权限安全):
面试官: 会话内存怎么做?哪些信息可以长期记忆,哪些必须短期?如何用 Spring Security + OAuth2/JWT 做用户鉴权,避免越权查订单?
小Y: 记忆就存 Redis!长期短期都存。鉴权用 JWT,解析一下就知道是谁。越权...就别让他查到别人的。
面试官: 你说的都是结论,没有机制。
Q4(云原生与灰度):
面试官: 我们上 K8s,LLM 编排服务要灰度发布。你如何用 Kubernetes + Jenkins/GitLab CI 做流水线?如何用指标与日志判断灰度是否扩大?
小Y: K8s 就 rolling update,Jenkins 点一下就行。灰度看错误率,如果没错就全量。
面试官: (合上笔记本)今天先到这。
面试收尾
面试官: 你的基础题还可以,但在分布式可靠性、可观测落地、以及 RAG/Agent 工程化细节上需要加强。你先回去等通知,有结果我们 HR 会联系。
小Y: 好的好的,我回去就把"别瞎说"优化成"请你务必不要瞎说"!
文末学习区:本场所有问题的「详细答案与落地要点」
下面按轮次逐题给出更工程化、更可落地的答案(结合电商智能客服/UGC 业务)。
第一轮答案
1)会话服务怎么设计?接口、幂等、存储
业务点:用户可能重复点击发送、网络重试;客服系统必须保证"同一条消息不重复入库、不重复触发下游"。
接口建议(示例):
POST /api/v1/conversations/{cid}/messages- Header:
Idempotency-Key(或 body 里的requestId) - Body:
{senderId, content, clientTs, msgType, attachments...}
- Header:
GET /api/v1/conversations/{cid}/messages?cursor=...&limit=...(游标分页)
幂等实现(常用三选一,可组合):
- 数据库唯一约束 :
(conversation_id, request_id)建唯一索引;插入用insert ... on duplicate key(MySQL)或捕获唯一键异常。 - Redis 幂等键 :
SETNX idem:{cid}:{requestId} 1 EX 300,成功才执行后续;用于"先挡住重复请求"。 - 消息链路幂等 :下游消费端基于
messageId做去重表/Redis set。
存储建议:
- 写扩展:分库分表或按会话分片;
- 读优化:最近消息放 Redis(ZSET/列表)或 ES 做全文检索(投诉/工单检索)。
2)JVM/G1:大量短对象如何看 GC?
业务点:prompt 拼装、JSON/Protobuf 序列化、日志 MDC、链路对象都会制造短命对象,容易造成 Young GC 频繁;当延迟抖动时要能定位。
为什么 G1 常用:
- 适合大堆、可预测停顿(目标 pause time);
- Region 化回收,Mixed GC 能逐步回收老年代。
怎么评估"不会 OOM":
- 观察
Old Gen增长趋势、to-space exhausted、Humongous分配; - 开启 GC 日志(Java 11/17):
-Xlog:gc*:file=gc.log:time,uptime,level,tags
- 用
jcmd,jstat,async-profiler看分配热点。
优化抓手:
- 减少大对象/超长字符串拼接(用 StringBuilder、复用 buffer);
- 避免把大对象放进缓存;
- 控制日志量(尤其是大 JSON 打印)。
3)HikariCP vs C3P0
结论:HikariCP 轻量、延迟低、并发性能好、监控指标友好;C3P0 较老,配置复杂且性能/稳定性在现代高并发场景下不占优。
工程建议:
- 连接池大小不是越大越好,通常与 DB CPU/IO 能力匹配;
- 指标:
active,idle,pending,以及慢 SQL 监控。
4)Swagger/OpenAPI 与 Protobuf
Swagger 要点:
- 请求/响应 schema 清晰;错误码与示例齐全;
- 幂等字段说明(Idempotency-Key);
- 分页方式(cursor vs pageNo)明确。
Protobuf 常用于跨语言:
- 二进制体积小、序列化快;
- 强 schema、向后兼容机制(字段号);
- 适合 gRPC(高性能内部 RPC)。
第二轮答案
1)Kafka 如何保证不丢消息(生产端/消费端)
生产端(Producer):
acks=all+enable.idempotence=true(幂等生产)- 合理设置
retries、delivery.timeout.ms,避免无止境阻塞; - 关键主题设置副本数
replication.factor>=3,min.insync.replicas合理。
消费端(Consumer):
- 关闭自动提交 :
enable.auto.commit=false - 处理成功后再提交 offset(手动 commit);
- 失败进入重试队列/死信队列(DLQ),避免卡死;
- 做业务幂等:基于
messageId去重。
业务解释:客服消息若丢失会导致"用户发了但系统没回应",体验灾难;宁可重复也别丢,所以要"至少一次 + 幂等"。
2)Resilience4j:超时、重试、熔断、隔离
目标:外部 LLM/检索服务慢或抖动时,不拖垮主链路。
落地组合:
- TimeLimiter:严格控制单次调用时长;
- Retry:只对可重试错误重试(超时/5xx),并设置退避;
- CircuitBreaker:错误率/慢调用率超过阈值打开;
- Bulkhead(隔离) :
- SemaphoreBulkhead:限制并发数(适合反应式/非阻塞);
- ThreadPoolBulkhead:线程池隔离(适合阻塞调用)。
关键点:
- 不要"无脑重试"导致雪崩;
- 熔断打开时返回降级:
- 给用户可解释的回复("当前繁忙,已为你创建工单");
- 或返回 RAG 低成本答案。
3)Redis 缓存:穿透/击穿/雪崩
- 穿透 (查不存在):
- 布隆过滤器(RedisBloom/自建);
- 缓存空值(短 TTL)。
- 击穿 (热点 key 失效瞬间):
- 互斥锁重建(
SETNX lock:key); - 提前异步刷新(逻辑过期)。
- 互斥锁重建(
- 雪崩 (大量 key 同时过期/Redis 故障):
- TTL 加随机;
- 多级缓存(Caffeine 本地 + Redis);
- 限流/熔断 + 降级。
结合 Spring Cache:
@Cacheable+ 自定义 key;- 对"活动规则"这类强一致性要求不高的数据非常合适。
4)Micrometer/Prometheus/Grafana + Jaeger/Zipkin
定位慢建议从三板斧:
- RED 指标(请求量 Rate、错误 Error、耗时 Duration)
- 关键依赖(DB、Redis、Kafka、外部 LLM)的耗时与错误率
- GC/CPU/线程池/连接池指标
Micrometer 埋点:
- Controller(入口延迟、状态码)
- Feign/WebClient(外呼耗时、错误码)
- 数据库连接池(Hikari metrics)
分布式追踪:
- 统一 traceId(W3C Trace Context / B3)
- 在网关、服务端、消息消费端继续传递(Kafka header)
- Jaeger/Zipkin 展示完整调用树,定位慢 span。
日志:
- ELK(或 OpenSearch)按 traceId 关联。
第三轮答案
1)RAG 全链路与降低幻觉
链路:
- 文档加载(文档库/工单/FAQ/商品说明):
- Spring AI 文档加载器或自研 ETL;
- 清洗与切分(chunk):
- 按段落/标题;控制 chunk size 与 overlap;
- 向量化(Embedding):
- OpenAI / Ollama 本地模型;
- 向量库:
- Milvus/Chroma/Redis Vector;
- 召回:
- TopK 相似度 + 元数据过滤(类目、语言、时间、生效状态);
- 重排(Rerank):
- 交叉编码器/规则重排,提高相关性;
- 提示填充(Prompt stuffing):
- 将检索片段以"证据"形式加入 system/user prompt;
- 生成答案 + 引用证据:
- 返回引用来源(文档 id、段落)。
降低幻觉(工程手段,不靠"求你别瞎说"):
- 回答必须基于证据:无证据则拒答/转人工;
- 设定置信度阈值:向量相似度过低直接降级;
- 输出结构化:如 JSON schema,减少自由发挥;
- 对关键字段做工具校验:订单号、金额、时间必须走工具查询;
- 评测与回归:用标注集评估命中率/幻觉率。
2)Agent 工具执行框架 + MCP/A2A/标准化
业务点:用户问"订单没发货",答案不能靠编;必须查订单状态、仓库出库、物流轨迹,再结合补偿规则。
工具执行框架核心要素:
- 工具注册表(Tool Registry):name、描述、入参 schema、鉴权需求;
- 工具网关(Tool Gateway):统一超时、重试、限流、审计日志;
- 执行计划:
- Planner(决定调用哪些工具、顺序)
- Executor(实际调用并回填结果)
- 结果校验:对关键字段做格式与权限校验。
**MCP(模型上下文协议)**理解:
- 让模型以标准方式访问"工具/资源/提示",把工具能力外置到 MCP Server;
- 客户端(你的 Java 服务)以 MCP Client 连接多个能力源(DB、搜索、内部 API)。
A2A(Agent-to-Agent):
- 多 Agent 协作协议/模式:如"检索 Agent""订单 Agent""补偿 Agent"分工,通过标准消息互通。
落地建议(Java):
- Spring AI + Tool/Function calling;
- 工具调用强制加:超时、隔离、审计、脱敏。
3)会话记忆与安全(Spring Security + OAuth2/JWT)
会话记忆分层:
- 短期记忆 :本次会话上下文(最近 N 轮对话、用户当前问题)
- 存 Redis,TTL 30min;
- 长期记忆 :用户偏好(语言、常用地址等)
- 存数据库,需用户授权、可删除;
- 禁止记忆 :身份证、银行卡、病历等敏感信息(合规要求)
- 做脱敏与 DLP(数据防泄漏)策略。
鉴权:
- OAuth2 登录后颁发 JWT(含 userId、scope、过期时间);
- Spring Security:
- Resource Server 校验 JWT 签名与过期;
- 方法级鉴权
@PreAuthorize控制 scope/role;
- 防越权 :
- 工具层(订单查询)必须校验
order.userId == token.userId; - 不信任前端传的 userId。
- 工具层(订单查询)必须校验
4)K8s 灰度发布与 CI/CD
流水线(Jenkins/GitLab CI):
- 单元测试(JUnit5/Mockito)→ 集成测试 → 构建镜像(Docker)→ 扫描(依赖/镜像漏洞)→ 推送镜像仓库 → 部署到 K8s
灰度策略:
- 金丝雀发布:10% 流量 → 30% → 100%
- 或按用户分桶/按地域灰度。
放量依据(可观测):
- SLI/SLO:P95/P99 延迟、5xx 错误率、下游 LLM 超时率、Kafka 消费堆积;
- 业务指标:问题一次解决率、转人工率、投诉率;
- 日志抽样:检查是否出现敏感信息泄露/幻觉关键词。
你可以怎么补齐"小Y缺的那块"
- Kafka:至少一次语义 + 幂等消费 + DLQ
- Resilience4j:隔离与降级策略要能说清
- 可观测:指标/追踪/日志三件套要知道"埋在哪里,看什么图"
- RAG/Agent:别停留在概念,能画出链路与拒答/校验机制