电商AIGC智能客服面试实录:JVM、Spring Cloud、Redis、Kafka、K8s、RAG一锅端
场景:某互联网大厂电商事业群------AIGC智能客服与内容审核平台。
角色:
- 面试官(严肃、逻辑强)
- 小Y(水货但嘴硬,简单题能答,难题就开始"玄学")
第一轮:从"下单咨询"入口讲起(Java基础、并发、JVM、Web框架)
Q1:
面试官: 我们智能客服入口是一个高并发HTTP服务(咨询量峰值10万QPS),你用 Spring MVC 还是 Spring WebFlux?怎么选?
小Y: 那肯定 Spring Boot 一把梭......MVC我熟,WebFlux也可以"搞",主要看同事用啥。
面试官:(皱眉)说点硬的。
小Y: MVC是线程模型,一个请求一个线程;WebFlux是响应式,适合IO多的场景。我们客服要调很多下游接口,WebFlux可能更省线程。
面试官: 还行,知道关键差异。那如果下游全是阻塞JDBC呢?
小Y: 额......那就......把阻塞变不阻塞?或者......开大线程池?
面试官: 行,先记下。
Q2:
面试官: 你在Java 17里如何解释 JVM内存结构,以及"频繁Full GC"在这个客服服务里可能是什么原因?
小Y: JVM有堆、栈、方法区......堆分新生代老年代。Full GC多可能是对象太多、内存小,或者代码写得太浪费,比如 new 太多。
面试官:(点头)方向对。那"对象太多"具体可能来自哪些业务动作?
小Y: 比如每次请求都把一大段聊天记录拼成字符串......或者把返回结果都缓存进内存。
面试官: 这回答不错,能联系业务。
Q3:
面试官: 客服对话要维护"会话上下文",你用什么并发容器存会话?以及如何避免内存爆?
小Y: 用 ConcurrentHashMap 存 sessionId -> 上下文对象。避免爆就......定时清理?
面试官: 具体点:怎么清理?
小Y: 用 ScheduledExecutorService 每隔一分钟扫描一遍,过期就删。
面试官: 可以,但扫描成本大。有没有更工程化的做法?
小Y:(开始飘)也可以用 Redis,或者用 Caffeine 自动过期......
面试官: 好,至少你知道别一直Map塞到底。
Q4:
面试官: 我们要把一次咨询拆成"鉴权、限流、路由、业务处理"。在 Spring Boot 里你会在哪些层做?
小Y: 鉴权用 Spring Security;限流......可以用过滤器或者网关;路由用 Spring Cloud Gateway;业务处理就是 Controller + Service。
面试官: 这题答得清楚。
第二轮:微服务化与稳定性(Spring Cloud、Kafka、Redis、数据库、事务)
面试官把白板上画了三块: 1)对话服务 Chat-Service 2)知识库检索服务 Retrieval-Service 3)生成服务 LLM-Proxy-Service
Q1:
面试官: 你会用 OpenFeign 还是 gRPC 调用下游?分别适合什么?
小Y: Feign简单,HTTP就能调;gRPC更快,用Protobuf,适合内部高性能调用。
面试官: 继续:在多语言团队里呢?
小Y: gRPC更统一,因为IDL定义好,各语言生成代码。
面试官: 不错。
Q2:
面试官: 我们把"用户问题 + 订单信息 + 历史对话"组装后写入 Kafka,异步做质检与训练数据沉淀。你怎么设计 Kafka Topic/Partition?如何保证同一会话有序?
小Y: Topic就叫 chat_event。Partition多开点提高吞吐。有序就按 sessionId 做 key,这样同一个 session 会落到同一个分区。
面试官: 很好。那"Exactly Once"你怎么理解?
小Y: 就是......只消费一次,Kafka能做到吧......
面试官:(冷笑)"能做到吧"在我们这不算答案。
Q3:
面试官: 订单信息你会从数据库查。说说你在 Spring Boot 里会选 JPA/Hibernate 、MyBatis 、还是 Spring Data JDBC?为什么?
小Y: 复杂SQL用MyBatis,JPA适合简单CRUD;Spring Data JDBC更轻量。
面试官: 在"订单+商品+优惠"这种多表组合、还要强可控SQL的场景?
小Y: 那我选 MyBatis,SQL写死,性能也好调。
面试官: OK。
Q4:
面试官: 咨询接口要查订单信息,且热点订单很集中。你怎么用 Redis + Spring Cache 做缓存?如何处理缓存穿透、击穿、雪崩?
小Y: 用 @Cacheable 缓存订单。穿透就缓存空值或布隆过滤器;击穿用互斥锁;雪崩加随机过期。
面试官: 这题答得不错,继续:互斥锁你用什么实现?
小Y:(开始含糊)就......Redis setnx,或者 Redisson。
面试官: 行。
第三轮:云原生观测 + AI工程化(K8s、Micrometer、Tracing、RAG、向量库、Agent)
Q1:
面试官: 线上出现"客服回复慢",你怎么用 Micrometer + Prometheus + Grafana 定位?至少说三个关键指标。
小Y: 看QPS、平均响应时间、错误率。还可以看JVM内存和GC。
面试官: 可以。那你会怎么区分是下游慢还是线程池打满?
小Y:(勉强)看接口分段耗时......再看线程池活跃线程数。
面试官: 还算及格。
Q2:
面试官: 你如何用 Jaeger/Zipkin 做分布式链路追踪?在 Spring Cloud 里怎么把 traceId 贯穿 Feign、Kafka?
小Y: traceId 放在 header 里,日志也打印。Kafka 就把 traceId 放在消息里。
面试官: 哪个库负责自动注入?
小Y:(开始飘)Spring Cloud......自己就行吧?
面试官: 行,那你回去补一下"自动注入靠谁"。
Q3:
面试官: 说说 RAG:我们要做"企业知识库问答",包括文档加载、向量化、语义检索、再生成。你如何设计端到端流程?向量库你选 Milvus/Chroma/Redis 的考虑点是什么?
小Y: 流程就是:文档切片 -> embedding -> 存向量库 -> query embedding -> topK检索 -> 拼到prompt -> 调大模型生成。
面试官: 不错。那"幻觉"怎么缓解?
小Y:(含糊其辞)让模型别乱说......加提示词,或者多检索点。
面试官:(叹气)行,至少你知道幻觉是问题。
Q4:
面试官: 我们要把"下单问题"交给 Agent 自动执行,比如查物流、改地址、申请退款。你怎么理解 MCP(模型上下文协议)/工具调用标准化?以及如何做权限控制?
小Y: MCP就是......给模型一个统一的工具接口标准?让它会调用工具。权限就......用JWT。
面试官: 你说对了一半。回去把"工具注册、参数校验、审计、幂等"梳理清楚。
面试收尾
面试官: 今天先到这。你基础题还行,但复杂工程化问题回答不够落地。回去等通知,有结果我们HR会联系你。
小Y: 好的好的,我回去把"能做到吧"改成"我能做到"。
文末学习区:逐题详细答案(结合业务场景 + 技术点)
下面把面试官问到的每个点,按"业务场景 → 技术方案 → 关键细节/坑"讲清楚。
第一轮答案
1)Spring MVC vs Spring WebFlux:客服入口怎么选?
业务场景:咨询接口要聚合多个下游(订单、物流、知识库、LLM代理),IO等待多。
技术结论:
- Spring MVC(Servlet + 线程模型)
- 优点:生态成熟、调试容易;对阻塞式组件(JDBC、MyBatis)天然匹配。
- 缺点:高并发下线程数上涨快,线程上下文切换成本高。
- Spring WebFlux(Reactive + 事件循环)
- 优点:适合大量IO等待(HTTP/R2DBC/Reactive Redis);用更少线程扛更高并发。
- 缺点:如果下游仍是阻塞式(JDBC、传统SDK),会把事件线程阻塞住,性能反而更差。
落地建议:
- 下游是 阻塞式JDBC/MyBatis :优先 MVC;或用 WebFlux 但必须把阻塞调用隔离到专用线程池(
Schedulers.boundedElastic()),否则得不偿失。 - 下游支持 Reactive(R2DBC、Reactive Feign/HTTP client):WebFlux 更香。
2)JVM内存结构与频繁 Full GC 的原因定位
业务场景:客服请求里可能携带大量历史对话,且需要组装prompt,字符串操作重。
JVM关键结构(简化):
- 堆(Heap):对象主要存放处,新生代/老年代。
- 栈(Thread Stack):局部变量、方法调用。
- 元空间(Metaspace):类元数据(Java 8后替代方法区概念)。
Full GC 可能原因(贴近业务):
- 大对象/长字符串:prompt 拼接、日志打印整段对话导致大量 char[]。
- 缓存误用:把会话上下文放本地 Map 不过期。
- 对象生命周期变长:线程池队列堆积,Request/Context 被引用无法回收。
排查路径:
- 打开 GC 日志(Java 11+ 推荐
-Xlog:gc*)看频率/停顿。 jmap/jcmd导出 heap dump,用 MAT 分析占用最大的对象。- 优化:减少不必要的字符串拼接(StringBuilder/分段),限制上下文长度,必要时把会话放 Redis。
3)会话上下文存储:并发容器与防内存爆
业务场景:每个 session 需要保存最近 N 轮对话,用于连续对话与RAG。
方案对比:
- 本地 ConcurrentHashMap:访问快,但多实例下不共享,且易内存爆。
- Caffeine(推荐本地缓存):支持最大容量、expireAfterAccess/Write、统计。
- Redis(推荐分布式):多实例共享、可持久化、可设置 TTL。
更工程化做法:
- 本地:Caffeine 做 LRU/TTL(避免全表扫描清理)。
- 分布式:Redis
SETEX或 Hash + TTL;限制每会话上下文长度(如保留最近 10 轮)。
4)鉴权/限流/路由/业务处理的分层
业务场景:入口需要挡住非法请求、控制流量并路由到不同模型能力。
常见分层:
- 网关层(Spring Cloud Gateway / Zuul Legacy):统一限流、路由、灰度、黑白名单。
- 应用层 Filter/Interceptor:补充细粒度控制、打点。
- 安全层(Spring Security / OAuth2 / JWT / Keycloak):认证授权。
- 业务层(Controller/Service):聚合下游、编排RAG/Agent流程。
第二轮答案
1)OpenFeign vs gRPC:服务间调用选型
业务场景:Chat-Service 调 Retrieval/LLM-Proxy。
- OpenFeign(HTTP/JSON)
- 快速集成、调试友好、对外接口更自然。
- 性能与协议开销略大。
- gRPC(HTTP/2 + Protobuf)
- 低延迟、强类型IDL、多语言一致性好,适合内部高频调用。
建议:对外 REST + OpenAPI;内部高频链路(检索/特征/Embedding)优先 gRPC。
2)Kafka 设计:Topic/Partition 与会话有序、Exactly Once
业务场景:对话事件进入 Kafka,供质检、训练样本沉淀、风控。
有序性:
- Kafka 只保证 同一 Partition 内有序。
- 用
sessionId作为 key:同一会话落同一分区 → 保序。
Partition 数:
- 根据吞吐与消费者并发确定:消费者组内并发上限 = partition 数。
- 注意:partition 太多会增加 broker 负担与 re-balance 成本。
Exactly Once(EOS)要点:
- 不是"玄学只消费一次",而是:不多处理也不少处理。
- Kafka 通过 幂等生产者 + 事务性 producer,以及消费端"读-处理-写"事务保证端到端一致。
- 落地常见做法:
- 消费端处理需要写数据库时:更常用 至少一次 + 业务幂等(例如用消息ID去重表/唯一约束)。
3)JPA/Hibernate vs MyBatis vs Spring Data JDBC
业务场景:订单域多表、复杂查询、性能敏感。
- MyBatis :
- 优点:SQL可控,适合复杂 join、分库分表场景。
- 缺点:需要维护SQL与Mapper。
- JPA/Hibernate :
- 优点:领域建模与CRUD省事。
- 缺点:复杂SQL与性能调优成本高,N+1、懒加载坑多。
- Spring Data JDBC :
- 更贴近 JDBC,简单聚合根,功能比JPA少但更直观。
结论:订单这种核心域,常见选 MyBatis 或 "MyBatis + 少量JPA"。
4)Redis + Spring Cache:穿透/击穿/雪崩
业务场景:咨询接口频繁查订单,热点集中。
-
基础用法:
@Cacheable(value="order", key="#orderId")- 底层可用 Redis 作为 CacheManager。
-
缓存穿透(查不存在的数据):
- 缓存空值(短TTL)
- 布隆过滤器(Bloom Filter)挡掉明显不存在的 key
-
缓存击穿(热点 key 过期瞬间并发打 DB):
- 互斥锁:Redis
SET key value NX PX或 RedissonRLock - 或"逻辑过期":缓存里存数据+过期时间,异步刷新
- 互斥锁:Redis
-
缓存雪崩(大量 key 同时过期):
- TTL 加随机抖动
- 多级缓存(本地Caffeine + Redis)
第三轮答案
1)Micrometer + Prometheus + Grafana:慢在哪里?
业务场景:用户说"客服回复慢"。
关键指标(至少三个):
- 请求延迟:P95/P99(平均值不够)
- 吞吐/QPS:流量是否突增
- 错误率:5xx、超时
- 线程池指标:active、queue size、reject count
- JVM指标:GC pause、heap 使用率
区分下游慢 vs 线程池满:
- 下游慢:外部HTTP/gRPC client 的 latency 飙升;trace 显示下游 span 时间长。
- 线程池满:队列长度上涨、reject 增加、应用自身处理耗时增加。
2)Jaeger/Zipkin 链路追踪:traceId 如何贯穿 Feign、Kafka?
业务场景:一次咨询会跨多个微服务与异步消息。
落地方案:
- HTTP/Feign:在拦截器里把 traceId 注入 header;日志 pattern 打印 traceId。
- Kafka:把 traceId 放入 message header(Kafka Headers)或消息体。
- 常见实现依赖:
- Spring 生态通常通过 Micrometer Tracing + Brave/OTel 自动生成/传播 trace。
3)RAG 端到端流程 + 向量库选型 + 幻觉治理
业务场景:企业知识库问答(售后政策、发票规则、物流时效)。
标准 RAG 流程:
- 文档加载(PDF/HTML/DB/Confluence)
- 清洗与切片(chunking)
- Embedding 向量化
- 写入向量库(向量 + 元数据)
- Query embedding
- TopK 语义检索(可加 rerank)
- 拼 Prompt(提示填充:引用片段、来源、约束)
- 调用 LLM 生成
- 返回答案 + 引用来源
向量库选型要点:
- Milvus:大规模向量、检索性能强、生产级能力强。
- Chroma:轻量、适合中小规模/原型。
- Redis Vector:如果本来就用 Redis 生态,运维成本低,适合中等规模。
幻觉缓解(要能落地):
- 强制"只根据检索片段回答",无资料则回复"不确定"。
- 输出引用来源(docId/段落),便于审计。
- 使用 rerank 提升相关性,减少"检索不准导致胡说"。
- 对关键业务(退款、赔付)加规则校验与人工兜底。
4)Agent + MCP:工具调用标准化与权限控制
业务场景:模型自动执行"查物流、改地址、退款"等动作。
MCP/工具调用标准化核心:
- 把工具(API)以统一协议暴露给模型:
- 工具描述(名称、用途)
- 参数 schema(类型/必填/枚举)
- 返回结构
- 好处:模型可以在不同工具/服务间"可插拔"地调用,便于扩展。
工程化必备(小Y缺的就是这些):
- 参数校验:防止模型拼错字段/注入。
- 权限与审计 :
- 用户态权限:OAuth2/JWT + 细粒度 RBAC
- 工具调用审计日志:谁在什么时间触发了什么动作
- 幂等:退款/改地址必须支持幂等(幂等key、业务唯一约束)。
- 风控:高风险动作二次确认(短信/人脸/人工确认)。
你可以用这篇文章怎么练习?
- 把三轮问题当成"面试脚本",自己用白板复述一遍。
- 对照文末答案,把"含糊点"补成"可落地的工程方案"。